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

MapboxSDKCommon.getContext crash

Open pikzelz opened this issue 1 year ago • 13 comments

Environment

  • Android OS version: 14
  • Devices affected: Mainly Samsung devices
  • Maps SDK Version: 10.5.0

Observed behavior and steps to reproduce

In crashlytics I receive a bunch of crashes from a small amount of users using my app where the app randomly crashes with the following stacktrace:

Exception java.lang.ExceptionInInitializerError:
    at com.mapbox.common.location.LocationUpdatesReceiver$service$2.invoke (LocationUpdatesReceiver.kt:10)
    at com.mapbox.common.location.LocationUpdatesReceiver$service$2.invoke (LocationUpdatesReceiver.kt:9)
    at kotlin.SynchronizedLazyImpl.getValue (LazyJVM.kt)
    at com.mapbox.common.location.LocationUpdatesReceiver.getService (LocationUpdatesReceiver.kt)
    at com.mapbox.common.location.LocationUpdatesReceiver.onReceive (LocationUpdatesReceiver.kt)
    at android.app.ActivityThread.handleReceiver (ActivityThread.java:4719)
    at android.app.ActivityThread.-$$Nest$mhandleReceiver
    at android.app.ActivityThread$H.handleMessage (ActivityThread.java:2448)
    at android.os.Handler.dispatchMessage (Handler.java:106)
    at android.os.Looper.loopOnce (Looper.java:257)
    at android.os.Looper.loop (Looper.java:368)
    at android.app.ActivityThread.main (ActivityThread.java:8839)
    at java.lang.reflect.Method.invoke
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:572)
    at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1049)
Caused by Bb.e: lateinit property appContext has not been initialized
    at com.mapbox.common.MapboxSDKCommon.getContext (MapboxSDKCommon.kt)
    at com.mapbox.common.location.LocationServiceImpl.<clinit> (LocationServiceImpl.kt)

From what I was able to gather this happens at random, sometimes when the app is backgrounded or sometimes when the app is left open. I tried many combinations to get it to happen to me but I myself am unable to reproduce it or even trigger it.

I'm not doing anything special, below is my code to initialize the map using Compose:

val mapState = rememberMapState {
    gesturesSettings = GesturesSettings.Builder()
        .setPinchScrollEnabled(false)
        .build()
}

MapboxMap(
    modifier = Modifier.fillMaxSize(),
    composeMapInitOptions = ComposeMapInitOptions(
        mapOptions = MapOptions.Builder()
            .contextMode(ContextMode.UNIQUE)
            .viewportMode(ViewportMode.DEFAULT)
            .build()
    ),
    mapState = mapState,
    scaleBar = { /* No scalebar */ },
    logo = { Logo(contentPadding = PaddingValues(20.dp)) },
    attribution = {
        Attribution(
            iconColor = theme.colorScheme.secondary,
            contentPadding = PaddingValues(
                start = 110.dp,
                top = 0.dp,
                end = 0.dp,
                bottom = 20.dp
            )
        )
    }
) {
    // Load the style on the fly.
    MapEffect(styleUrl) {
        it.mapboxMap.loadStyle(styleUrl)
    }
}

I receive the styleURL from a viewModel.

Then there's also a button added that toggles the user location. That's being done this way:

MapEffect(isShowingUsersLocation) {
    it.location.updateSettings {
        enabled = isShowingUsersLocation
    }
}

Expected behavior

A crash not related to the location update service.

Notes / preliminary analysis

I run the app through R8, but can't tell if that's the reason of the crashes as I'm unable to reproduce it in either an R8 built or a non R8 built.

pikzelz avatar Aug 13 '24 15:08 pikzelz

@pikzelz do you remove the Mapbox initializers from your manifest?

jush avatar Aug 14 '24 14:08 jush

@jush yes I do, but also manually initialize them the same way the docs explain.

If I leave the initializer in the manifest i receive a lot more crashes related to the shared library where initializing fails, and since the app doesn't always require mapbox, but only in certain states I figured I go with manually initializing

Are both related to that specific initializer? Could I be missing an initializer I should call manually?

Thank you

pikzelz avatar Aug 14 '24 16:08 pikzelz

@pikzelz could you please share:

  1. The code that calls the Mapbox initializers
  2. Where do you call that code

I am afraid that you initialize them too late and the location receiver runs before your initializer code.

jush avatar Aug 15 '24 06:08 jush

@jush I use this in my manifest:

<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">
    <meta-data
        android:name="com.mapbox.maps.loader.MapboxMapsInitializer"
        android:value="androidx.startup"
        tools:node="remove" />
    <meta-data
        android:name="com.mapbox.common.MapboxSDKCommonInitializer"
        android:value="androidx.startup"
        tools:node="remove" />
</provider>

And this in the composable to initialize:

LaunchedEffect(Unit) {
    try {
        AppInitializer.getInstance(context)
            .initializeComponent(MapboxMapsInitializer::class.java)

        isInitialized = true
    } catch (e: Exception) {
        e.printStackTrace()

        isInitialized = false
    }
}

And now that I paste this code I think I see what I'm missing. I'll report back if this fixes it. Thank you for pointing it out and reminding me to review it again. To clarify the initializer is being run before the map get's initialized (hance the isInitialized flag). Depending on that value the rest of the code is being run.

pikzelz avatar Aug 15 '24 10:08 pikzelz

@jush follow-up, after adding:

AppInitializer.getInstance(context)
    .initializeComponent(MapboxSDKCommonInitializer::class.java)

The crash of the app is unfortunately still there, I still see reports coming in through crashlytics.

pikzelz avatar Aug 15 '24 14:08 pikzelz

@pikzelz firstly I assume you're using 11.5.0 and not 10.5.0 as you mentioned in the issue description (taking into account you use Mapbox Compose extension). Secondly - may I ask you why do you remove the initializers in AndroidManifest.xml and initialize them manually in LaunchedEffect? This is violating our startup logic and indeed could be error-prone.

I am almost sure that if you will remove all your workarounds with removing initializers from manifest and running them manually via AppInitializer.getInstance(context).initializeComponent - everything will work fine.

kiryldz avatar Aug 16 '24 07:08 kiryldz

@kiryldz you’re correct in the version number, small typo.

When I left all the initializers in I got even more crashes, again on Samsung devices mostly, about not being able to get the shared libmapbox.so file (from the top of my mind).

I don’t mind trying it again, to see if that’s no longer happening. Just looking for ways to get this solved as this is unfortunately the only crash in my app.

pikzelz avatar Aug 16 '24 08:08 pikzelz

@pikzelz I am sorry to hear it's the only crash in your app. Those crashes on startup are pretty nasty and, sadly, it is out of out control why some devices are removing on startup the native libraries we rely on. The big summary of this is here (in case you did not read it).

Based on the info you provided I assume that you have some call to Mapbox APIs - and then that could be the reason why you see native library not found error when you revert your workaround of explicit removing initializers from the manifest. However if that's the case - I wonder how does your approach work of loading the initializers manually in LaunchedEffect(Unit)...

If you have ability to perform some A/B testing - I would still give it a shot try releasing your app without any workarounds related to Mapbox initializers and see the results. If the crash will be there, please add the stacktrace in this ticket.

kiryldz avatar Aug 16 '24 09:08 kiryldz

@kiryldz I just submitted a build to google where I removed all my manual initializing and just use the default way for Mapbox. Will report back with anything I find. Hopefully I have nothing to report back except it works :)

pikzelz avatar Aug 16 '24 15:08 pikzelz

@kiryldz after reverting back to the original initializer like you suggested the crash has disappeared. I missed the part where you guys rewrote the initialization. I also haven't seen the libmapbox.so crash and the app has been heavily used this past weekend.

pikzelz avatar Aug 19 '24 09:08 pikzelz

We've had this crash reported via Firebase Crashlytics that we didn't see in internal testing.

Mapbox 11.5.0 Samsung S23 Android 14.

Like OP here, we disable the androidx.startup component and initialise the SDK ourselves, we do this because the app does not display a map by default and is a feature a user can turn on. For users not needing the map the reduced overhead helps the devices and saves calls to the SDK on start-up where the SDK is not actually used.

I would guess that unlucky timing leads to the user enabling the map and a location update coming in as the AppInitializer.getInstance(context).initializeComponent(MapboxMapsInitializer::class.java) call is still working

I think what we'll try is to also nullify this from the manifest and manually register this broadcast after the initialise call, then the service can't try starting up and crash?

        <receiver
            android:name="com.mapbox.common.location.LocationUpdatesReceiver"
            android:enabled="true"
            android:exported="false" >
            <intent-filter>
                <action android:name="com.mapbox.common.location.LocationUpdatesReceiver.ACTION_PROCESS_LOCATION_UPDATES" />
            </intent-filter>
        </receiver>

Interestingly if this is done

        <receiver
            android:name="com.mapbox.common.location.LocationUpdatesReceiver"
            tools:node="remove"
            android:exported="false">
        </receiver>

the broadcast receiver still shows up as being used in logcat, so does MapboxCommon register that receiver in code on init too?

ac87 avatar Sep 13 '24 07:09 ac87

We also would like to init MapBox sdk as late as possible but it is not clear Who and Why does trigger LocationUpdatesReceiver. Could you guide us how can we stop LocationUpdatesReceiver working until SDK is initialized.

Ideally, library should not do anything until SDK fully initialized. Also removing the LocationUpdatesReceiver from manifest has not impact as it is not mandatory to register in manifest.

kozaxinan avatar Sep 30 '24 12:09 kozaxinan

@ac87 can you please provide the full stacktrace from Crashlytics?

@ac87 @kozaxinan assuming you do have

        <receiver
            android:name="com.mapbox.common.location.LocationUpdatesReceiver"
            tools:node="remove"
            android:exported="false">
        </receiver>

in your AndroidManifest.xml - how do you verify that LocationUpdatesReceiver is still called before you initialize Mapbox? Can you provide logcat confirming this?

kiryldz avatar Oct 02 '24 07:10 kiryldz