Skip to content

Crash: IllegalStateException "Can't take a snapshot while executing in the background" in QuakeDetailsActivity share flow #83

Description

@figonzal1

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:

  1. fabShare setOnClickListener — user-triggered, always in foreground (safe).
  2. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions