My activity is composed of 3 nested Fragments. There is my MainFragment that is displayed by default, ProductFragment that can be called from it, then DetailFragment can be called from ProductFragment.
I can go back and forth between my ProductFragment and DetailFragment. By doing so, the popStackBack method is accumulating similar fragments. Then, if I click on the back button, It will go back through all the Fragments as many time I called them.
What is the proper way to avoid the same Fragment to be kept in the back stack ?
EDIT :
I firstly call my main fragment :
if (savedInstanceState == null) {
getFragmentManager()
.beginTransaction()
.add(R.id.container, new SearchFragment(), "SEARCH_TAG")
.commit();
}
Here is the code that calls the fragments from the activity :
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.setCustomAnimations(R.animator.enter_from_bottom, R.animator.exit_to_top, R.animator.enter_from_bottom, R.animator.exit_to_top);
ft.replace(R.id.container, new FactFragment(), "FACT_TAG");
ft.addToBackStack("FACT_TAG");
ft.commit();
Then, on back click :
#Override
public void onBackPressed() {
getFragmentManager().popBackStack();
}
I tried to get the tag of my current fragment and execute some specific code related to it but it doesn't work well. I also tried to addToBackStack() only when current Fragment wasn't already added to the backStack but it messed up my fragment view.
Use fragment's method isAdded() to evaluate the insertion. For example:
if(!frag.isAdded()){
//do fragment transaction and add frag
}
Here is my solution. Maybe dirty but it works. I implemented a method that returns the tag of the fragment that is displayed before clicking the on back button :
public String getActiveFragment() {
if (getFragmentManager().getBackStackEntryCount() == 0) {
return null;
}
String tag = getFragmentManager()
.getBackStackEntryAt(getFragmentManager()
.getBackStackEntryCount() - 1)
.getName();
return tag;
}
Then, on my onBackPressed() method :
// Get current Fragment tag
String currentFrag = getActiveFragment();
if(currentFrag.equals("PRODUCT_TAG")) {
// New transaction to first Fragment
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.setCustomAnimations(R.animator.enter_from_right, R.animator.exit_to_left, R.animator.enter_from_right, R.animator.exit_to_left);
ft.replace(R.id.container, new SearchFragment(), "MAIN_TAG");
ft.commit();
} else {
// Go to Fragment-1
getFragmentManager().popBackStack();
}
Here is my handy and simple solution to check for duplicate insertion through fragment manager
at first, I check if it is first time intention for adding fragment and then I check if the fragment is presented using fragment manager
Fragment fragment = getSupportFragmentManager().findFragmentByTag("firstFragment");
if (fragment == null) {
getSupportFragmentManager().beginTransaction().add(R.id.frameLayout, new FirstFragment(), "firstFragment")
.addToBackStack(null)
.commit();
}else if(!fragment.isAdded()){
getSupportFragmentManager().beginTransaction().add(R.id.frameLayout, new FirstFragment(), "firstFragment")
.addToBackStack(null)
.commit();
}
Here is my solution:
Fragment curFragment = fragmentManager.findFragmentById(R.id.frameLayout);
if(curFragment != null
&& curFragment.getClass().equals(fragment.getClass())) return;
// add the fragment to BackStack here
Xamarin.Android (C#) version:
var curFragment = fragmentManager.FindFragmentById(Resource.Id.frameLayout);
if (curFragment != null
&& curFragment.GetType().Name == fragment.GetType().Name) return;
// add the fragment to BackStack here
Related
I am pretty new to android development so I am curious how to work properly with Fragments.
My application contains a BottomNavigationActivity which switches between 3 fragments with this code:
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction().replace(R.id.content_montage_order_detail, fragment).commit();
I am storing the Fragments in a List<Fragment> to avoid loosing the current state. But everytime I replace the fragment with another the method onDestroy() is called.
I know, I know I could add and remove the fragment in the fragmentmanager instead of replacing it. I googled alot and most of the tutorials tell me to replace the fragment.
Whats the common way to keep a fragments state without recreating it on every call?
Find the solution
It will not recreate fragment anytime
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction().add(R.id.content_montage_order_detail, fragment).commit();
Use fragment TAG at time of creation of fragment then when you want to get it again use findFragmentByTag. if fragment already created then old one will be find by fragment manager.
Fragment previousFragment = fragmentManager.findFragmentByTag("TAG");
I suggest you use show,not forreplace
protected void addFragmentStack(Fragment fragment) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
if (this.mContent != fragment) {
if (fragment.isAdded()) {
ft.hide(this.mContent).show(fragment);
} else {
ft.hide(this.mContent).add(getFragmentViewId(), fragment);
}
this.mContent = fragment;
}
ft.commit();
}
Try using switchFragment to switch fragment, it will show fragment if it is already added.
Use fragmentTransaction.show method to re-use existing fragment i.e. saved instance.
public void switchFragment (Fragment oldFragment, Fragment newFragment, int frameId) {
boolean addFragment = true;
FragmentManager fragmentManager = getFragmentManager ();
String tag = newFragment.getArguments ().getString (BaseFragment.TAG);
Fragment fragment = fragmentManager.findFragmentByTag (tag);
// Check if fragment is already added
if (fragment != null && fragment.isAdded ()) {
addFragment = false;
}
// Hide previous fragment
String oldFragmentTag = oldFragment.getArguments ().getString (BaseFragment.TAG);
if (!tag.equals (oldFragmentTag)) {
FragmentTransaction hideTransaction = fragmentManager.beginTransaction ();
Fragment fragment1 = fragmentManager.findFragmentByTag (oldFragmentTag);
hideTransaction.hide (fragment1);
hideTransaction.commit ();
}
// Add new fragment and show it
FragmentTransaction addTransaction = fragmentManager.beginTransaction ();
if (addFragment) {
addTransaction.add (frameId, newFragment, tag);
addTransaction.addToBackStack (tag);
}
else {
newFragment = fragmentManager.findFragmentByTag (tag);
}
addTransaction.show (newFragment);
addTransaction.commit ();
}
Ya, you can also manage the state by managing the backstack.
I have two fragments one list and one detail fragment. On list item click I a hiding list fragment and adding detail fragment on back press detail fragment is popped automatically I am just calling super.onBackPressed() but issue is it is creating so many references of detail fragments resulting is memory leaks
Following is my code
FragmentTransaction ft = getActivity().getSupportFragmentManager().beginTransaction();
ft.hide(this);
ft.add(containerId, detailFragment, "detail");
ft.addToBackStack("detail");
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.commit();
I don't want to view to be recreated view when user press back button on detail fragment thats why I used above approach. Also with current implementation when I press back button recylerview scroll possition and other data I don't have to save
My Activity has only following code it inflates list fragment
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.FragmentContainer1, ListFragment.newInstance(), TAG).commit();
}
I think what you are doing wrong is that you are adding fragments each time to the container again and again by calling add method:
In your case you should use replace method and add your list fragment to the backstack. Here's how you should start your detail fragment:
FragmentManager fm = getFragmentManager();
fm.beginTransaction()
.replace(R.id.container, new DetailFragment())
.addToBackstack(null)
.commit();
To get back to your list fragment, which is in the back stack, just call:
fm.popBackStack();
EDIT:
Try this to show your list fragment:
protected void displayListFragment() {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
if (listFragment.isAdded()) {
ft.show(listFragment);
} else {
ft.add(R.id.flContainer, listFragment, "ListFragment");
}
if (detailFragment.isAdded()) {
ft.remove(detailFragment);
}
ft.commit();
}
And this to show your detail fragment:
protected void displayDetailFragment() {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
if (listFragment.isAdded()) {
ft.hide(listFragment);
}
if (!detailFragment.isAdded()) {
ft.add(R.id.flContainer, detailFragment, "DetailFragment");
}
ft.commit();
}
For my application, some fragments require the user to enter a pin in a dialog before it is shown. I have a BaseFragment class that all my other fragments extend which stores if the pin is required. My issue right now is dealing with the back button as if the user tries to go back to a fragment that requires the pin it needs to show a pin dialog first.
Currently I'm overriding onBackPressed() in which I want to peek the backstack to see what fragment will be resumed if popBackstack() was called so I can check if the pin dialog should be shown. Below is my current code which popFragment is coming back null:
#Override
public void onBackPressed() {
if(getSupportFragmentManager().findFragmentById(R.id.fragment_container) instanceof BaseFragment) {
BaseFragment frag = (BaseFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
int index = getSupportFragmentManager().getBackStackEntryCount() - 1;
Log.e("Back stack", Integer.toString(index));
FragmentManager.BackStackEntry backEntry = (FragmentManager.BackStackEntry) getSupportFragmentManager().getBackStackEntryAt(index);
BaseFragment popFragment = (BaseFragment) getSupportFragmentManager().findFragmentById(backEntry.getId());
BaseFragment.checkAuth(this, frag, popFragment, new NavigationCallback((AppCompatActivity) this));
} else {
super.onBackPressed();
}
} else {
super.onBackPressed();
}
}
I've checked if backEntry is null and its not. backEntry.getId() also comes back with a value of 1 so I'm not sure why findFragmentById() is then null. I also dont think it has to do with how I'm showing fragments but here is my code for that as well:
FragmentManager fragManager = mActivity.getSupportFragmentManager();
FragmentTransaction transaction = fragManager.beginTransaction();
transaction.replace(R.id.fragment_container, mFragment);
transaction.addToBackStack(null);
transaction.commit();
The BackStackEntry id and Fragment id have nothing to do with each other.
I have done something similar to what you are doing by using tags. Again, the BackStackEntry tag and Fragment tags have nothing to do with each other; however, by using the same value for both tags you can accomplish your goal.
String tag = ...
FragmentManager fragManager = mActivity.getSupportFragmentManager();
FragmentTransaction transaction = fragManager.beginTransaction();
// tag is associated with fragment
transaction.replace(R.id.fragment_container, mFragment, tag);
// same tag is associated with back stack entry
transaction.addToBackStack(tag);
transaction.commit();
...
int count = fragmentManager.getBackStackEntryCount();
if (count > 0) {
FragmentManager.BackStackEntry topBackStackEntry =
fragmentManager.getBackStackEntryAt(count - 1);
String tag = topBackStackEntry.getName();
if (tag != null) {
Fragment fragment = fragmentManager.findFragmentByTag(tag);
if (fragment != null) {
// we found the fragment
// you can also sanity-check here with fragment.isResumed()
}
}
}
You are gone Frag-A -> Frag-B -> Frag-C. Now you want to go back to Frag-A without creating a new Fragment view for Frag-A ?
Here is the solution:
Navigation.findNavController(requireView()).popBackStack(R.id.Frag_A, false)
Also delete your nav entry action_Frag_C_to_Frag_A. If you have accidentely created a navgraph.
Hi I am using a Stack variable for storing the sequence of fragments clicked by the user
For example : [A,B,C,A,D]
Now I need to check if a fragment exits in the stack I need to reuse it.
From the above example I need to reuse the first element i.e, A when user clicks on the fourth time.
The problem is I have a custom keyboard opening in fragment A, now if the same fragment is in the stack as forth element the keyboard is not opening .but when I backpress and go to first element keyboard is opened in the first instance of A
Code for storing in the Stack
public void switchContent(Fragment fragment) {
if(mContent !=null && (fragment.getClass().toString().equals(mContent.getClass().toString()))){
getSlidingMenu().showContent();
return;
}
mContent = fragment;
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction ft = fragmentManager.beginTransaction();
ft.add(R.id.content_frame, mContent);
FragmentChangeActivity.fragmentStack.lastElement().onPause();
FragmentChangeActivity.fragmentStack.push(mContent);
ft.commit();
getSlidingMenu().showContent();
}
Code for backpress
#Override
public void onBackPressed() {
if (fragmentStack.size() >= 2) {
FragmentTransaction ft = fragmentManager.beginTransaction();
fragmentStack.lastElement().onPause();
ft.remove(fragmentStack.pop());
fragmentStack.lastElement().onResume();
ft.show(fragmentStack.lastElement());
ft.commit();
} else {
super.onBackPressed();
}
}
Any help is appreciated.
The following code check's if Fragment named SearchFragment exists or not in backstack and processes accordingly. You can use this logic to determine if a specific Fragment exists or not
FragmentManager fragmentManager=getSupportFragmentManager();
Fragment currentFragment = (Fragment)fragmentManager.findFragmentById(R.id.mainContent);
if(currentFragment!=null && currentFragment instanceof SearchFragment){
//SearchFragment exists in backstack , process accordingly
}else{
//SearchFragment not present in backstack
}
Im trying to load an new fragment when a method is called. This method creates a new fragment and "replaces" the other fragment:
private void showTestFragment(Fragment oldFragment, boolean addBackStack, BaseAdapter adapter, int position) {
Cursor cursor = (Cursor)adapter.getItem(position);
if(cursor != null){
int idx = cursor.getColumnIndexOrThrow(Episode._ID);
long rowId = cursor.getLong(idx);
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
if(oldFragment != null){
Log.i(TAG, "Removing the old fragment");
fragmentTransaction.remove(oldFragment);
}
TestFragment testFragment = new TestFragment();
testFragment.setId(rowId);
fragmentTransaction.add(android.R.id.content, testFragment);
if(addBackStack){
Log.i(TAG, "Added to the backstack");
fragmentTransaction.addToBackStack(TAG);
}
fragmentTransaction.commit();
Fragment f = getFragmentManager()
.findFragmentById(R.id.index);
Log.i(TAG, "after commit, frag is "+ f);
}
}
This works fine, until i go back. The last fragment, should be removed when i go back. Before i'm going to implement methods on the activities
public void onBackPressed(){}
to remove the last fragment, i want to know if i handle the fragment change correctly. It looks like i'm missing something here..
If you really want to replace the fragment then use replace() methode instead of doing a remove() and an add().
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(..............);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
Don't forget to do the addToBackStack(null) so your previous state will be added to the backstack allowing you to go back with the back button.
See also https://developer.android.com/reference/android/app/FragmentTransaction.html#replace(int, android.app.Fragment, java.lang.String) .
Another good source is http://developer.android.com/guide/components/fragments.html (search for replace() function).
Just remove first and the call super.onBackPressed
public void onBackPressed(){
// here remove code for your last fragment
super.onBackPressed();
}
//lets wait for systems back to finish
super.onBackPressed();
//here we believe a fragment was popped, so we need to remove the fragment from ourbackstack
if(fragmentBackStack.size()>0)
{
Log.d("custombackstack","before back: "+fragmentBackStack.size()+" current:"+fragmentBackStack.peek());
fragmentBackStack.pop();
}
//after popping is the size > 0, if so we set current fragment from the top of stack, otherwise we default to home fragment.
if(fragmentBackStack.size()>0)
{
Log.d("custombackstack","after back: "+fragmentBackStack.peek());
currentFragment = fragmentBackStack.peek();
}
else
{
//back stack empty
currentFragment = HOME_FRAGMENT;
}