Skip to content

Reports in Crashlytics after upgrading to 6.12.0: Method addObserver must be called on the main thread #789

@sanskar10100

Description

@sanskar10100

We have started noticing an increasing number of Crash reports on Firebase Crashlytics since we bumped our com.google.maps.android:maps-compose version from 6.10.0 to 6.12.0 and 6.12.1

They're only happening on certain devices. The stacktrace is obfuscated, so there isn't a lot of data here, but generally it goes like this:

          Fatal Exception: java.lang.IllegalStateException: Method addObserver must be called on the main thread
       at m.abg.i(:com.google.android.gms.policy_maps_core_dynamite@233610105@233610102065.573949206.573949206:21)
       at m.abg.b(:com.google.android.gms.policy_maps_core_dynamite@233610105@233610102065.573949206.573949206:3)
       at m.dba.e(:com.google.android.gms.policy_maps_core_dynamite@233610105@233610102065.573949206.573949206:96)
       at com.google.android.gms.maps.internal.CreatorImpl.c(:com.google.android.gms.policy_maps_core_dynamite@233610105@233610102065.573949206.573949206:908)
       at com.google.android.gms.maps.internal.CreatorImpl.logInitialization(:com.google.android.gms.policy_maps_core_dynamite@233610105@233610102065.573949206.573949206:16)
       at com.google.android.gms.maps.internal.i.bp(:com.google.android.gms.policy_maps_core_dynamite@233610105@233610102065.573949206.573949206:75)
       at m.bbf.onTransact(:com.google.android.gms.policy_maps_core_dynamite@233610105@233610102065.573949206.573949206:21)
       at android.os.Binder.transact(Binder.java:1219)
       at com.google.android.gms.internal.maps.zza.zzc(com.google.android.gms:play-services-maps@@19.2.0:2)
       at com.google.android.gms.maps.internal.zze.zzm(com.google.android.gms:play-services-maps@@19.2.0:4)
       at com.google.android.gms.maps.MapsInitializer.initialize(com.google.android.gms:play-services-maps@@19.2.0:12)
       at com.google.android.gms.maps.MapsInitializer.initialize(com.google.android.gms:play-services-maps@@19.2.0:1)
       at com.google.maps.android.compose.internal.DefaultGoogleMapsInitializer$initialize$3.invokeSuspend(DefaultGoogleMapsInitializer.java:146)
       at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
       at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
       at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.java:124)
       at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
       at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.java:586)
       at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:820)
       at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
       at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)

Device here was Samsung Galaxy M35 5G running Android 14.

Another from Samsung Galaxy A16 running Android 14:

          Fatal Exception: java.lang.IllegalStateException: Method addObserver must be called on the main thread
       at m.acj.k(:com.google.android.gms.policy_maps_core_dynamite@241110203@241110201042.615149234.615149234:21)
       at m.acj.b(:com.google.android.gms.policy_maps_core_dynamite@241110203@241110201042.615149234.615149234:3)
       at m.dec.e(:com.google.android.gms.policy_maps_core_dynamite@241110203@241110201042.615149234.615149234:113)
       at com.google.android.gms.maps.internal.CreatorImpl.d(:com.google.android.gms.policy_maps_core_dynamite@241110203@241110201042.615149234.615149234:920)
       at com.google.android.gms.maps.internal.CreatorImpl.logInitialization(:com.google.android.gms.policy_maps_core_dynamite@241110203@241110201042.615149234.615149234:60)
       at com.google.android.gms.maps.internal.i.bo(:com.google.android.gms.policy_maps_core_dynamite@241110203@241110201042.615149234.615149234:75)
       at m.bcw.onTransact(:com.google.android.gms.policy_maps_core_dynamite@241110203@241110201042.615149234.615149234:21)
       at android.os.Binder.transact(Binder.java:1219)
       at com.google.android.gms.internal.maps.zza.zzc(com.google.android.gms:play-services-maps@@19.2.0:2)
       at com.google.android.gms.maps.internal.zze.zzm(com.google.android.gms:play-services-maps@@19.2.0:4)
       at com.google.android.gms.maps.MapsInitializer.initialize(com.google.android.gms:play-services-maps@@19.2.0:12)
       at com.google.android.gms.maps.MapsInitializer.initialize(com.google.android.gms:play-services-maps@@19.2.0:1)
       at com.google.maps.android.compose.internal.DefaultGoogleMapsInitializer$initialize$3.invokeSuspend(DefaultGoogleMapsInitializer.java:146)
       at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
       at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
       at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.java:124)
       at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:89)
       at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.java:586)
       at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:820)
       at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:717)
       at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:704)

I saw that there were updates to the initializer when bumping the version, so that might be it. We're also using com.google.maps.android:maps-compose-utils in our app.

Steps to reproduce

  1. Have a Map Screen
  2. Launch the app on certain devices

Code example

@Composable
fun NearbyDiscoveryFeature() {
    val context = LocalContext.current
    var location by remember { mutableStateOf<Location?>(null) }
    var isLoading by remember { mutableStateOf(true) }
    var hasLocationPermission by remember {
        mutableStateOf(
            context.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
        )
    }

    val locationPermissionLauncher = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.RequestPermission(),
        onResult = { isGranted ->
            hasLocationPermission = isGranted
            if (!isGranted) {
                isLoading = false
            }
        }
    )

    LaunchedEffect(hasLocationPermission) {
        if (hasLocationPermission) {
            // Simulate a network call to fetch location
            delay(1500)
            location = createFakeLocation()
            isLoading = false
        } else {
            locationPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION)
        }
    }

    Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
        when {
            isLoading -> {
                CircularProgressIndicator()
            }
            hasLocationPermission -> {
                LocationMapView(
                    location = location,
                    onMapClicked = { /* Handle map click */ }
                )
            }
            else -> {
                Column(horizontalAlignment = Alignment.CenterHorizontally) {
                    Text(
                        text = "Location permission is required to use this feature.",
                        textAlign = TextAlign.Center,
                        modifier = Modifier.padding(16.dp)
                    )
                    Button(onClick = { locationPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION) }) {
                        Text("Grant Permission")
                    }
                }
            }
        }
    }
}

@Composable
fun LocationMapView(
    location: Location?,
    modifier: Modifier = Modifier,
    onMapClicked: () -> Unit,
) {
    val cameraPositionState = rememberCameraPositionState {
        val targetLocation = location?.let { LatLng(it.latitude, it.longitude) } ?: LatLng(0.0, 0.0)
        position = CameraPosition.fromLatLngZoom(
            targetLocation,
            if (location == null) 1f else 15f
        )
    }

    Box(
        modifier = modifier
            .clip(RoundedCornerShape(8.dp))
            .clickable { onMapClicked() }
    ) {
        GoogleMap(
            modifier = Modifier.fillMaxSize(),
            cameraPositionState = cameraPositionState,
            properties = MapProperties(
                isMyLocationEnabled = location != null
            ),
            uiSettings = MapUiSettings(
                myLocationButtonEnabled = false,
                zoomControlsEnabled = false,
                mapToolbarEnabled = false,
                scrollGesturesEnabled = false,
                compassEnabled = false,
                indoorLevelPickerEnabled = false,
                rotationGesturesEnabled = false,
                scrollGesturesEnabledDuringRotateOrZoom = false,
                tiltGesturesEnabled = false,
                zoomGesturesEnabled = false,
            ),
            onMapClick = { onMapClicked() },
        )

        Box(
            modifier = Modifier
                .fillMaxSize()
                .background(
                    brush = Brush.verticalGradient(
                        colors = listOf(Color.Transparent, Color.Black),
                    ),
                    alpha = 0.5f
                )
        )

        Button(
            modifier = Modifier
                .align(Alignment.BottomStart)
                .padding(16.dp),
            onClick = { onMapClicked() }
        ) {
            Text("Open Map")
        }
    }
}

private fun createFakeLocation(): Location {
    return Location("fake_provider").apply {
        latitude = 34.0522
        longitude = -118.2437
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    triage meI really want to be triaged.type: bugError or flaw in code with unintended results or allowing sub-optimal usage patterns.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions