How can I hide a fragment using supportFragmentManager? - android

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.

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()
}

Fragment doesn't register in backstack

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

Context is null in fragment before start of lifecycle?

There is a huge bug I am trying to figure out in my latest project using Kotlin. So how the app works is that it has a Bottom Navigation Activity, and on click on the icons on the Bottom menu, it replaces the container with the respected fragment. On onCreate of the activity, I instantiate all the different fragments with a function defined in a companionObject within the fragments, which returns itself (kind of like a static factory method in java):
override fun onCreate(savedInstanceState: Bundle?) {
{...}
navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener)
fragment1 = Fragment1.newInstance()
fragment2 = Fragment2.newInstance()
}
Then I have this switch to replace the container to the respected fragment on click:
private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.f1 -> {
replaceFragment(fragment1)
return#OnNavigationItemSelectedListener true
}
R.id.f2 -> {
replaceFragment(fragment2)
return#OnNavigationItemSelectedListener true
}
}
false
}
{...}
fun replaceFragment(destFragment: Fragment) {
supportFragmentManager
.beginTransaction()
.replace(R.id.container, destFragment)
.addToBackStack(destFragment.toString())
.commit()
}
The fragments are communicating. If you where to click on both fragments, and thereby cause their lifecycles to execute, the code works fine. However, if you do changes in frag1, invoking a communication between the fragments, without having clicked on frag2 in advance, the app crashes. The reason is this line in frag2:
fun saveList(){
val sharedpref : SharedPreferences = context!!.getSharedPreferences("sharedPrefs", MODE_PRIVATE)
{...}
}
Turns out that the context is null, if the user has never invoked the frag2 lifecycle by clicking it. Any ideas to how to fix this problem?
Use Activity context for these case
fun saveList() {
activity?.let {
val sharedpref: SharedPreferences =
it.getSharedPreferences("sharedPrefs", MODE_PRIVATE)
{ }
}
}

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