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()
}
}
Related
I have the following method
// Change fragment extension function to handle navigation easily
fun changeFragment(fragmentManager: FragmentManager?, #IdRes containerId: Int, fragment: Fragment?, addToBackStack: Boolean = false) {
if (fragmentManager == null || fragment == null) return
val fragmentTransaction = fragmentManager.beginTransaction()
if (addToBackStack) fragmentTransaction.addToBackStack(null)
fragmentTransaction.replace(containerId, fragment, fragment::class.java.simpleName).commit()
}
and I am using it inside one of my fragments -
class InspectionFragment : Fragment(R.layout.fragment_inspection) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
inspectionAdapter = InspectionAdapter { inspection ->
changeFragment(
parentFragmentManager, R.id.fragment_inspection_frame_layout,
InspectionDetailsFragment.newInstance(inspection), true
)
}
}
}
class InspectionDetailsFragment : Fragment() {
companion object {
fun newInstance(inspection: Inspection): InspectionDetailsFragment {
val inspectionDetailsFragment = InspectionDetailsFragment()
val bundle = Bundle()
bundle.putParcelable(GeneralConstants.INSPECTION, inspection)
inspectionDetailsFragment.arguments = bundle
return inspectionDetailsFragment
}
}
}
The thing is that, when I instansiate onetop of the 'InspectionDetailsFragment' layer another fragment and press back, it goes directly back to the 'InspectionFragment' parent.
I can't understand why this is happening.
Anyone has an idea?
When you call changeFragment function, you're passing true as last argument:
changeFragment(
parentFragmentManager, R.id.fragment_inspection_frame_layout,
InspectionDetailsFragment.newInstance(inspection), true
)
In changeFragment function:
if (addToBackStack) fragmentTransaction.addToBackStack(null)
It doesn't get added to backStack. Instead of null i think you should pass String value as the name of the backStack you want to use.
I'm not 100% sure about this. You can read more about fragment transactions from here: https://developer.android.com/guide/fragments/transactions
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.
I'm pretending to switch from one Fragment to another and get a saved Bundle configuration between them:
Fragment A (named SpotSelection)
// Communication
interface OnMessageSendListener {
public fun onMessageSend(id: Int, currentFragment: Fragment)
}
var messageSendListener: OnMessageSendListener? = null
override fun onAttach(context: Context) {
super.onAttach(context)
val activity: Activity = context as Activity
try {
messageSendListener = activity as OnMessageSendListener
} catch (e: ClassCastException) {
throw ClassCastException(activity.toString() + " must implement onMessageSendListener...")
}
}
[...]
// This is a method of a setOnClickListener button, to switch from the fragment "SpotSelection" to the fragment "Spots" when I press the button
private fun entrarColeccion(id: Int) {
Log.d("selection", "Entrando en onMessageSend(" + id + ")")
getVisibleFragment()?.let { messageSendListener?.onMessageSend(id, it) }
// I cannot do this because I would miss the Bundle ;( it would be so easy...
//findNavController().navigate(R.id.action_spotSelection_to_spots)
}
fun getVisibleFragment(): Fragment? {
val fragmentManager: FragmentManager = requireActivity().supportFragmentManager
val fragments: List<Fragment> = fragmentManager.getFragments()
if (fragments != null) {
for (fragment in fragments) {
if (fragment != null && fragment.isVisible) return fragment
}
}
return null
}
MainActivity
// SpotSelection Communication
override fun onMessageSend(id: Int, currentFragment: Fragment) {
Log.d("selection", "Dentro de onMessageSend(" + id + ")")
// The Fragment where I want to go
val spotsFragment: Spots = Spots()
val bundle: Bundle = bundleOf(Pair("coleccionID", id))
Log.d("selection", "Guardando bundle: (coleccionID, " + id + ")")
spotsFragment.setArguments(bundle)
supportFragmentManager.beginTransaction()
.hide(currentFragment)
.replace(R.id.fragment_container, spotsFragment)
.show(spotsFragment)
.addToBackStack(null)
.commit()
}
It does the following (by my observations of the debbuger): hides the actual fragment (SpotSelection), start the onCreateView of the Spots fragment (where I want to go), but doesn't display its UI. How do I display the UI of Spots? I feel like I'm so close...
using replace may cause problem some times use this maybe help you:
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment fragment = fragmentManager.findFragmentById(R.id.fragment_container);
if(fragment == null){
fragmentManager.beginTransaction()
.hide(currentFragment)
.add(R.id.fragment_container, spotsFragment)
.show(spotsFragment)
.addToBackStack(null)
.commit()
}else{
fragmentManager.beginTransaction()
.hide(currentFragment)
.replace(R.id.fragment_container, spotsFragment)
.show(spotsFragment)
.addToBackStack(null)
.commit()
}
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.
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;
}
...
}