Skip to content

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

Open
@FilippoVigani

Description

@FilippoVigani
  • 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
            }
        }
    }

Metadata

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