Clear Fragment Backstack except top fragment - java

In my app I have some menu items in a Navigation Drawer. When the user taps one of these items and navigates to the corresponding fragment, I want the fragment backstack to be cleared EXCEPT for the fragment they just navigated to. Let me illustrate:
In my nav drawer the user has options to open fragments F1, F2, F3, F4, F5, F6. When the app is first opened, the user starts on F1. Let's say from F1, the user opens fragments F7 -> F8 -> F9 -> F10 using buttons within the fragment views themselves. At this point, the backstack looks like:
F1 -> F7 -> F8 -> F9 -> F10
Suppose from here, the user opens the nav drawer and selects F2. What I want to happen is that the above backstack is cleared and is populated with ONLY F2, so that they user can't go back to F10 from F2. In otherwords, the backstack should become:
Not this: F1 -> F7 -> F8 -> F9 -> F10 -> F2
But this: F2
How can I accomplish this?
EDIT 1:
One of my attempted solutions was to add an OnNavigationItemSelectedListener to the navigationView when the activity is loading that would clear the backstack when one of the items was pressed. My solution worked, but in the process I overrode the default code that actually changes to the new fragment. I snooped through the Android API and found this:
navigationView.setNavigationItemSelectedListener(
new NavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
boolean handled = onNavDestinationSelected(item, navController);
if (handled) {
ViewParent parent = navigationView.getParent();
if (parent instanceof DrawerLayout) {
((DrawerLayout) parent).closeDrawer(navigationView);
} else {
BottomSheetBehavior bottomSheetBehavior =
findBottomSheetBehavior(navigationView);
if (bottomSheetBehavior != null) {
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
}
}
}
return handled;
}
});
This is NOT my code. This is the native code that handles selection of menu items on the navigation drawer (found in the NavigationUI class). If I want to add my own OnNavigationItemSelectedListener I would have to recreate the functionality shown above, which I can't do since some of the methods in there are protected. So that's why I don't believe this solution will work.

You can try to clear fragments which are in backstack
FragmentManager manager = getSupportFragmentManager();
if (manager.getBackStackEntryCount() > 0) {
FragmentManager.BackStackEntry first = manager.getBackStackEntryAt(0);
manager.popBackStack(first.getId(), FragmentManager.POP_BACK_STACK_INCLUSIVE);
}

I hope you are setting all this fragment in the same activity. If yes, then it will not be very hard to achieve whatever you want to achieve.
Make sure whenever you set your fragment in suppose MainActivity, follow this code.
val fragmentTransaction = supportFragmentManager.beginTransaction()
if (fragment == null) {
fragment = Frag2.newInstance()
fragmentTransaction.replace(R.id.container, fragment, Frag2.TAG)
} else {
fragmentTransaction.show(fragment)
}
fragmentTransaction.commit()
Main logic to achieve required behaviour is behind fragmentTransaction.replace().
So, whenever you set another fragment in a container, use replace() rather than add().
So, try this and give me feedback about it.
Happy coding..!

Related

How to check which menu file is inflated in BottomNavigationView?

I use 2 different menu for my bottom navigation view called
bottom_navigation_menu.xml
bottom_navigation_menu_verified.xml
I need to set different action based on what is the current menu inflated in my bottom navigation view. I want to make something like this
lateinit var bottomNavigationView : BottomNavigationView
if (bottomNavigationView.menu == R.menu.bottom_navigation_menu_verified) {
// do something here
}
but the code above is invalid because bottomNavigationView.menu will return Menu data type and R.menu.bottom_navigation_menu_verified will return int, type doesn't match. so how to check what is the current inflated menu in my bottom navigation view ?
java/kotlin is okay
You can probably check for a specific MenuItem:
if (null != bottomNavigationView.menu.findItem(R.id.bottom_navigation_menu_item1)) {
// do something here
}

Navigation Drawer has wierd behaviour

So my situation is that i've implemented navigation drawer correctly (so to speak, it displays properly and calls out method (in a weird way but it triggers)) but it acts weird and I don't know what else to do but to ask u peeps !
Once i've started binding methods to drawer's menu items (options) i've begun to notice weird behavior. Let me explain by words first :
Let this be dummy menu :
Ok so you see the order goes: home - history - favourite - logout
So far i've implemented history and favorite this is the behaviour i get :
If i call history it will open activity properly and display data
If i call favourite i will open HISTORY FIRST then FAVOURITE SECOND (i can see that because when i press back it closes favourite, opens history)
I've added a dummy activity as third option to menu (not displayed on pic) and i get the same behaviour but this time it opens History - Favourite - Dummy if i click on dummy activity.
One more thing before i post the code of how i implemented it.
Lets take 2 of mine activities and make real time example ( all in same ciclus).
1) From home page i try to use drawer and call out history/fav it works perfectly, i enter search i try to use drawer call out history/fav works perfeclty, i press back return to main activity (home) press drawer to open history/favourite and puuf it's not working anymore... any idea if it's connected or just an separate problem to solve?
Implementation:
final NavigationView navigationView = findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(
new NavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(MenuItem menuItem) {
// set item as selected to persist highlight
menuItem.setChecked(true);
// close drawer when item is tapped
switch (menuItem.getItemId()) {
case R.id.nav_home: {
if (checkInternetConnection(getApplicationContext())) {
Log.i(LOG_TAG, "PROVJERA NETA POVEZAN");
start_loader();
} else {
mEmptyStateTextView.setText("Cannot connect to network, please check your network status and try again.");
// Clear the adapter of previous earthquake data
}
}
case R.id.nav_history: {
pozoviHistory(null);
}
case R.id.nav_favourite: {
pozoviFavorites();
}
}
mDrawerLayout.closeDrawers();
// Add code here to update the UI based on the item selected
// For example, swap UI fragments here
return true;
}
}
);
mDrawerLayout.addDrawerListener(
new DrawerLayout.DrawerListener() {
#Override
public void onDrawerSlide(View drawerView, float slideOffset) {
// Respond when the drawer's position changes
}
#Override
public void onDrawerOpened(View drawerView) {
// Respond when the drawer is opened
}
#Override
public void onDrawerClosed(View drawerView) {
// Respond when the drawer is closed
navigationView.getMenu().getItem(0).setChecked(true);
}
#Override
public void onDrawerStateChanged(int newState) {
// Respond when the drawer motion state changes
}
}
);
****** EDIT ********
Okay so from the answer below adding the break; to each case resolved issue of opening everything. I've managed to find more symptoms causing drawer to act dumb.
So the action i'm doing that's causing drawer to act dumb is :
- I've got inside activity a thread which makes http request and starts media player.
- Once the thread is finished through the listener i've added i begin reproduction of media player
- Once it initializes i inflate my layout with some layout that represents media player( could this be the issue of making drawer dumbs ? )
- Also slide right-to-left (for closing) is not working but clicking outside drawer (on activity layout) closes it normally
This is how i inflate :
DrawerLayout myLayout = (DrawerLayout) findViewById(R.id.cili_test);
View itemInfo1 = getLayoutInflater().inflate(R.layout.player2, myLayout, true);
You need to add break; in your switch case
Like this:
switch (menuItem.getItemId()) {
case R.id.nav_home: {
if (checkInternetConnection(getApplicationContext())) {
Log.i(LOG_TAG, "PROVJERA NETA POVEZAN");
start_loader();
} else {
mEmptyStateTextView.setText("Cannot connect to network, please check your network status and try again.");
// Clear the adapter of previous earthquake data
}
break;
}
case R.id.nav_history: {
pozoviHistory(null);
break;
}
case R.id.nav_favourite: {
pozoviFavorites();
break;
}
}

Android Fragments: Prevent mutiple addToBackStack calls

public void onClick(View v) {
switch(v.getId())
{
case R.id.bAddYourNumber:
FragmentTransaction trans = getFragmentManager().beginTransaction();
trans.replace(R.id.root_frame, new InsertPastNumbersFragment());
trans.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
trans.addToBackStack(TAG);
trans.commit();
break;
}
When the button is pressed, the fragment is replaced and the previous one placed into back stack.
The problem is if the button is pressed several times, the same fragment is placed into backstack multiple times which results in the phone back button having to be pressed x amount of times the button was pressed to go back to the previous fragment.
Is there a way to control this to only add to back stack once?
Give your transaction a tag:
trans.replace(R.id.root_frame, new InsertPastNumbersFragment())
becomes
trans.replace(R.id.root_frame, new InsertPastNumbersFragment(), PAST_NUM_TAG)
Then, before handling the transaction, check:
if (getFragmentManager().findFragmentByTag(PAST_NUM_TAG) == null) {
// Fragment hasn't yet been added, do the transaction
} else {
// Fragment has already been added
}
Alternatively, if you have a button that triggers this, it might make more sense to just disable the button after you add the fragment to prevent the user from being able to press it more than once.

Which method is being called after popBackStack?

I have an activity where I am calling three fragments - each depending on each other:
A(ctivity) -> f1 (Fragment one, title {is|should}: list) -> f2 (Fragment two, title {is|should}: overview) -> f3 (Fragment three, title {is|should}: detail)
ATM I use the following method call to jump backwards:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
FragmentManager fragmentManager = getSupportFragmentManager();
if (fragmentManager.getBackStackEntryCount()>0){
fragmentManager.popBackStack();
}
}
}
This works fine.
I am overriding the ActionBar title in each fragment like this:
ActionBar bar = getSherlockActivity().getSupportActionBar();
bar.setTitle(R.string.title_f3);
When navigating forward (like shown above) this works flawlessly but navigating backwards the title of the ActionBar isn´t updated:
f3 (title {is|should}: detail) -> f2 (title {is}: detail, {should}: overview) -> f1 (title {is}: detail, {should}: list)
Obviously I could just update it again when the fragment is shown. But my debugger never stops in any of the methods I´d except which would be called like onResume().
So is there actually any method being called in a previous fragment after popBackStack() ?
I know this is a bit late for an answer but for anyone who navigates here this might help!
First thing is first: popBackStack()doesn't pop a fragment, it pops a fragment transaction. So the last fragment transaction is reversed upon being called. If you were displaying FragmentA currently and your transaction was:
fragmentTransaction.replace(R.id.your_layout, fragmentB);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
It would replace FragmentA with FragmentB, and add that transaction (not the fragment) to the back stack. If you then hit the back button, it pops the transaction off the back stack, which was "replace this FragmentA with a FragmentB". Essentially, this instruction reverses the last transaction and removes it from the stack of transactions carried out. If the original FragmentA still exists, it uses that one. If it's been destroyed, it makes a new one.
So, if the Fragment hasn't been destroyed, then recalling the fragment after using on popBackStack(), the onStart() and onResume() methods are called. If the Fragment has been destroyed previously, then the lifecycle methods will be called starting from onAttach(). It's the same as pressing the back button on Activities.
Now the important bit, what happens re fragment lifecycle when we pop off back stack? Well as said before the fragment transaction is reversed so:
Scenario 1: Your fragmentB didn't already exist before transaction.
In this case the onCreate() and onAttach() methods are called during the transaction so the fragment will be destroyed and detached if you call popBackStack() and reverse the transaction (Note FragmentA probably already existed so replacing it wont destroy it as we're not undoing a fragment creation). In this case the lifecycle methods will be called starting from onAttach().
Scenario 2: Your fragmentB did already exist before transaction. In this case the fragment won't be destroyed and the next time you access it the onStart() and onResume() methods are called.
This fellow here explains a few things about using popbackstack() http://vinsol.com/blog/2014/09/19/transaction-backstack-and-its-management/ and the fragment lifecycle http://vinsol.com/blog/2014/10/22/fragment-view-state-retention-a-dirty-solution/. The other related posts are worth reading too!
use addOnBackStackChangedListener method in your BaseActivity, which will be called any time backstack changes
getSupportFragmentManager().addOnBackStackChangedListener(
new FragmentManager.OnBackStackChangedListener() {
public void onBackStackChanged() {
FragmentManager fm = getSupportFragmentManager();
if (fm != null) {
int backStackCount = fm.getBackStackEntryCount();
if (backStackCount == 0) {
if (getSupportActionBar() != null) {
getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_menu);
}
setToolbarTittle(R.string.app_name);
} else {
if (getSupportActionBar() != null) {
getSupportActionBar().setHomeAsUpIndicator(R.drawable.back);
}
}
}
}
});
My workaround is to get the current title of the actionbar in the Fragment before setting it to the new title. This way, once the Fragment is popped, I can change back to that title.
#Override
public void onResume() {
super.onResume();
// Get/Backup current title
mTitle = ((ActionBarActivity) getActivity()).getSupportActionBar()
.getTitle();
// Set new title
((ActionBarActivity) getActivity()).getSupportActionBar()
.setTitle(R.string.this_fragment_title);
}
#Override
public void onDestroy() {
// Set title back
((ActionBarActivity) getActivity()).getSupportActionBar()
.setTitle(mTitle);
super.onDestroy();
}
There's also one other good source that you can read through.
I think, the important point is the difference of the transaction you performed on Fragment B. If it's add, then no lifecycle methods are called on Fragment A, if it's replace, you will get some calls on those methods.
I use following workaround:
1) set 1st fragment setHasOptionsMenu(false) before add 2nd fragment on top of 1st one.
2) set 1st fragment setHasOptionsMenu(true) in onOptionsItemSelected() after return from 2nd fragment.
3) onCreateOptionsMenu() of 1st fragment should be called and you can change actionbar here.
But I want to know a better solution.
You can find a nice Fragment lifecycle diagram in the android docs here. So yes, if the fragment is brought to the foreground from the backstack onCreateView(), onActivityCreated(), onStart() and onResume() are called.

Persisting fragment state

I have a frame layout in an activity to which i want to display different fragments inside. I have a sliding drawer with 3 options, each of which lead to a fragment being loaded inside the frame layout. Currently i use the following to accomplish this:
Fragment nextFragment = determineFragmentToSwitchTo(nextFragmentTag);
transaction.replace(R.id.fragment_container, nextFragment);
The first method determines what fragment i need by evaluating the nextFragmentTag string and loading a new fragment like so:
if (fragmentTag.equals(Constants.STUDENTPAGE))
nextFragment = new StudentFragment();
else if (fragmentTag.equals(Constants.TEACHERPAGE))
nextFragment = new TeacherFragment();
else if (fragmentTag.equals(Constants.PARENTPAGE))
nextFragment = new ParentFragment();
Clearly this approach is creating a new fragment each time and running through the whole fragment lifecycle without saving state. So if i am on the student page and scrolling through the student list and i switch to the parent page, when i go back to the student page, it reloads the entire list (i am fetching it from a server) and looses my place in it. How can i get it to persist state and sort of cache that fragment in the manager (if that makes sense)?
You could use the FragmentTransaction's hide(Fragment) and show(Fragment) methods, e.g.:
// In the parent Activity
StudentFragment studentFragment;
TeacherFragment teacherFragment;
ParentFragment parentFragment;
Fragment fragmentOnDisplay;
#Override
protected void onCreate(Bundle savedInstanceState) {
// Initialize fragmentManager, fragmentTransaction, etc.
studentFragment = (StudentFragment) fragmentManager.findFragmentByTag(Constants.STUDENTPAGE);
if (studentFragment == null) {
studentFragment = new StudentFragment ();
fragmentTransaction.add(R.id.your_frame_layout, studentFragment, Constants.STUDENTPAGE);
}
// repeat the same procedure for the other two fragments
// Suppose you want to begin with the teacherFragment on
// display - in that case hide the studentFragment and
// the parentFragment:
fragmentTransaction.hide(studentFragment);
fragmentTransaction.hide(parentFragment);
fragmentOnDisplay = teacherFragment;
fragmentTransaction.commit();
}
Now whenever you need to switch your fragments, simply hide the fragment on display, and show the fragment you need, e.g.:
...
fragmentTransaction.hide(fragmentOnDisplay);
fragmentTransaction.show(parentFragment);
fragmentOnDisplay = parentFragment;
fragmentTransaction.commit();

Categories