Skip to content

Commit 0f7273c

Browse files
jushgithub-actions[bot]
authored andcommitted
Integrate the new camera changed coalesced into platform code (#3562)
PR #3525 introduced a new event: camera changed coalesced. We do not currently expose the new event fully. It is annotated with `@RestrictTo(Scope.LIBRARY_GROUP_PREFIX)` and also `MapboxExperimental`. We might expose it later on in 11.13.0-beta.1. This PR substitutes the previous use of `cameraChanged` event in scalebar and `MapCamera` plugins. Previously, with `cameraChanged` event, we were running unnecessary code in those cases where the camera state changed internally but it was not the one being rendered in the map. That was clearly visible when doing an animation (for example `flyTo`) where each animator calls `setCamera`. Every `setCamera` results in `cameraChanged` event. The new `cameraChangedCoalesced` will only be triggered once, once all the `setCamera` are done and the new camera options to be rendered is known. https://mapbox.atlassian.net/browse/MAPSAND-2116 cc @mapbox/sdk-ci GitOrigin-RevId: 394adbb4e8c7d40c37ab2326ed1538042955508c
1 parent ab4836a commit 0f7273c

File tree

30 files changed

+386
-94
lines changed

30 files changed

+386
-94
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ Mapbox welcomes participation and contributions from everyone.
77
* Expose `RenderThreadStatsRecorder` as experimental API.
88
* Expose an experimental API to define a non-rectangular screen culling shape(`MapboxMap.get/setScreenCullingShape`)
99

10+
## Bug fixes 🐞
11+
* Fix registering camera change listener every time `mapView.scalebar.enabled` is called with value `true` and it was already enabled.
1012

1113
# 11.12.0-rc.1
1214
## Features ✨ and improvements 🏁

app/src/androidTest/java/com/mapbox/maps/testapp/observable/ObservableEventsTest.kt

Lines changed: 95 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import androidx.core.content.ContextCompat
55
import androidx.core.graphics.drawable.toBitmap
66
import androidx.test.ext.junit.runners.AndroidJUnit4
77
import androidx.test.filters.LargeTest
8+
import com.mapbox.annotation.MapboxExperimental
89
import com.mapbox.bindgen.DataRef
910
import com.mapbox.common.Cancelable
1011
import com.mapbox.geojson.Point
@@ -18,6 +19,7 @@ import com.mapbox.maps.testapp.BaseMapTest
1819
import com.mapbox.maps.testapp.R
1920
import org.junit.Assert.assertEquals
2021
import org.junit.Assert.assertNotNull
22+
import org.junit.Assert.assertTrue
2123
import org.junit.Before
2224
import org.junit.Test
2325
import org.junit.runner.RunWith
@@ -517,36 +519,110 @@ class ObservableEventsTest : BaseMapTest() {
517519
}
518520
}
519521

522+
@OptIn(MapboxExperimental::class)
520523
@Test
521524
@UiThread
522525
fun subscribeCameraChangedEvent() {
523-
val latch = CountDownLatch(1)
526+
var latch = CountDownLatch(2)
527+
val targetCameraOptions2 = cameraOptions {
528+
center(Point.fromLngLat(1.0, 2.0))
529+
zoom(16.0)
530+
bearing(18.0)
531+
pitch(85.0)
532+
anchor(ScreenCoordinate(1.0, 2.0))
533+
padding(EdgeInsets(1.0, 2.0, 3.0, 4.0))
534+
}
524535

536+
val cameraChangedResults = mutableListOf<CameraChanged>()
525537
val listener = CameraChangedCallback {
526-
val actualCameraOption = it.cameraState.toCameraOptions()
527-
assertNotNull(it.timestamp.time)
528-
assertEquals(targetCameraOptions.center!!.longitude(), actualCameraOption.center!!.longitude(), EPS)
529-
assertEquals(targetCameraOptions.center!!.latitude(), actualCameraOption.center!!.latitude(), EPS)
530-
assertEquals(targetCameraOptions.zoom, actualCameraOption.zoom)
538+
cameraChangedResults.add(it)
531539
latch.countDown()
532540
}
533541

534-
rule.scenario.onActivity { activity ->
535-
activity.runOnUiThread {
536-
mapboxMap.apply {
537-
cancelable = subscribeCameraChanged(listener)
538-
setCamera(targetCameraOptions)
542+
val coalescedLatch = CountDownLatch(1)
543+
val cameraChangedCoalescedResults = mutableListOf<CameraChangedCoalesced>()
544+
val listenerCoalesced = CameraChangedCoalescedCallback {
545+
cameraChangedCoalescedResults.add(it)
546+
coalescedLatch.countDown()
547+
}
548+
549+
var cancelableCoalesced: Cancelable? = null
550+
551+
try {
552+
rule.scenario.onActivity { activity ->
553+
activity.runOnUiThread {
554+
mapboxMap.apply {
555+
cancelable = subscribeCameraChanged(listener)
556+
cancelableCoalesced = subscribeCameraChangedCoalesced(listenerCoalesced)
557+
setCamera(targetCameraOptions)
558+
setCamera(targetCameraOptions2)
559+
}
539560
}
540561
}
541-
}
542-
loadTestStyle()
543-
if (!latch.await(20000, TimeUnit.MILLISECONDS)) {
544-
throw TimeoutException()
545-
}
562+
loadTestStyle()
563+
564+
// For normal camera changed events we should get 2 events (same as `setCamera` calls)
565+
assertTrue(latch.await(20_000, TimeUnit.MILLISECONDS))
566+
assertEquals(2, cameraChangedResults.size)
567+
assertNotNull(cameraChangedResults[0].timestamp.time)
568+
with(cameraChangedResults[0].cameraState) {
569+
assertEquals(targetCameraOptions.center!!.longitude(), center.longitude(), EPS)
570+
assertEquals(targetCameraOptions.center!!.latitude(), center.latitude(), EPS)
571+
assertEquals(targetCameraOptions.zoom, zoom)
572+
}
573+
with(cameraChangedResults[1].cameraState) {
574+
assertEquals(targetCameraOptions2.center!!.longitude(), center.longitude(), EPS)
575+
assertEquals(targetCameraOptions2.center!!.latitude(), center.latitude(), EPS)
576+
assertEquals(targetCameraOptions2.zoom, zoom)
577+
assertEquals(targetCameraOptions2.bearing, bearing)
578+
assertEquals(targetCameraOptions2.pitch, pitch)
579+
assertEquals(targetCameraOptions2.anchor, ScreenCoordinate(1.0, 2.0))
580+
assertEquals(targetCameraOptions2.padding, EdgeInsets(1.0, 2.0, 3.0, 4.0))
581+
}
546582

547-
rule.scenario.onActivity { activity ->
548-
activity.runOnUiThread {
549-
cancelable.cancel()
583+
// We should only get one coalesced event instead of 2 like the normal camera changed event
584+
assertEquals(1, cameraChangedCoalescedResults.size)
585+
assertNotNull(cameraChangedCoalescedResults[0].timestamp.time)
586+
with(cameraChangedCoalescedResults[0].cameraState) {
587+
assertEquals(targetCameraOptions2.center!!.longitude(), center.longitude(), EPS)
588+
assertEquals(targetCameraOptions2.center!!.latitude(), center.latitude(), EPS)
589+
assertEquals(targetCameraOptions2.zoom, zoom)
590+
assertEquals(targetCameraOptions2.bearing, bearing)
591+
assertEquals(targetCameraOptions2.pitch, pitch)
592+
assertEquals(targetCameraOptions2.anchor, ScreenCoordinate(1.0, 2.0))
593+
assertEquals(targetCameraOptions2.padding, EdgeInsets(1.0, 2.0, 3.0, 4.0))
594+
}
595+
596+
latch = CountDownLatch(1)
597+
rule.scenario.onActivity { activity ->
598+
activity.runOnUiThread {
599+
mapboxMap.apply {
600+
setCamera(targetCameraOptions2)
601+
}
602+
}
603+
}
604+
assertTrue(latch.await(20_000, TimeUnit.MILLISECONDS))
605+
assertEquals(3, cameraChangedResults.size)
606+
assertNotNull(cameraChangedResults[2].timestamp.time)
607+
with(cameraChangedResults[2].cameraState) {
608+
assertEquals(targetCameraOptions2.center!!.longitude(), center.longitude(), EPS)
609+
assertEquals(targetCameraOptions2.center!!.latitude(), center.latitude(), EPS)
610+
assertEquals(targetCameraOptions2.zoom, zoom)
611+
assertEquals(targetCameraOptions2.bearing, bearing)
612+
assertEquals(targetCameraOptions2.pitch, pitch)
613+
assertEquals(targetCameraOptions2.anchor, ScreenCoordinate(1.0, 2.0))
614+
assertEquals(targetCameraOptions2.padding, EdgeInsets(1.0, 2.0, 3.0, 4.0))
615+
}
616+
617+
// The camera changed coalesced event should not be called again because we pushed the same
618+
// camera than last time.
619+
assertEquals(1, cameraChangedCoalescedResults.size)
620+
} finally {
621+
rule.scenario.onActivity { activity ->
622+
activity.runOnUiThread {
623+
cancelable.cancel()
624+
cancelableCoalesced?.cancel()
625+
}
550626
}
551627
}
552628
}

app/src/main/java/com/mapbox/maps/testapp/examples/DebugModeActivity.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ class DebugModeActivity : AppCompatActivity() {
148148
}
149149
}
150150

151+
@OptIn(com.mapbox.annotation.MapboxExperimental::class)
151152
private fun registerListeners(mapboxMap: MapboxMap) {
152153
mapboxMap.subscribeStyleLoaded {
153154
logI(TAG, "StyleLoadedCallback: $it")
@@ -173,6 +174,9 @@ class DebugModeActivity : AppCompatActivity() {
173174
mapboxMap.subscribeCameraChanged {
174175
logI(TAG, "CameraChangedCallback: $it")
175176
}
177+
mapboxMap.subscribeCameraChangedCoalesced {
178+
logI(TAG, "CameraChangedCoalescedCallback: $it")
179+
}
176180
mapboxMap.subscribeRenderFrameStarted {
177181
logI(TAG, "RenderFrameStartedCallback: $it")
178182
}

app/src/main/java/com/mapbox/maps/testapp/examples/InsetMapActivity.kt

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@ package com.mapbox.maps.testapp.examples
33
import android.graphics.Color
44
import android.os.Bundle
55
import androidx.appcompat.app.AppCompatActivity
6+
import androidx.lifecycle.Lifecycle
7+
import androidx.lifecycle.lifecycleScope
8+
import androidx.lifecycle.repeatOnLifecycle
69
import com.mapbox.geojson.Feature
710
import com.mapbox.geojson.LineString
811
import com.mapbox.geojson.Point
912
import com.mapbox.maps.*
13+
import com.mapbox.maps.coroutine.cameraChangedEvents
1014
import com.mapbox.maps.dsl.cameraOptions
1115
import com.mapbox.maps.extension.style.layers.addLayer
1216
import com.mapbox.maps.extension.style.layers.generated.lineLayer
@@ -26,12 +30,13 @@ import com.mapbox.maps.plugin.scalebar.scalebar
2630
import com.mapbox.maps.testapp.R
2731
import com.mapbox.maps.testapp.databinding.ActivityInsetMapBinding
2832
import com.mapbox.maps.testapp.examples.fragment.MapFragment
33+
import kotlinx.coroutines.launch
2934

3035
/**
3136
* Example demonstrating displaying two maps: main map and small map with lower zoom
3237
* in bottom-right corner with optional bounds showing what area is covered by main map.
3338
*/
34-
class InsetMapActivity : AppCompatActivity(), CameraChangedCallback {
39+
class InsetMapActivity : AppCompatActivity() {
3540

3641
private lateinit var mainMapboxMap: MapboxMap
3742
private var insetMapboxMap: MapboxMap? = null
@@ -44,7 +49,17 @@ class InsetMapActivity : AppCompatActivity(), CameraChangedCallback {
4449
mainMapboxMap.setCamera(MAIN_MAP_CAMERA_POSITION)
4550
mainMapboxMap.loadStyle(
4651
style = STYLE_URL
47-
) { mainMapboxMap.subscribeCameraChanged(this@InsetMapActivity) }
52+
)
53+
54+
lifecycleScope.launch {
55+
// repeatOnLifecycle launches the block in a new coroutine every time the
56+
// lifecycle is in the STARTED state (or above) and cancels it when it's STOPPED.
57+
repeatOnLifecycle(Lifecycle.State.STARTED) {
58+
mainMapboxMap.cameraChangedEvents.collect {
59+
updateInsetMapCamera(it.cameraState)
60+
}
61+
}
62+
}
4863

4964
var insetMapFragment: MapFragment? =
5065
supportFragmentManager.findFragmentByTag(INSET_FRAGMENT_TAG) as? MapFragment
@@ -76,10 +91,7 @@ class InsetMapActivity : AppCompatActivity(), CameraChangedCallback {
7691
loadStyle(
7792
style = STYLE_URL
7893
) { style ->
79-
val source = geoJsonSource(BOUNDS_LINE_LAYER_SOURCE_ID) {
80-
feature(Feature.fromGeometry(LineString.fromLngLats(getRectanglePoints())))
81-
}
82-
style.addSource(source)
94+
style.addSource(geoJsonSource(BOUNDS_LINE_LAYER_SOURCE_ID) {})
8395
// The layer properties for our line. This is where we make the line dotted, set the color, etc.
8496
val layer = lineLayer(BOUNDS_LINE_LAYER_LAYER_ID, BOUNDS_LINE_LAYER_SOURCE_ID) {
8597
lineCap(LineCap.ROUND)
@@ -89,7 +101,7 @@ class InsetMapActivity : AppCompatActivity(), CameraChangedCallback {
89101
visibility(Visibility.VISIBLE)
90102
}
91103
style.addLayer(layer)
92-
updateInsetMapLineLayerBounds(style)
104+
updateInsetMapLineLayerBounds(style, mainMapboxMap.cameraState)
93105
}
94106
}
95107
insetMapFragment.getMapView().apply {
@@ -106,27 +118,29 @@ class InsetMapActivity : AppCompatActivity(), CameraChangedCallback {
106118
}
107119
}
108120

109-
override fun run(cameraChanged: CameraChanged) {
110-
val mainCameraPosition = mainMapboxMap.cameraState
121+
private fun updateInsetMapCamera(mainCameraPosition: CameraState) {
111122
val insetCameraPosition = CameraOptions.Builder()
112123
.zoom(mainCameraPosition.zoom.minus(ZOOM_DISTANCE_BETWEEN_MAIN_AND_INSET_MAPS))
113124
.pitch(mainCameraPosition.pitch)
114125
.bearing(mainCameraPosition.bearing)
115126
.center(mainCameraPosition.center)
116127
.build()
117128
insetMapboxMap?.setCamera(insetCameraPosition)
118-
insetMapboxMap?.getStyle { style -> updateInsetMapLineLayerBounds(style) }
129+
insetMapboxMap?.getStyle { style -> updateInsetMapLineLayerBounds(style, mainCameraPosition) }
119130
}
120131

121-
private fun updateInsetMapLineLayerBounds(fullyLoadedStyle: Style) {
132+
private fun updateInsetMapLineLayerBounds(
133+
fullyLoadedStyle: Style,
134+
mainMapCameraState: CameraState
135+
) {
122136
(fullyLoadedStyle.getSource(BOUNDS_LINE_LAYER_SOURCE_ID) as? GeoJsonSource)?.apply {
123-
feature(Feature.fromGeometry(LineString.fromLngLats(getRectanglePoints())))
137+
feature(Feature.fromGeometry(LineString.fromLngLats(getRectanglePoints(mainMapCameraState))))
124138
}
125139
}
126140

127-
private fun getRectanglePoints(): List<Point> {
141+
private fun getRectanglePoints(mainMapCameraState: CameraState): List<Point> {
128142
val bounds = mainMapboxMap.coordinateBoundsForCamera(
129-
mainMapboxMap.cameraState.toCameraOptions()
143+
mainMapCameraState.toCameraOptions()
130144
)
131145
return listOf(
132146
Point.fromLngLat(bounds.northeast.longitude(), bounds.northeast.latitude()),

app/src/main/java/com/mapbox/maps/testapp/examples/markersandcallouts/viewannotation/DynamicViewAnnotationActivity.kt

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import android.graphics.Canvas
55
import android.graphics.Color
66
import android.graphics.Paint
77
import android.graphics.Rect
8-
import android.graphics.drawable.BitmapDrawable
98
import android.graphics.drawable.Drawable
109
import android.os.Bundle
1110
import android.view.View
@@ -15,6 +14,7 @@ import androidx.annotation.UiThread
1514
import androidx.appcompat.app.AppCompatActivity
1615
import androidx.core.content.ContextCompat.getColor
1716
import androidx.core.content.ContextCompat.getDrawable
17+
import androidx.core.graphics.drawable.toDrawable
1818
import androidx.lifecycle.lifecycleScope
1919
import com.mapbox.geojson.Feature
2020
import com.mapbox.geojson.FeatureCollection
@@ -288,13 +288,13 @@ class DynamicViewAnnotationActivity : AppCompatActivity() {
288288
val paddingView = PaddingView(context)
289289
addView(paddingView)
290290
mapboxMap.subscribeCameraChanged {
291-
mapboxMap.cameraState.padding.let {
291+
it.cameraState.padding.let { padding ->
292292
paddingView.updateRect(
293293
Rect(
294-
/* left = */ it.left.toInt(),
295-
/* top = */ it.top.toInt(),
296-
/* right = */ width - it.right.toInt(),
297-
/* bottom = */ height - it.bottom.toInt()
294+
/* left = */ padding.left.toInt(),
295+
/* top = */ padding.top.toInt(),
296+
/* right = */ width - padding.right.toInt(),
297+
/* bottom = */ height - padding.bottom.toInt()
298298
)
299299
)
300300
}
@@ -497,15 +497,12 @@ class DynamicViewAnnotationActivity : AppCompatActivity() {
497497
}
498498
}
499499

500-
return BitmapDrawable(
501-
resources,
502-
BitmapUtils.drawableToBitmap(
503-
getDrawable(this, R.drawable.bg_dva_eta)!!,
504-
flipX = flipX,
505-
flipY = flipY,
506-
tint = tint,
507-
)
508-
)
500+
return BitmapUtils.drawableToBitmap(
501+
getDrawable(this, R.drawable.bg_dva_eta)!!,
502+
flipX = flipX,
503+
flipY = flipY,
504+
tint = tint,
505+
).toDrawable(resources)
509506
}
510507

511508
private companion object {

extension-compose/api/Release/metalava.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ package com.mapbox.maps.extension.compose {
4141
@androidx.compose.runtime.Stable public final class MapState {
4242
ctor public MapState();
4343
method @com.mapbox.maps.MapboxExperimental public kotlinx.coroutines.flow.Flow<com.mapbox.maps.GenericEvent> genericEvents(String eventName);
44+
method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public kotlinx.coroutines.flow.Flow<com.mapbox.maps.CameraChangedCoalesced> getCameraChangedCoalescedEvents();
4445
method public kotlinx.coroutines.flow.Flow<com.mapbox.maps.CameraChanged> getCameraChangedEvents();
4546
method @com.mapbox.maps.MapboxExperimental public suspend <FS extends com.mapbox.maps.interactions.FeatureState> Object? getFeatureState(com.mapbox.maps.interactions.FeaturesetFeature<FS> featuresetFeature, kotlin.coroutines.Continuation<? super FS>);
4647
method public com.mapbox.maps.plugin.gestures.generated.GesturesSettings getGesturesSettings();
@@ -67,6 +68,7 @@ package com.mapbox.maps.extension.compose {
6768
method @com.mapbox.maps.MapboxExperimental public suspend Object? resetFeatureStates(com.mapbox.maps.interactions.TypedFeaturesetDescriptor<?,?> descriptor, kotlin.coroutines.Continuation<? super com.mapbox.bindgen.Expected<java.lang.String,com.mapbox.bindgen.None>>);
6869
method @com.mapbox.maps.MapboxExperimental public suspend <FS extends com.mapbox.maps.interactions.FeatureState> Object? setFeatureState(com.mapbox.maps.interactions.FeaturesetFeature<FS> featuresetFeature, FS state, kotlin.coroutines.Continuation<? super com.mapbox.bindgen.Expected<java.lang.String,com.mapbox.bindgen.None>>);
6970
method public void setGesturesSettings(com.mapbox.maps.plugin.gestures.generated.GesturesSettings);
71+
property @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final kotlinx.coroutines.flow.Flow<com.mapbox.maps.CameraChangedCoalesced> cameraChangedCoalescedEvents;
7072
property public final kotlinx.coroutines.flow.Flow<com.mapbox.maps.CameraChanged> cameraChangedEvents;
7173
property public final com.mapbox.maps.plugin.gestures.generated.GesturesSettings gesturesSettings;
7274
property public final kotlinx.coroutines.flow.Flow<com.mapbox.maps.MapIdle> mapIdleEvents;

extension-compose/api/extension-compose.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public final class com/mapbox/maps/extension/compose/MapState {
6363
public static final field Companion Lcom/mapbox/maps/extension/compose/MapState$Companion;
6464
public fun <init> ()V
6565
public final fun genericEvents (Ljava/lang/String;)Lkotlinx/coroutines/flow/Flow;
66+
public final fun getCameraChangedCoalescedEvents ()Lkotlinx/coroutines/flow/Flow;
6667
public final fun getCameraChangedEvents ()Lkotlinx/coroutines/flow/Flow;
6768
public final fun getFeatureState (Lcom/mapbox/maps/interactions/FeaturesetFeature;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
6869
public final fun getGesturesSettings ()Lcom/mapbox/maps/plugin/gestures/generated/GesturesSettings;

0 commit comments

Comments
 (0)