SupportFragmentManager destroyed, behind the scenes - android

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.

Related

how to fix strictmode.FragmentReuseViolation: Attempting to reuse fragment

I have this code and i call several times navigateTo(messageFragment) and have
StrictMode violation in ru.mars_groupe.socpayment.ui.fragment.MessageFragment
androidx.fragment.app.strictmode.FragmentReuseViolation: Attempting to reuse fragment MessageFragment{f4b8c68} (9ce8abef-3a1e-4ea9-9069-1404ec6a8127) with previous ID 561afbe2-e1b6-47f2-a2d9-56588dd2cbe9
how to fix it?
private val messageFragment = MessageFragment()
protected fun navigateTo(fragment: Fragment, addBackStackFlag: Boolean = false) {
if (addBackStackFlag) {
supportFragmentManager.commit {
replace(containerId, fragment)
addToBackStack(PaymentActivity.BACK_STACK_FRAGMENT)
setReorderingAllowed(true)
}
} else {
supportFragmentManager.beginTransaction()
.replace(containerId, fragment)
.commitAllowingStateLoss()
}
}

Fragment navigation onBackPRessed

My task is to dynamically go on back pressed when i want to go back from fragment.
I have bottom navigation which is in activity.
My Code:
MainActivity:
setCurrentFragment(topArticlesFragment)
binding.bottomNavigationView.setOnItemSelectedListener {
when (it.itemId) {
R.id.topArticles -> setCurrentFragment(topArticlesFragment)
R.id.allArticles -> setCurrentFragment(everythingArticlesFragment)
}
return#setOnItemSelectedListener true
}
private fun setCurrentFragment(fragment: Fragment) {
supportFragmentManager
.beginTransaction().apply {
replace(R.id.frame_layout, fragment)
this.addToBackStack(null)
commit()
}
}
TopArticlesFragment:
binding.recyclerViewTop.visibility = GONE
binding.searchView.visibility = GONE
parentFragmentManager
.beginTransaction()
.replace(R.id.topArticlesFragment, fragment)
.addToBackStack(null)
.commit()
}
DetailsFragment:
binding.toolbar.setNavigationOnClickListener {
activity?.onBackPressed()
}
Pictures of what is happening
I also tried with nav_graph but i put bottom navigation in MainFragment and when i choose another fragment bottom navigation disappeared.
Edit:
EDIT:
I also have onResume and onStop in TopArticleFragment and Everything article fragment to show and hide topMenu and bottom navigation. But when used binding.recyclerView.isVisible = true nothing happens.
override fun onResume() {
super.onResume()
(activity as AppCompatActivity?)!!.supportActionBar!!.show()
(activity as AppCompatActivity?)!!.findViewById<BottomNavigationView>(R.id.bottomNavigationView).isVisible =
true
}
override fun onStop() {
super.onStop()
(activity as AppCompatActivity?)!!.supportActionBar!!.hide()
}

How to return previous fragment by calling activity's onBackPress()

I have an activity with two fragments added to it using :
fun addFragment(fragment: Fragment){
private val fragmentManager = supportFragmentManager
val fragmentTag = fragment::class.simpleName.toString()
val ft = fragmentManager.beginTransaction()
ft.add(R.id.fragments_container, fragment,fragmentTag)
ft.addToBackStack(fragmentTag)
ft.commit()
}
I add two fragments A and B to activity with this order:
A ---> B
when i press back button on phone it return from B to A as expected
but the problem is that when i call activity's onBackPressed method when clicking on a view for example:
imgBack.setOnClickListener {
onBackPressed()
}
it does not work like when i press back button on the phone
it returns to fragment A but not showing fragment A views as expected.
onBackPressed:
override fun onBackPressed() {
if (fragmentManager.backStackEntryCount > 1) {
fragmentManager.popBackStack()
} else {
finish()
}
}
if you imgBack are in fragmentB call
imgBack.setOnClickListener {
activity?.onBackPressed()
}

How can I hide a fragment using supportFragmentManager?

Here's my attempt:
private inline fun FragmentManager.inTransaction(func: FragmentTransaction.() -> FragmentTransaction) {
beginTransaction().func().addToBackStack(null).commit()
}
private fun AppCompatActivity.addFragment(fragment: Fragment, frameId: Int){
supportFragmentManager.inTransaction { add(frameId, fragment) }
}
private fun AppCompatActivity.showFragment(fragment: Fragment) {
supportFragmentManager.inTransaction{show(fragment)}
}
private fun showFragmentView(fragment: Fragment){
// Hide the current Fragment
if (supportFragmentManager.fragments.isNotEmpty()) {
val currentFragment = supportFragmentManager.fragments.last()
if (currentFragment != null) {
supportFragmentManager
.beginTransaction()
.hide(currentFragment)
.commit()
}
}
// Add or Show
if (!fragment.isAdded) {
addFragment(fragment, sendFragFrame.id)
} else {
showFragment(fragment)
}
}
It properly adds the fragment to the frame, but when I attempt to hide it nothing happens, it's stays visible and the second fragment cannot be seen. Can someone explain why this is happening?
It is not good practice (and most likely won't work) to access fragments in this way.
if (supportFragmentManager.fragments.isNotEmpty()) {
val currentFragment = supportFragmentManager.fragments.last()
if (currentFragment != null) {
supportFragmentManager
.beginTransaction()
.hide(currentFragment)
.commit()
You should add fragments with a tag and get the fragment by tag when you want to remove it and then do your transaction.
See the comments on this for more:
How do I get the currently displayed fragment?
Well embarrassingly my problem was that I had inadvertently swap my layout in Fragment2 with the layout for Fragment1...so it WAS working, but because they shared a layout you couldn't see it visually. I'd delete this post if I could, but I'll leave it here as tribute to my shame as a developer.

java.lang.IllegalStateException: Fragment no longer exists for key f0: index 1

I am having couple of fragment in an Activity. After doing some process I am closing the fragment using the below code.
if (getActivity().getSupportFragmentManager().getBackStackEntryCount() > 0) {
getActivity().getSupportFragmentManager().popBackStackImmediate();
}
But on line popBackStackImmediate() its throwing the error
Process: com.TestProject.testpro, PID: 17966
java.lang.IllegalStateException: Fragment no longer exists for key f0: index 1
at android.support.v4.app.FragmentManagerImpl.getFragment(FragmentManager.java:879)
at android.support.v4.app.FragmentStatePagerAdapter.restoreState(FragmentStatePagerAdapter.java:215)
at android.support.v4.view.ViewPager.onRestoreInstanceState(ViewPager.java:1481)
at android.view.View.dispatchRestoreInstanceState(View.java:14746)
at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3121)
at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3127)
at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3127)
at android.view.View.restoreHierarchyState(View.java:14724)
at android.support.v4.app.Fragment.restoreViewState(Fragment.java:475)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1329)
at android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1528)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1595)
at android.support.v4.app.BackStackRecord.executePopOps(BackStackRecord.java:807)
at android.support.v4.app.FragmentManagerImpl.executeOps(FragmentManager.java:2360)
at android.support.v4.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2149)
at android.support.v4.app.FragmentManagerImpl.optimizeAndExecuteOps(FragmentManager.java:2103)
at android.support.v4.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.java:823)
at android.support.v4.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.java:776)
at com.TestProject.testpro.application.fragments.WFMUpgradeToAffinityFragmentAN$WFMStoreProfileIdReceiver.onReceive(WFMUpgradeToAffinityFragmentAN.java:1064)
at android.support.v4.content.LocalBroadcastManager.executePendingBroadcasts(LocalBroadcastManager.java:297)
at android.support.v4.content.LocalBroadcastManager.access$000(LocalBroadcastManager.java:46)
at android.support.v4.content.LocalBroadcastManager$1.handleMessage(LocalBroadcastManager.java:116)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
There is no other crash which close the fragment. I cant find the exact solution for this issue. Please help me on this.
Edited:
I am using the below function to call the fragment.
void showContentFragment(Fragment newFragment, String backStackName) {
//Log.e(TAG, "showContentFragment- " + "BackstackName: "+ backStackName);
FragmentManager mFragmentManager = getActivity().getSupportFragmentManager();
if (newFragment != null) {
//updateNavigation(backStackName);
FragmentTransaction ft = mFragmentManager.beginTransaction();
ft.replace(R.id.fragment_container, newFragment);
if (backStackName != null && !backStackName.isEmpty()) {
ft.addToBackStack(backStackName);
}
ft.commit();
}
}
For ViewPager2 simply use viewPager.setSaveEnabled(false);
In the ViewPager Adapter, override restoreState(state, loader)
#Override
public void restoreState(final Parcelable state, final ClassLoader loader) {
try {
super.restoreState(state, loader);
} catch (Exception e) {
e.printStackTrace();
}
}
Please follow this steps to add fragments from your activity
getSupportFragmentManager().beginTransaction()
.replace(R.id.frame, yourFragment, yourFragment.class.getSimpleName()).addToBackStack(yourFragment.class.getSimpleName())
.commit();
using this code for calling popBackStack() from your fragment
if (getFragmentManager().getBackStackEntryCount() > 0) {
getFragmentManager().popBackStack();
}
Make sure that you are importing right fragment class
import android.support.v4.app.Fragment;
add Below code in your Fragment classes , according to doc it returns the Transition to use to move Views out of the Scene when the Fragment is preparing to close.
#Override
public Object getReturnTransition() {
return super.getReturnTransition();
}
Your stacktrace shows you are using both a ViewPager and manipulating the backstack yourself.
This is probably not a good idea - either let your ViewPager manage the backstack (which it does well) or manage it entirely yourself.
Note that in managing the backstack yourself you will have to account for restoring instance state (which features in your stacktrace) and the Fragment lifecycle.
This means in conditions of low memory your Activity and Fragments will be stopped. If you have not saved the instance state correctly, when they are restarted you will get errors like the above one. If the workflow of your Fragments is simple, you can avoid all of this by using a ViewPager.
public void showContentFragment(Fragment fragment, Bundle bundle, boolean addToBackStack) {
if (bundle != null)
fragment.setArguments(bundle);
tag = fragment.getClass().getSimpleName();
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left, R.anim.slide_in_left, R.anim.slide_out_right);
ft.replace(R.id.container_internal, fragment, tag);
fragment.setRetainInstance(true); // IMPORTANT
if (addToBackStack)
ft.addToBackStack(tag);
try {
ft.commit();
} catch (Exception ex) {
ex.printStackTrace();
ft.commitAllowingStateLoss();
}
First, you don't need to call getActivity().getSupportFragmentManager(). getFragmentManager() is enough to call FragmentManager.
If you are in Support Fragment getFragmentManager() will give you supportFragmentManager() or it will give you app.Fragment Manager.
If you are calling the PopBackstack inside the ViewPager, it won't work. If you are using viewPager inside another fragment. you should call popBackstack only in the parent fragment. Not in the viewPager.
Try the below code it will clear the stack according to the back stack name.
getFragmentManager.popBackStackImmediate(YourBackStackName, FragmentManager.POP_BACK_STACK_INCLUSIVE)
Docs: https://developer.android.com/reference/android/app/FragmentManager.html#popBackStackImmediate(java.lang.String, int)
Hope it helps :)
Overriding restoreState method in my viewPager worked for me.
override fun restoreState(state: Parcelable?, loader: ClassLoader?) {
try {
super.restoreState(state, loader)
} catch (e: Exception) {
e.printStackTrace()
}
}
Here is the final code of my view pager
class MenuTabAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) {
private val mFragmentList = ArrayList<Fragment>()
private val mFragmentTitleList = ArrayList<String>()
private var mCurrentPosition = -1
override fun getItem(position: Int): Fragment {
return mFragmentList[position]
}
override fun getCount(): Int {
return mFragmentList.size
}
fun addFragment(fragment: Fragment, title: String) {
mFragmentList.add(fragment)
mFragmentTitleList.add(title)
}
override fun getPageTitle(position: Int): CharSequence? {
return mFragmentTitleList[position]
}
override fun setPrimaryItem(container: ViewGroup, position: Int, `object`: Any) {
super.setPrimaryItem(container, position, `object`)
if (position !== mCurrentPosition && container is HeightWrappingViewPager) {
val fragment = `object` as Fragment
if (fragment?.view != null) {
mCurrentPosition = position
container.requestLayout()
}
}
}
override fun restoreState(state: Parcelable?, loader: ClassLoader?) {
try {
super.restoreState(state, loader)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
When this crash occurs?
When ViewPager wraps Fragment, and there is ViewPager wraps Fragment inside Fragment.
When the outer Fragment or the inner Adapter inherits FragmentStatePagerAdapter, or both inherit FragmentStatePagerAdapter.
How to fix?
Override the following method in the Adapter of the Fragment that reported the error and return null.
#Override
public Parcelable saveState() {
return null;
}
More details here.
This happens when viewPagerAdapter(in ViewPager2->restorePendingState->restoreState) became null, but ViewPager2->restorePendingState still received it.
Try to mark stop breakpoint at this line or reinstall app
void is in androidx.viewpager2.widget.ViewPager2
private void restorePendingState() {
...
Adapter<?> adapter = getAdapter();
if (adapter == null) {
return;
}
...
}

Categories

Resources