Skip to content

Unable to resume activity: java.util.ConcurrentModificationException in Remote Config #5439

Open
@FilippoVigani

Description

  • Android Studio version: Android Studio Giraffe | 2022.3.1
  • Firebase Component: Remote Config
  • Component version: 21.4.1 (BOM 32.2.2)

I'm seeing a crash from my bug tracker that appears to be coming from remote config, here's the stack trace:

java.util.ConcurrentModificationException: null
    at java.util.LinkedHashMap$LinkedHashIterator.nextNode(LinkedHashMap.java:760)
    at java.util.LinkedHashMap$LinkedKeyIterator.next(LinkedHashMap.java:782)
    at com.google.firebase.remoteconfig.internal.ConfigRealtimeHttpClient.propagateErrors(ConfigRealtimeHttpClient.java:225)
    at com.google.firebase.remoteconfig.internal.ConfigRealtimeHttpClient.makeRealtimeHttpConnection(ConfigRealtimeHttpClient.java:387)
    at com.google.firebase.remoteconfig.internal.ConfigRealtimeHttpClient.startHttpConnection(ConfigRealtimeHttpClient.java:355)
    at com.google.firebase.remoteconfig.internal.ConfigRealtimeHandler.beginRealtime(ConfigRealtimeHandler.java:81)
    at com.google.firebase.remoteconfig.internal.ConfigRealtimeHandler.setBackgroundState(ConfigRealtimeHandler.java:96)
    at com.google.firebase.remoteconfig.FirebaseRemoteConfig.setConfigUpdateBackgroundState(FirebaseRemoteConfig.java:674)
    at com.google.firebase.remoteconfig.RemoteConfigComponent.notifyRCInstances(RemoteConfigComponent.java:341)
    at com.google.firebase.remoteconfig.RemoteConfigComponent.access$100(RemoteConfigComponent.java:60)
    at com.google.firebase.remoteconfig.RemoteConfigComponent$GlobalBackgroundListener.onBackgroundStateChanged(RemoteConfigComponent.java:363)
    at com.google.android.gms.common.api.internal.BackgroundDetector.zza(com.google.android.gms:play-services-basement@@18.2.0:3)
    at com.google.android.gms.common.api.internal.BackgroundDetector.onActivityResumed(com.google.android.gms:play-services-basement@@18.2.0:3)
    at android.app.Application.dispatchActivityResumed(Application.java:450)
    at android.app.Activity.dispatchActivityResumed(Activity.java:1482)
    at android.app.Activity.onResume(Activity.java:2043)
    at com.truescreen.android.MainActivity.onResume(MainActivity.kt:280)
    at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1531)
    at android.app.Activity.performResume(Activity.java:8734)
    at android.app.ActivityThread.performResumeActivity(ActivityThread.java:5351)
    at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:5444)
    at android.app.servertransaction.ResumeActivityItem.execute(ResumeActivityItem.java:54)
    at android.app.servertransaction.ActivityTransactionItem.execute(ActivityTransactionItem.java:45)
    at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:176)
    at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2574)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at android.os.Looper.loopOnce(Looper.java:226)
    at android.os.Looper.loop(Looper.java:313)
    at android.app.ActivityThread.main(ActivityThread.java:8757)
    at java.lang.reflect.Method.invoke(Method.java)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1067)
java.lang.RuntimeException: Unable to resume activity {com.truescreen.app/com.truescreen.android.MainActivity}: java.util.ConcurrentModificationException
    at android.app.ActivityThread.performResumeActivity(ActivityThread.java:5378)
    at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:5444)
    at android.app.servertransaction.ResumeActivityItem.execute(ResumeActivityItem.java:54)
    at android.app.servertransaction.ActivityTransactionItem.execute(ActivityTransactionItem.java:45)
    at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:176)
    at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2574)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at android.os.Looper.loopOnce(Looper.java:226)
    at android.os.Looper.loop(Looper.java:313)
    at android.app.ActivityThread.main(ActivityThread.java:8757)
    at java.lang.reflect.Method.invoke(Method.java)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1067)

From the stack trace itself it's hard to tell exactly where the issue arises. Some code I use from remote config:

    private val configCoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)

    private val configStatus: MutableStateFlow<ConfigStatus> =
        MutableStateFlow(ConfigStatus.Uninitialized)

    private enum class ConfigStatus {
        Uninitialized, DefaultsReady, RemoteReady
    }

    override val appUrls = configStatus.filter {
            it == ConfigStatus.DefaultsReady || it == ConfigStatus.RemoteReady
        }.flatMapLatest {
            streamRemoteConfig(
                setOf(
                    "example", "test"
                )
            ).retryWithExponentialBackoff(onError = {
                    Timber.w(it)
                })
        }.map {
            ExampleData(
                 example = getString("example"),
                 test = getString("test"),
            )
        }

    private fun streamRemoteConfig(keys: Set<String>): Flow<FirebaseRemoteConfig> {
        return callbackFlow {
            val listener = object : ConfigUpdateListener {
                override fun onUpdate(configUpdate: ConfigUpdate) {
                    Firebase.remoteConfig.activate()
                    if (configUpdate.updatedKeys.intersect(keys).isNotEmpty()) {
                        runBlocking {
                            send(Firebase.remoteConfig)
                        }
                    }
                }

                override fun onError(error: FirebaseRemoteConfigException) {
                    close(error)
                }
            }
            send(Firebase.remoteConfig)
            val registration = Firebase.remoteConfig.addOnConfigUpdateListener(listener)

            awaitClose {
                registration.remove()
            }
        }
    }
    
    override fun initialize() {
        configCoroutineScope.launch {
            retryWithExponentialBackoff {
                Firebase.remoteConfig.setDefaultsAsync(
                    mapOf(
                        // Here i set some defaults...
                    )
                ).await()
                configStatus.value = ConfigStatus.DefaultsReady
            }
            
            retryWithExponentialBackoff {
                Firebase.remoteConfig.fetch(0).await()
                Firebase.remoteConfig.activate()
                configStatus.value = ConfigStatus.RemoteReady
            }
        }
    }

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions