googlemaps / android-maps-compose

Jetpack Compose composables for the Maps SDK for Android
https://developers.google.com/maps/documentation/android-sdk/maps-compose
Apache License 2.0
1.16k stars 143 forks source link

google map crashed with instrumentation test #211

Open rakshitsoni02 opened 2 years ago

rakshitsoni02 commented 2 years ago

Device: Pixel 5 API 30 sdk version: 2.5.3 & 2.7.2

i've implemented GoogleMap with ComposeView, it's crashing in the instrumentation test case with the following stack trace:

 com.google.maps.api.android.lib6.common.apiexception.c: Not on the main thread
    at com.google.maps.api.android.lib6.common.m.i(:com.google.android.gms.dynamite_mapsdynamite@223017081@22.30.17 (150400-0):0)
    at com.google.maps.api.android.lib6.common.r.a(:com.google.android.gms.dynamite_mapsdynamite@223017081@22.30.17 (150400-0):2)
    at com.google.maps.api.android.lib6.impl.bj.p(:com.google.android.gms.dynamite_mapsdynamite@223017081@22.30.17 (150400-0):1)
    at com.google.android.gms.maps.internal.i.ba(:com.google.android.gms.dynamite_mapsdynamite@223017081@22.30.17 (150400-0):186)
    at en.onTransact(:com.google.android.gms.dynamite_mapsdynamite@223017081@22.30.17 (150400-0):4)
    at android.os.Binder.transact(Binder.java:1043)
    at com.google.android.gms.internal.maps.zza.zzc(com.google.android.gms:play-services-maps@@18.1.0:2)
    at com.google.android.gms.maps.internal.zzg.clear(com.google.android.gms:play-services-maps@@18.1.0:2)
    at com.google.android.gms.maps.GoogleMap.clear(com.google.android.gms:play-services-maps@@18.1.0:1)
    at com.google.maps.android.compose.MapApplier.onClear(MapApplier.kt:46)
    at androidx.compose.runtime.AbstractApplier.clear(Applier.kt:213)
    at androidx.compose.runtime.CompositionImpl.dispose(Composition.kt:601)
    at com.google.maps.android.compose.GoogleMapKt$GoogleMap$10.invokeSuspend(GoogleMap.kt:229)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at androidx.compose.ui.test.ApplyingContinuationInterceptor$SendApplyContinuation.resumeWith(ApplyingContinuationInterceptor.kt:70)
    at kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:234)
    at kotlinx.coroutines.DispatchedTaskKt.resumeUnconfined(DispatchedTask.kt:190)
    at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:161)
    at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:397)
    at kotlinx.coroutines.CancellableContinuationImpl.cancel(CancellableContinuationImpl.kt:183)
    at kotlinx.coroutines.CancellableContinuationImpl.parentCancelled$kotlinx_coroutines_core(CancellableContinuationImpl.kt:190)
    at kotlinx.coroutines.ChildContinuation.invoke(JobSupport.kt:1475)
    at kotlinx.coroutines.JobSupport.notifyCancelling(JobSupport.kt:1500)
    at kotlinx.coroutines.JobSupport.tryMakeCancelling(JobSupport.kt:795)
    at kotlinx.coroutines.JobSupport.makeCancelling(JobSupport.kt:755)
    at kotlinx.coroutines.JobSupport.cancelImpl$kotlinx_coroutines_core(JobSupport.kt:671)
    at kotlinx.coroutines.JobSupport.parentCancelled(JobSupport.kt:637)
    at kotlinx.coroutines.ChildHandleNode.invoke(JobSupport.kt:1466)
    at kotlinx.coroutines.JobSupport.notifyCancelling(JobSupport.kt:1500)
    at kotlinx.coroutines.JobSupport.tryMakeCompletingSlowPath(JobSupport.kt:900)
    at kotlinx.coroutines.JobSupport.tryMakeCompleting(JobSupport.kt:863)
    at kotlinx.coroutines.JobSupport.cancelMakeCompleting(JobSupport.kt:696)
    at kotlinx.coroutines.JobSupport.cancelImpl$kotlinx_coroutines_core(JobSupport.kt:667)
    at kotlinx.coroutines.JobSupport.cancelInternal(JobSupport.kt:632)
    at kotlinx.coroutines.JobSupport.cancel(JobSupport.kt:617)
    at kotlinx.coroutines.Job$DefaultImpls.cancel$default(Job.kt:183)
    at androidx.compose.runtime.Recomposer.cancel(Recomposer.kt:781)
    at androidx.compose.ui.test.AndroidComposeUiTestEnvironment.withWindowRecomposer(ComposeUiTest.android.kt:324)
    at androidx.compose.ui.test.AndroidComposeUiTestEnvironment.access$withWindowRecomposer(ComposeUiTest.android.kt:217)
    at androidx.compose.ui.test.AndroidComposeUiTestEnvironment$runTest$1$1$1$1.invoke(ComposeUiTest.android.kt:291)
    at androidx.compose.ui.test.AndroidComposeUiTestEnvironment.withTestCoroutines(ComposeUiTest.android.kt:334)
    at androidx.compose.ui.test.AndroidComposeUiTestEnvironment.access$withTestCoroutines(ComposeUiTest.android.kt:217)
    at androidx.compose.ui.test.AndroidComposeUiTestEnvironment$runTest$1$1$1.invoke(ComposeUiTest.android.kt:290)
    at androidx.compose.ui.test.junit4.EspressoLink.withStrategy(EspressoLink.android.kt:66)
    at androidx.compose.ui.test.AndroidComposeUiTestEnvironment$runTest$1$1.invoke(ComposeUiTest.android.kt:289)
    at androidx.compose.ui.test.junit4.IdlingResourceRegistry.withRegistry(IdlingResourceRegistry.jvm.kt:157)
    at androidx.compose.ui.test.AndroidComposeUiTestEnvironment$runTest$1.invoke(ComposeUiTest.android.kt:288)
    at androidx.compose.ui.test.junit4.ComposeRootRegistry.withRegistry(ComposeRootRegistry.android.kt:146)
    at androidx.compose.ui.test.AndroidComposeUiTestEnvironment.runTest(ComposeUiTest.android.kt:287)
    at androidx.compose.ui.test.junit4.AndroidComposeTestRule$apply$1.evaluate(AndroidComposeTestRule.android.kt:147)
    at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
    at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
    at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
    at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
    at androidx.test.ext.junit.runners.AndroidJUnit4.run(AndroidJUnit4.java:162)
    at org.junit.runners.Suite.runChild(Suite.java:128)
    at org.junit.runners.Suite.runChild(Suite.java:27)
    at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
    at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
    at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
    at androidx.test.internal.runner.TestExecutor.execute(TestExecutor.java:56)
    at androidx.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:444)
    at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2205)

ComposeView:

 <androidx.compose.ui.platform.ComposeView
                android:id="@+id/map_compose_view"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@color/black_50"
                android:paddingHorizontal="14dp"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@id/venue_section" />

component:

@Composable
fun MiniMap(
    latLng: LatLng,
    modifier: Modifier = Modifier,
    cornerRadius: Int = 10,
    isMapUIControlsEnabled: Boolean = false,
    onMapViewClicked: (() -> Unit)? = null
) {
    val radius = LocalDensity.current.run { cornerRadius.dp.toPx() }
    Box(
        modifier = modifier
            .clip(RoundedCornerShape(radius))
    ) {
        val context = LocalContext.current
        val cameraPositionState = rememberCameraPositionState {
            position = CameraPosition.fromLatLngZoom(latLng, MAP_ZOOM_LEVEL)
        }
        val uiSettings by remember {
            mutableStateOf(
                MapUiSettings(
                    compassEnabled = false,
                    zoomControlsEnabled = isMapUIControlsEnabled,
                    zoomGesturesEnabled = isMapUIControlsEnabled,
                    tiltGesturesEnabled = isMapUIControlsEnabled,
                    indoorLevelPickerEnabled = isMapUIControlsEnabled,
                    mapToolbarEnabled = isMapUIControlsEnabled,
                    myLocationButtonEnabled = isMapUIControlsEnabled,
                    scrollGesturesEnabled = isMapUIControlsEnabled,
                    scrollGesturesEnabledDuringRotateOrZoom = isMapUIControlsEnabled,
                )
            )
        }
        GoogleMap(
            modifier = Modifier.fillMaxSize(),
            uiSettings = uiSettings,
            cameraPositionState = cameraPositionState,
            onMapClick = {
                onMapViewClicked?.invoke()
            },
            properties = MapProperties(
                mapStyleOptions = MapStyleOptions.loadRawResourceStyle(context, resourcesR.raw.google_map_style)
            )
        ) {
            Marker(
                state = MarkerState(position = latLng),
                onClick = {
                    onMapViewClicked?.invoke()
                    true
                },
                icon = ContextCompat.getDrawable(
                    context,
                    resourcesR.drawable.ic_dot_filled_36
                )?.let {
                    bitMapFromVector(
                        vectorDrawable = it,
                        iconTint = ContextCompat.getColor(context, resourcesR.color.dice_yellow)
                    )
                }
            )
        }
    }
}

I've tried running the test on the UI thread but no luck it seems like an issue when disposing of the mapview. The implementation used from the activity also tried setting different disposition strategy.

polivmi1 commented 2 years ago

@rakshitsoni02 could you test if it also happens with version 2.7.0?

minaxiDH commented 2 years ago

Hi @rakshitsoni02 @polivmi1 , I am facing the same issue and it also happens with upgraded version, is there any fix/workaround for this issue?

rakshitsoni02 commented 2 years ago

hey @minaxiDH unfortunately no solid workaround I tried to dispose compose view in onstop() but still randomly failing the test suite

DSteve595 commented 1 year ago

@minaxiDH @rakshitsoni02 Would you be able to provide a sample project with a test that causes this crash? Our project has instrumentation tests that don't have this issue, so there may be something different in your setup that we can investigate.

wangela commented 1 year ago

Reopening for comments from developers reporting this issue in the Maps SDK for Android issue tracker.

MehriAbdukodirova commented 1 year ago

Experiencing the same issue when writing an automation test - crashing due to the following issue: com.google.maps.api.android.lib6.common.apiexception.c: Not on the main thread

Has anyone found any workarounds?

amrfarid140 commented 1 year ago

It doesn't throw if you change the rule from being createAndroidComposeRule<ComponentActivity>() to createAndroidComposeRule<MainActivity>().

Here's a project that reproduces the issue https://github.com/amrfarid140/googlemapscompose-issue-reporducer. It has 2 tests.

To run the sample project you will need to replace Google Maps API key in the AndroidManifest. The key in there is not valid anymore.

amrfarid140 commented 1 year ago

Not sure if anyone is looking into this yet but something interesting I found was when the test fails.

The call to disposingComposition changes threads at the point of calling composition.dispose(). See screenshots.

Phase Screenshot
calling factory() 1
calling awaitCancellation 2
Disposing composition 3

When we launch a Google Map containing activity, the cancellation signal comes from a LifecycleObserver inside WrappedComposition which runs on the main thread.

When we launch an activity then navigate to a GoogleMap containing activity , the cancellation signal comes from AndroidComposeUiTestEnvironment which runs on the test thread.

Still don't know why this is happening.

joetsaitw commented 1 year ago

I'm facing the same issue as well. Any update on this issue?

amrfarid140 commented 1 year ago

The workaround I have so far is to finish the activity/activities as part of the test instead of waiting for the test to tear down.

Ayskin commented 1 year ago

I'm using ActivityScenario to launch activities in test. I have this same issue when the Google Map containing activity is launched from another one.

Closing the activity with Espresso.pressBackUnconditionally() works for me.

joetsaitw commented 1 year ago

@amrfarid140 @Ayskin thank you so much. Finally, it works.

minaxiDH commented 1 year ago

This doesn't work if we need to assert on map screen, has anyone found any workaround on it?

shriharsha-bhagwat commented 1 year ago

@minaxiDH Any workaround for this issue?

minaxiDH commented 1 year ago

@shriharsha-bhagwat this is still an open issue, we can't assert anything on map screen with composeView as it crashes with same exception, but it can be successfully closed using Espresso.pressBackUnconditionally()

wangela commented 1 year ago

Please comment here if you're still seeing this issue with v3.1.0.

tolulonge commented 1 year ago

started experiencing this issue with the same stacktrace as above with one of the instrumentation test that launches an Activity after updating the androidx.navigation library from 2.7.4 to the latest 2.7.5, is there a known conflict between the latest version of maps-compose and androidx.navigation?

sergionettl commented 8 months ago

Facing the same issue. Using last version.

sergionettl commented 8 months ago

Fix: Injecting the main dispatcher fixes the crash.

@get:Rule val composeTestRule = createComposeRule(
    effectContext = EmptyCoroutineContext + Dispatchers.Main.immediate
  )

(EmptyCoroutineContext is not necessary)

oheyadam commented 7 months ago

We're still seeing this on version 4.3.3 of the lib. Closing the app using pressBackUnconditionally() doesn't work reliably enough, and overriding the effectContext fails for other reasons.

BogumilMecel commented 1 month ago

Issue still there on version 6.1.2 of the lib. Adding effectContext crashes with java.lang.IllegalStateException: Method removeObserver must be called on the main thread

Mary-a67 commented 1 month ago

this issue prevent to update the androidx_navigation from 2.7.4 to 2.8.1 and workarounds are not working in our case. is there any estimation when it would be fixed?

noamsalome commented 1 month ago

Issue reproduced on v6.1.2 as well. I managed to avoid it using launchActivity<StoresActivity>().use {} but it's really not ideal.

DJuanan commented 13 hours ago

Still happening, no workarounds working for us, any clue?