I have this scenario on my MainActivity
:
// onCreate
firebaseAuth.addAuthStateListener { firebaseAuth ->
when (firebaseAuth.currentUser) {
null -> {
hideAppBars()
clearBackStack(supportFragmentManager)
showFragment(fragment = LoginOrRegisterFragment())
}
else -> {
showAppBars()
clearBackStack(supportFragmentManager)
showFragment(fragment = HomeFragment())
}
}
}
The clearBastack
is just a method that is popping the full backstack of the Fragments:
private fun clearBackStack(fragmentManager: FragmentManager) {
with(fragmentManager) {
if (backStackEntryCount > 0)
popBackStack()
}
}
And showFragment
method:
fun showFragment(fragment: Fragment, addToBackStack: Boolean = false) {
supportFragmentManager.beginTransaction().apply {
replace(R.id.fragmentContainer, fragment)
if (addToBackStack) addToBackStack(null)
}.commit()
}
In a usual flow, everything goes OK. Hit Login: BackStack clears and from LoginFragment
I get to HomeFragment
. However, if I press back when I'm in the LoginFragment
and resume , I get IllegalStateException: FragmentManager has been destroyed
What seems to fix the issue
Explicitly checking if(!supportFragmentManager.isDestroyed)
:
fun showFragment(fragment: Fragment, addToBackStack: Boolean = false) {
if (!supportFragmentManager.isDestroyed) {
supportFragmentManager.beginTransaction().apply {
replace(R.id.fragmentContainer, fragment)
if (addToBackStack) addToBackStack(null)
}.commit()
}
}
UPDATE: Full stacktrace:
java.lang.IllegalStateException: FragmentManager has been destroyed
at androidx.fragment.app.FragmentManager.enqueueAction(FragmentManager.java:1725)
at androidx.fragment.app.BackStackRecord.commitInternal(BackStackRecord.java:321)
at androidx.fragment.app.BackStackRecord.commit(BackStackRecord.java:286)
at com.coroutinedispatcher.datacrypt.MainActivity.showFragment(MainActivity.kt:57)
at com.coroutinedispatcher.datacrypt.MainActivity.showFragment$default(MainActivity.kt:52)
at com.coroutinedispatcher.datacrypt.MainActivity$onCreate$1.onAuthStateChanged(MainActivity.kt:36)
at com.google.firebase.auth.zzj.run(com.google.firebase:firebase-auth@@19.4.0:3)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at com.google.android.gms.internal.firebase_auth.zzj.dispatchMessage(com.google.firebase:firebase-auth@@19.4.0:6)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
Line that throws is supportFragmentManager.apply{bla()}.commit()
.
Question is, why, what is actually happening in the background?
You should remove the AuthStateListnener in onDestroy of Activity.
// onCreate
private val authStateListener = AuthStateListener { firebaseAuth ->
when (firebaseAuth.currentUser) {
null -> {
hideAppBars()
clearBackStack(supportFragmentManager)
showFragment(fragment = LoginOrRegisterFragment())
}
else -> {
showAppBars()
clearBackStack(supportFragmentManager)
showFragment(fragment = HomeFragment())
}
}
}
override fun onCreate(...) {
super.onCreate(...)
firebaseAuth.addAuthStateListener(authStateListener)
}
override fun onDestroy() {
firebaseAuth.removeAuthStateListener(authStateListener)
super.onDestroy()
}
Although technically you should also consider that this could still trigger fragment transactions after onStop
, which would cause a this action cannot be performed after onSaveInstanceState
error, so you should actually only handle the navigation action if the Activity is at least started.
You could use https://github.com/Zhuinden/live-event for example for that.