Summary
Production crash reported by Crashlytics:
Fatal Exception: java.lang.IllegalStateException: Can't take a snapshot while executing in the background.
at com.google.maps.api.android.lib6.impl.a.a(...)
at com.google.maps.api.android.lib6.phoenix.eg.run(...)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7680)
This is the same class of bug as #74, but on a different (previously unguarded) code path.
Root cause
GoogleMap.snapshot() is only called from Context.makeSnapshot() (GoogleMapsExt.kt). It is invoked from two places in QuakeDetailsActivity.onMapReady:
fabShare setOnClickListener — user-triggered, always in foreground (safe).
- The deferred snapshot path used when sharing from the bottom sheet:
if (isSnapshotRequest == true) {
lifecycleScope.launch {
delay(1000)
this@QuakeDetailsActivity.makeSnapshot(p0, it) // runs even if app went to background
}
}
When the user taps "Share" in the bottom sheet, QuakeDetailsActivity opens with IS_SNAPSHOT_REQUEST_FROM_BOTTOM_SHEET = true and schedules a snapshot 1s later. lifecycleScope.launch is not paused at STARTED (it keeps running down to CREATED), so if the user backgrounds the app during that second (Home, screen lock, the share chooser / another activity coming to the front), the delay(1000) still completes and snapshot() runs while the map's GL surface is paused -> crash.
The fix for #74 (9cf6a54) only guarded the MapsFragment map; this QuakeDetailsActivity share-snapshot path remained unguarded.
Why a try/catch around snapshot() does not help
The IllegalStateException is thrown asynchronously on the Maps SDK internal handler (eg.run -> Handler.handleCallback in the stack trace), after snapshot() returns. A try/catch around the snapshot() call cannot catch it. The only reliable defense is to not request the snapshot while the host is in the background.
Fix
Layered lifecycle guards:
- Central guard in
makeSnapshot (GoogleMapsExt.kt): when the Context is a LifecycleOwner, return early unless the lifecycle is at least STARTED. This protects every call site in one place. A defensive try/catch (IllegalStateException) is also added for any synchronous throw.
- Guard in the deferred path (
QuakeDetailsActivity.kt): re-check lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED) after the delay(1000), so the snapshot is skipped if the app was backgrounded during that window.
Known limitation
A tiny, unavoidable race remains if the app moves to the background in the milliseconds between the STARTED check and the SDK's internal snapshot runnable executing. The Maps SDK exposes no API to query whether the GL surface is active, so it cannot be fully eliminated client-side. The guards remove the real, frequent case (the 1s deferred-snapshot window) causing the production crash.
Affected files
app/src/main/java/cl/figonzal/lastquakechile/core/utils/GoogleMapsExt.kt
app/src/main/java/cl/figonzal/lastquakechile/quake_feature/ui/QuakeDetailsActivity.kt
Related: #74
Summary
Production crash reported by Crashlytics:
This is the same class of bug as #74, but on a different (previously unguarded) code path.
Root cause
GoogleMap.snapshot()is only called fromContext.makeSnapshot()(GoogleMapsExt.kt). It is invoked from two places inQuakeDetailsActivity.onMapReady:fabSharesetOnClickListener— user-triggered, always in foreground (safe).When the user taps "Share" in the bottom sheet,
QuakeDetailsActivityopens withIS_SNAPSHOT_REQUEST_FROM_BOTTOM_SHEET = trueand schedules a snapshot 1s later.lifecycleScope.launchis not paused atSTARTED(it keeps running down toCREATED), so if the user backgrounds the app during that second (Home, screen lock, the share chooser / another activity coming to the front), thedelay(1000)still completes andsnapshot()runs while the map's GL surface is paused -> crash.The fix for #74 (
9cf6a54) only guarded theMapsFragmentmap; thisQuakeDetailsActivityshare-snapshot path remained unguarded.Why a try/catch around snapshot() does not help
The
IllegalStateExceptionis thrown asynchronously on the Maps SDK internal handler (eg.run->Handler.handleCallbackin the stack trace), aftersnapshot()returns. A try/catch around thesnapshot()call cannot catch it. The only reliable defense is to not request the snapshot while the host is in the background.Fix
Layered lifecycle guards:
makeSnapshot(GoogleMapsExt.kt): when theContextis aLifecycleOwner, return early unless the lifecycle is at leastSTARTED. This protects every call site in one place. A defensivetry/catch (IllegalStateException)is also added for any synchronous throw.QuakeDetailsActivity.kt): re-checklifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)after thedelay(1000), so the snapshot is skipped if the app was backgrounded during that window.Known limitation
A tiny, unavoidable race remains if the app moves to the background in the milliseconds between the
STARTEDcheck and the SDK's internal snapshot runnable executing. The Maps SDK exposes no API to query whether the GL surface is active, so it cannot be fully eliminated client-side. The guards remove the real, frequent case (the 1s deferred-snapshot window) causing the production crash.Affected files
app/src/main/java/cl/figonzal/lastquakechile/core/utils/GoogleMapsExt.ktapp/src/main/java/cl/figonzal/lastquakechile/quake_feature/ui/QuakeDetailsActivity.ktRelated: #74