android-maps-compose icon indicating copy to clipboard operation
android-maps-compose copied to clipboard

google map crashed with instrumentation test

Open rakshitsoni02 opened this issue 3 years ago • 27 comments

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@[email protected] (150400-0):0)
    at com.google.maps.api.android.lib6.common.r.a(:com.google.android.gms.dynamite_mapsdynamite@[email protected] (150400-0):2)
    at com.google.maps.api.android.lib6.impl.bj.p(:com.google.android.gms.dynamite_mapsdynamite@[email protected] (150400-0):1)
    at com.google.android.gms.maps.internal.i.ba(:com.google.android.gms.dynamite_mapsdynamite@[email protected] (150400-0):186)
    at en.onTransact(:com.google.android.gms.dynamite_mapsdynamite@[email protected] (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.

rakshitsoni02 avatar Sep 30 '22 16:09 rakshitsoni02

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

polivmi1 avatar Oct 05 '22 06:10 polivmi1

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

minaxiDH avatar Nov 15 '22 14:11 minaxiDH

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

rakshitsoni02 avatar Nov 16 '22 08:11 rakshitsoni02

@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.

DSteve595 avatar Dec 12 '22 21:12 DSteve595

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

wangela avatar Feb 15 '23 18:02 wangela

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?

MehriAbdukodirova avatar Feb 21 '23 05:02 MehriAbdukodirova

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.

  • TestWithIssue throws the error. It uses createAndroidComposeRule<ComponentActivity>() then navigates to MainActivity where GoogleMap composable is created
  • TestWithoutIssue doesn't throw the error. It uses createAndroidComposeRule<Main>() to launch MainActivity where GoogleMap composable is created.

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 avatar Mar 05 '23 13:03 amrfarid140

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.

amrfarid140 avatar Mar 12 '23 10:03 amrfarid140

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

joetsaitw avatar May 11 '23 07:05 joetsaitw

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.

amrfarid140 avatar May 11 '23 13:05 amrfarid140

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.

Ayskin avatar May 12 '23 07:05 Ayskin

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

joetsaitw avatar May 24 '23 08:05 joetsaitw

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

minaxiDH avatar Jul 07 '23 12:07 minaxiDH

@minaxiDH Any workaround for this issue?

shriharsha-bhagwat avatar Jul 26 '23 09:07 shriharsha-bhagwat

@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()

minaxiDH avatar Jul 26 '23 10:07 minaxiDH

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

wangela avatar Oct 07 '23 01:10 wangela

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?

tolulonge avatar Nov 03 '23 12:11 tolulonge

Facing the same issue. Using last version.

sergionettl avatar Mar 18 '24 11:03 sergionettl

Fix: Injecting the main dispatcher fixes the crash.

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

(EmptyCoroutineContext is not necessary)

sergionettl avatar Mar 19 '24 14:03 sergionettl

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.

oheyadam avatar Apr 19 '24 11:04 oheyadam