I have a navigation drawer setup which on menu item click will replace the fragment container with whichever new fragment is being chosen in the menu:
public boolean onNavigationItemSelected(MenuItem item) {
// Handle navigation view item clicks here.
int id = item.getItemId();
switch (id) {
case R.id.nav_new:
if (subInfo.isEmpty()){
addFragment(new NewFragment(), id);
} else {
addFragment(new SubjectInfoFragment(), id);
}
break;
case R.id.nav_start:
addFragment(new StartFragment(), id);
break;
case R.id.nav_save:
addFragment(new SaveFragment(), id);
break;
case R.id.nav_raw:
addFragment(new RawFragment(), id);
break;
case R.id.nav_accelerometer:
addFragment(new AccelerometerFragment(), id);
break;
case R.id.nav_accelerometerWorld:
addFragment(new AccelerometerWorldFragment(), id);
break;
case R.id.nav_gyroscope:
addFragment(new GyroscopeFragment(), id);
break;
}
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
drawer.closeDrawer(GravityCompat.START);
return true;
}
And the method for adding fragments:
public void addFragment(Fragment fragment, int id) {
if (fragment != null) {
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
if (fragmentManager.getFragments() == null) {
fragmentTransaction.replace(R.id.fragment_container, fragment, fragment.getClass().getSimpleName())
.commit();
} else {
fragmentTransaction.replace(R.id.fragment_container, fragment, fragment.getClass().getSimpleName())
.addToBackStack(Integer.toString(id))
.commit();
}
}
}
In one of my fragments (NewFragment()) there is a data insert form, the id of that menu item is nav_new. When the submit button is clicked, the addFragment method is called to direct the user to another fragment (StartFragment()) in my app:
mainActivity.addFragment(new StartFragment(), R.id.nav_new);
However, the above will add the data insert form to the backstack. Once the user has submitted the form, I don't want them to be able to press back to get back to the submission form. Instead I want to direct them to another fragment (SubjectInfoFragment()) which contains a summary of the information they've entered
I've designed my onNavigationItemSelected method to support this. When the nav_new menu item is clicked, it checks to see whether the user has already entered any info. If they haven't, then a transaction takes place that takes them to NewFragment to enter form info. If they have, SubjectInfoFragment is shown instead to display a summary of their previously entered info
I have to direct them to StartFragment on form submit, but then the old form fragment is added to the backstack, which is undesireable
After form submission, how do I handle fragment transactions such that the back button will take them to a completely new fragment (SubjectInfoFragment) instead of taking them back to the form submission page?
Or in the worst case scenario, if I cant add an entirely new fragment to the backstack, how can I at least prevent users from pressing back to the form submit page?
you should use the following line to add fragment to backstack:
getFragmentManager.addToBackStack(tag or null);
Related
When I open a fragment from BottomNavigationBar, it opens perfectly. When I press back button, previous fragment opens but state of BottomNavigationBar does not change.
As in my screenshots, when i backpressed from Account fragment, Home fragment opens but state of BottomNavigationBar has not been changed.
Screenshot 1 - https://drive.google.com/file/d/12cDvhwO1jpG2A1PUsfHQGProqx2cT6Bp/view
Screenshot 2 - https://drive.google.com/file/d/1Zws5sMJeXxts6k6IEBGUGyJYfZP58Czs/view
btnNavBar.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.action_item1:
loadFragment(fragmentManager, new HomeFragment(), "Home");
break;
case R.id.action_item2:
loadFragment(fragmentManager, new SearchFragment(), "Search");
break;
case R.id.action_item3:
loadFragment(fragmentManager, new AccountFragment(), "Account");
break;
}
return true;
}
});
public static void loadFragment(FragmentManager fragmentManager, Fragment fragment, String tag) {
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.frameLayoutContainer, fragment);
fragmentTransaction.commit();
}
I suggest you to not add the fragments to the back stack because it's not the expected behaviour for the Android users. The bottom bar is here to show 3 to 5 different destinations to the user (for example, a news feed, a user profile, etc.). When you select a tab, you should just change the current fragment (and not create a fragments stack). The back button should only close the app, or re-open the previous screen if there is one (definitely not re-open the previous tab :) ). I suggest you to look at this Material Design guidelines for BottomNavigationBar.
Hi guys here is my code
navigationView.setNavigationItemSelectedListener(new NavigationView
.OnNavigationItemSelectedListener() {
#Override public boolean onNavigationItemSelected(#NonNull MenuItem item) {
if (item.isChecked()) {
//item already selected. Do nothing
drawerLayout.closeDrawer(GravityCompat.START);
return true;
}
switch (item.getItemId()) {
case R.id.home:
FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction();
transaction.replace(R.id.fragment, new HomeFragment())
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE).commit();
break;
case R.id.other:
transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragment, new OtherFragment())
.addToBackStack(null).setTransition(FragmentTransaction
.TRANSIT_FRAGMENT_FADE).commit();
break;
default:
break;
}
drawerLayout.closeDrawer(GravityCompat.START);
return true;
}
});
I basically only have 2 fragments and merely the HomeFragment should be added to the backstack. After switching between HomeFragment and OtherFragment for a while and clicking on the back button while on the HomeFragment, I end up with the HomeFragment getting displayed several times.
Within the HomeFragment lies a recyclerview. When I scroll up and down I can really see that the rows are displayed multiple times.
How can I make sure that the HomeFragment is added to the backstack only once.
Thanks
You could easily check your fragment with
YourFragment.isAdded
And if you have a multiple fragment you could create a new class to manage all the fragment and create state to check if fragment have been added or not.
When you click on the Home menu item try to find out whether there is any fragment in the backstack or not. If yes, call popBackStack() or replace a fragment as you do now otherwise. Use this code: if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
getSupportFragmentManager().popBackStack();
}
I have Fragment A that lets the user access Fragment B by the click of a button, when an action is performed on B, user is redirected to Fragment C to retrieve a last bit of information before returning to A.
(A -> B -> C -> A).
After doing this, if I open my Navigation Drawer and click on the Fragment I am currently on (A), its contents are gone, only the Drawer itself remains.
I can't seem to explain the reason behind this.
Here is how I conserve my Navigation Drawer's Fragment states:
// User clicks on an item in Nav. Drawer, call this method
private Fragment checkFragmentState(int itemId) {
Fragment fragment = null;
switch (itemId) {
// HOME
case R.id.nav_home:
if (home == null) {
fragment = new Home();
home = fragment;
} else
fragment = home;
break;
// SEARCH CARD
case R.id.nav_searchCard:
if (searchCard == null) {
fragment = new SearchCard();
searchCard = fragment;
} else
fragment = searchCard;
break;
}
return fragment;
}
I call this method when my user clicks on an element in the Drawer, basically it checks if the Fragment has already been created, if so, it saves the currently existing one and uses it to be displayed.
What could cause this odd behavior?
Just launch a new instance each time
// User clicks on an item in Nav. Drawer, call this method
private Fragment checkFragmentState(int itemId) {
Fragment fragment = null;
switch (itemId) {
case R.id.nav_home:
fragment = new Home();
break;
case R.id.nav_searchCard:
fragment = new SearchCard();
break;
default:
fragment = new Fragment();
}
return fragment;
}
okay i know there are other questions that on first glance make this one look like a duplicate, but none of these answers work in my case,
What i want is the first fragment displayed to be like a Main Activity in respect to how the back button works, i need whichever fragment i choose from my navigation drawer to go back to the first fragment when the back button is pressed then a user would quit the app by pressing it again.
So ive tried using addToBackStack and when i move to another fragment if i press the back button it comes back to my first fragment (exactly as i want) but pressing the back button again leaves me with a white screen (i wonder if this is due to the transaction animation im using which ive included below) so to get around this i tried overriding the back button and throwing in a call to finish(); but this causes whichever fragment im in to finish instead of going back to the first fragment, ive tried a handful of workarounds from the above mentioned link and many others but cannot find a decent fix any suggestions?
here is my Main Activity displayView
private void displayView(int position) {
// update the main content by replacing fragments
Fragment fragment = null;
switch (position) {
case 0:
fragment = new FirstFragment();
break;
case 1:
fragment = new glideFrag();
break;
case 2:
fragment = new secondGlideFrag();
break;
case 3:
fragment = new thirdGlideFrag();
break;
case 4:
fragment = new forthGlideFrag();
break;
case 5:
fragment = new lgFrag();
break;
case 6:
fragment = new cyanFrag();
break;
case 7:
fragment = new sonyFrag();
break;
case 8:
fragment = new SecondFragment();
break;
default:
break;
}
if (fragment != null) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.setCustomAnimations(R.anim.enter,R.anim.exit,R.anim.pop_enter,R.anim.pop_exit);
//fragmentManager.beginTransaction()
fragmentTransaction.replace(R.id.frame_container, fragment).addToBackStack("first Fragment").commit();
// update selected item and title, then close the drawer
mDrawerList.setItemChecked(position, true);
mDrawerList.setSelection(position);
setTitle(navMenuTitles[position]);
mDrawerLayout.closeDrawer(mDrawerList);
} else {
// error in creating fragment
Log.e("MainActivity", "Error in creating fragment");
}
i found this that looks like a great way around it
private boolean popNext = false;
if(popNext){
if(position == INITIAL_POSITION){
onBackPressed();
mDrawerLayout.closeDrawer(mDrawerList);
popNext = false;
return;
}
getSupportFragmentManager().popBackStackImmediate();
}
else{
if(position == INITIAL_POSITION){
mDrawerLayout.closeDrawer(mDrawerList);
return;
}
popNext=true;
}
but im still fairly new to android and im not sure what to set INITIAL_POSITION to, I tried
private static final INITIAL_POSITION = 0;
but without any luck
When adding the initial fragment, you must not add it to the back stack.
You must only do it for the next ones. When the back stack will be empty, the Activity will just finish.
Edit: Here is an explanation of the problem so you can figure out how to fix it:
Each time you add a fragment transaction to the back stack, you allow the user to revert it by pressing the back button and the Activity will return to the state it was before the transaction. If the initial fragment is added to the back stack, then when the user press back, the screen becomes blank, because there was nothing displayed before you added the initial fragment. That's why the initial fragment transaction which adds the first visible fragment to your Activity must not be added to the back stack. Usually you initialize the initial fragment like this:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState == null) {
Fragment fragment = new FirstFragment();
getSupportFragmentManager().beginTransaction()
.add(R.id.frame_container, fragment)
.commit();
}
}
BladeCoders answer was more trying to tell me how the backstack works rather than answering my question, i ended up not adding any fragments to the back stack, .addToBackStack(null), and overriding back button in MainActivity, feels like a little bit of a hack but works perfectly
#Override
public void onBackPressed() {
FragmentManager fragmentManager = getSupportFragmentManager();
if (fragmentManager.getBackStackEntryCount() < 1){
Fragment fragment = new FirstFragment();
FragmentTransaction fragmentTransaction =
getSupportFragmentManager().beginTransaction();
fragmentTransaction.setCustomAnimations(R.anim.enter, R.anim.exit,
R.anim.pop_enter, R.anim.pop_exit);
getSupportActionBar().setTitle(mDrawerTitle);
fragmentTransaction.replace(R.id.frame_container,
fragment).addToBackStack("first").commit();
}else{
finish();
}
}
You can do it even with out backstack its just my point of view to simplify so that it can help some one.
#Override
public void onBackPressed(){
Fragment f = getSupportFragmentManager().findFragmentById(R.id.container_body);
if(f.getClass().getName().equals(HomeFragment.class.getName())){ // here HomeFragment.class.getName() means from which faragment you actually want to exit
finish();
}else{
displayView(0); //were you want to go when back button is pressed
}
}
private void displayView(int position) {
Fragment fragment = null;
String title = getString(R.string.app_name);
switch (position) {
case 0:
fragment = new HomeFragment();
title = getString(R.string.app_name);
break;
case 1:
fragment = new OffersFragment();
title = getString(R.string.nav_item_offers);
break;
case 2:
fragment = new NotificationFragment();
title = getString(R.string.nav_item_notifications);
break;
default:
break;
}
if (fragment != null) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.container_body, fragment);
fragmentTransaction.commit();
// set the toolbar title
getSupportActionBar().setTitle(title);
}
}
I have 4 fragment in my android app :
let's say A B C D where they 4 are fragments called in 1 activity. When I go from A to B and from be to C I want the back button to bring me to B and to A if I press again. But instead the back button forwards me to the previous activity.
I've read that the back button need to explicitely be handled as for fragments and I saw that code :
FragmentTransaction tx = fragmentManager.beginTransation();
tx.replace( R.id.fragment, new MyFragment() ).addToBackStack( "tag" ).commit();
fragment.getView().setFocusableInTouchMode(true);
fragment.getView().setOnKeyListener( new OnKeyListener()
{
#Override
public boolean onKey( View v, int keyCode, KeyEvent event )
{
if( keyCode == KeyEvent.KEYCODE_BACK )
{
return true;
}
return false;
}
} );
The problem is that I'm not using any FragmentTransaction. Here is how I call my Fragments :
private void displayView(int position) {
Fragment fragment = null;
switch (position) {
case 0:
fragment = new HomeFragment();
break;
/*case 1:
fragment = new ContactsFragment();
break;*/
case 2:
fragment = new ContactsFragment();
break;
case 3:
fragment = new SitesFragment();
break;
default:
break;
}
if (fragment != null) {
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.frame_container, fragment).commit();
}
How can I use that back stacking with my code please ?
Thank you for your help in advance !
By calling .beginTransaction(), you are actually using a FragmentTransaction. The type itself is just not explicitly mentioned in your example. You can simply call addToBackStack() on the transaction to add the transaction to the fragment manager's back stack:
fragmentManager.beginTransaction()
.replace(R.id.frame_container, fragment).addToBackStack(null).commit();
Edit:
As Larry pointed out, adding to the fragment manager's back stack will automatically handle going back if your structure is as simple as A>B>C>D. If, for any reason, you need to manually handle the behavior of popping the backstack, you can use these instructions below:
In your activity, override onBackPressed() and call getFragmentManager().popBackStack(); Note: that you will need to know which fragment you are currently displaying so that you can safely call popBackStack on B, C, and D but if you are on A, you will want to (most likely) exit the activity. (Alternatively, you could only pop the back stack if there are items in it. For that, check if (getFragmentManager().getBackStackEntryCount() > 0)
Here's how your full fragment transaction code should look:
// the tag here is if you need to know which fragment is loaded, later
fragmentManager.beginTransaction()
.replace(R.id.frame_container, fragment, tag).addToBackStack(null).commit();
Then, the back handler:
#Override
public void onBackPressed()
{
if (someCriteriaToDetermineIfBackStackShouldBePopped)
{
getFragmentManager().popBackStack();
return;
}
// this fragment is A (or something else) - let the parent handle the back press
// which will finish the activity
super.onBackPressed();
}
If you need help detecting which fragment is currently loaded, this should get you started: get currently displayed fragment
You need to use the FragmentTransaction to add/replace the new fragment, add it to the back stack, then commit. You do not need to handle the BACK button yourself unless you wish to override the default behavior. Adding it to the back stack in the FragmentTrasnaction before you commit will automatically pick up the desired BACK button support.