fragment can not go back to previous state when press Back button - android

Hi I have a app that follow as follow: MainActivity use navigationDrawer to navigate between fragment for example fragment1, fragment2, fragment3. Fragment1 Contain tablayout fragment1-A and child fragment1-B and fragment1-C. when user in fragment1-B click a button, they will go to another fragment1-B-a (this transaction will be add to backstack). When user press backButton in fragment1-B-a. They will go back to fragment1-B by these code in the MainActivity.
#Override
public void onBackPressed() {
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
} else {
// if got BackStack, come back to it
if (fragmentManager.getBackStackEntryCount()>0) {
super.onBackPressed();
// if not come to home screen
}else {
if (!homeFlag) {
fragment = new MainFragment();
fragmentManager.popBackStack();
fragmentManager.beginTransaction().replace(R.id.contentMainDrawer, fragment).commit();
homeFlag = true;
} else exit();
}
}
}
But when user inside fragment1-B-a (2nd tablayout fragment) if they press BackButton. It give the error and crash the app (inside fragment1-B-a press backButton work normally)
this is the error. Anyhelp is much appreciate. Thanks.
12-31 12:20:22.118 9719-9719/victory1908.nlbstafflogin2 E/AndroidRuntime: FATAL EXCEPTION: main
Process: victory1908.nlbstafflogin2, PID: 9719
java.lang.IndexOutOfBoundsException: Invalid index 1, size is 0
at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:255)
at java.util.ArrayList.get(ArrayList.java:308)
at android.support.design.widget.TabLayout.getTabAt(TabLayout.java:448)
at android.support.design.widget.TabLayout$TabLayoutOnPageChangeListener.onPageSelected(TabLayout.java:1759)
at android.support.v4.view.ViewPager.dispatchOnPageSelected(ViewPager.java:1794)
at android.support.v4.view.ViewPager.setCurrentItemInternal(ViewPager.java:548)
at android.support.v4.view.ViewPager.setCurrentItemInternal(ViewPager.java:514)
at android.support.v4.view.ViewPager.onRestoreInstanceState(ViewPager.java:1320)
at android.view.View.dispatchRestoreInstanceState(View.java:14564)
at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3157)
at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3163)
at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3163)
at android.view.View.restoreHierarchyState(View.java:14542)
at android.support.v4.app.Fragment.restoreViewState(Fragment.java:468)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1094)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1248)
at android.support.v4.app.BackStackRecord.popFromBackStack(BackStackRecord.java:958)
at android.support.v4.app.FragmentManagerImpl.popBackStackState(FragmentManager.java:1666)
at android.support.v4.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.java:586)
at android.support.v4.app.FragmentActivity.onBackPressed(FragmentActivity.java:169)
at victory1908.nlbstafflogin2.MainActivity.onBackPressed(MainActivity.java:134)
at android.app.Activity.onKeyUp(Activity.java:2550)
at android.view.KeyEvent.dispatch(KeyEvent.java:3159)
at android.app.Activity.dispatchKeyEvent(Activity.java:2805)
at android.support.v7.view.WindowCallbackWrapper.dispatchKeyEvent(WindowCallbackWrapper.java:50)
at android.support.v7.app.AppCompatDelegateImplBase$AppCompatWindowCallbackBase.dispatchKeyEvent(AppCompatDelegateImplBase.java:224)
at android.support.v7.view.WindowCallbackWrapper.dispatchKeyEvent(WindowCallbackWrapper.java:50)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:2429)
at android.view.ViewRootImpl$ViewPostImeInputStage.processKeyEvent(ViewRootImpl.java:4508)
at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4463)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4020)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4073)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4039)
at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4153)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4047)
at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4210)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4020)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4073)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4039)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4047)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4020)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4073)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4039)
at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4186)
at android.view.ViewRootImpl$ImeInputStage.onFinishedInputEvent(ViewRootImpl.java:4347)
at android.view.inputmethod.InputMethodManager$PendingEvent.run(InputMethodManager.java:2480)
at android.view.inputmethod.InputMethodManager.invokeFinishedInputEventCallback(InputMethodManager.java:2074)
at android.view.inputmethod.InputMethodManager.finishedInputEvent(InputMethodManager.java:2065)
at android.view.inputmetho

Store fragment name is public static variable lastFragment...
update it whenever moving to new fragment by setting its previous fragment ....
make it null in main activity ...
-exit app if it is null otherwise replace fragmentmanger with lastFragment.

Ok. I got your problem. This is because getBackStackEntryCount() is returning Zero.
So you need to add below lines after committing your fragment onBackPressed Button.
fragmentManager.executePendingTransactions();
and add
fragmentManager(null, FragmentManager.POP_BACK_STACK_INCLUSIVE) instead of fragmentManager.popBackStack();
FragmentManager.POP_BACK_STACK_INCLUSIVE) is usedto pop the entire back stack.
See this discussion
Editing in your code :
#Override
public void onBackPressed() {
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
} else {
// if got BackStack, come back to it
if (fragmentManager.getBackStackEntryCount()>0) {
super.onBackPressed();
// if not come to home screen
}else {
if (!homeFlag) {
fragment = new MainFragment();
fragmentManager(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)// Change
fragmentManager.beginTransaction().replace(R.id.contentMainDrawer, fragment).commit();
fragmentManager.executePendingTransactions();// Change
homeFlag = true;
} else exit();
}
}
}
Hope it help you.

Related

Back navigation from PreferenceFragment

I handle my back navigation like such:
When creating / committing a Fragment:
getSupportFragmentManager()
.beginTransaction()
.addToBackStack("ToDoFragment")
.replace(R.id.content_frame, fragmentR, fragmentR.getClass().getSimpleName())
.commit();
When catching a back button press:
#Override
public void onBackPressed() {
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
// If navigation drawer is open, close it
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
} else {
super.onBackPressed();
setDrawerState(true);
displayMenu(true);
}
}
This works fine for my Fragments, but I wish to do the same for my PreferenceFragment, that I call like such:
getFragmentManager().beginTransaction()
.add("ToDoFragment")
.replace(R.id.content_frame, new SettingsFragment()).commit();
But when pressing my back nav. button it does not bring me back to the previous Fragment, instead it overlaps my Settings view and my previous Fragment.
What am I missing / not understanding?
EDIT:
I have added getFragmentManager().popBackStackImmediate(); to my onBackPressed() method, I do not have any view overlapping, but when going into my settings and pressing back, the Fragment is empty.
Using a PreferenceFragment you canĀ“t override onBackPressed() in this case use onDetach() :
#Override
public void onDetach() {
super.onDetach();
//Do your process here!
}
Instead of using 'add' in manager use 'addtoBackStack' Because add retains the existing fragments and adds a new fragment which means existing fragment will be active. Hence back button is not called for existing fragment.
getFragmentManager().beginTransaction()
.addToBackStack("ToDoFragment")
.replace(R.id.content_frame, new SettingsFragment()).commit();

reset addToBackStack() on MainActivity

I am building an android application using default Navigation Drawer layout and Fragment to switch from one layout to another.
The layout sequence is like
Dashboard -> Settings -> Profile Settings
MailActivity.class loads main_activity.xml which containes a frameLayout to contain fragment
and Dashboard loads ActivityDashboard Fragment.
Now, when I switch from Dashboard to Settings and then Profile Settings. onBackPress works fine. But after on Profile Settings, I selected Dashboard from Navigation Drawer to go back to the home screen. Then pressing back key follows the stack
Dashboard -> Profile Settings -> Settings -> Dashboard
The code for Navigation Drawer is
#SuppressWarnings("StatementWithEmptyBody")
#Override
public boolean onNavigationItemSelected(MenuItem item) {
// calling the method displaySelectedScreen()
displaySelectedScreen(item.getItemId());
return true;
}
private void displaySelectedScreen(int itemId) {
// creating Fragment object
Fragment fragment = null;
// initializing fragment
switch (itemId) {
case R.id.nav_dashboard:
fragment = new ActivityDashboard();
break;
case R.id.nav_calendar:
fragment = new ActivityCalendar();
break;
case R.id.nav_history:
fragment = new ActivityHistory();
break;
case R.id.nav_about:
fragment = new ActivityAbout();
break;
case R.id.nav_settings:
fragment = new ActivitySettings();
break;
}
// replacing the fragment
if (fragment != null) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, fragment);
ft.addToBackStack(null);
ft.commit();
}
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
drawer.closeDrawer(GravityCompat.START);
}
I'm using same FragmentTransaction code to switch from one fragment to another in ActivityDashboard, ActivitySettings fragment classes.
Q 1. How to flush the addBackToStack when the ActivityDashboard is loaded so that user have only option to exit app ?
Q 2. How to get back to ActivityDashboard, when layout is changed from Navigation Drawer irespective of which fragment is replaced ?
i.e., When user is on ActivitySettings fragment and changed to ActivityHistory from Navigation Drawer, then on backPress, user must be tracked following
ActivityHistory -> ActivityDashboard
instead of
ActivityHistory -> ActivitySettings -> ActivityDashboard
onBackPressed() method on MainActivity.class is
#Override
public void onBackPressed() {
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
} else {
super.onBackPressed();
}
}
Simply call
fragmentManager.popBackStack(stackName /* null if you did not supply a stack name while creating your fragments */, FragmentManager.POP_BACK_STACK_INCLUSIVE);
inside your ActivityDashboard

clear fragment history in drawer

In my application, I have 5 fragments and the requirement is that on any given fragment, if back button is pressed, it should exit out of the application. In other words the fragments does not have any order and are independent. SO pressing the back button on any fragment will actually close the app itself.
In some fragments, I am opening other fragments using this code:
FragmentManager fm = getActivity().getSupportFragmentManager();
fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
track nextFrag= new track();
login.this.getFragmentManager().beginTransaction()
.replace(R.id.content_frame, nextFrag, null)
.addToBackStack(null)
.commit();
However the history is still maintained, What can I do to clear the history on start of any fragment.
EDIT:
Tried this and the application does exit on pressing back, however I can still see a glimpse of previous activity and it throws some nasty exceptions.
#Override
public void onBackPressed() {
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
} else {
super.onBackPressed();
finish();
}
}

Navigation DrawerLayout button never changes to back button and won't navigate

I am using a DrawerLayout with and adding a new Fragment each time the user taps an item from the drawer menu. This works perfectly.
Within one of those fragments the user can navigate to another fragment:
private void loadArchives() {
PUCNewsArchive news_archive = new PUCNewsArchive();
getFragmentManager()
.beginTransaction()
.add(R.id.container_frame, news_archive, "news_archives")
.addToBackStack(null)
.commit();
}
This will correctly add another fragment to the stack. BUT, it does not change the action bar drawer button/icon to a back button. So, in tapping that just opens the drawer again, which is should not do. The only way to navigate back is the use the hardware back button which does go back to the previous fragment. I tried adding the following to my main Activity to see what was going on:
#Override
public void onBackPressed(){
FragmentManager fm = getFragmentManager();
if (fm.getBackStackEntryCount() > 0) {
Log.i("MainActivity", "popping backstack");
fm.popBackStack();
} else {
Log.i("MainActivity", "nothing on backstack, calling super");
super.onBackPressed();
}
}
But that is only ever called when I use the hardware back button, as the drawer button is only ever opening the drawer.
I assume this should be checking to see if it should open the drawer or navigate:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
if (mDrawerToggle.onOptionsItemSelected(item)) {
return true;
}
switch(item.getItemId()) {
case android.R.id.home:
return true;
default:
return super.onOptionsItemSelected(item);
}
}
What have I missed? Why is the drawer button never changing to a back button nor acting like one?
You don't need to use the onBackPressed().
I had a very similar situation in my app. Here is how I got it to work:
I created a method in the main activity:
public void foo(int fragment_id) {
if(fragment_id == some_id){
// keep the drawer carat
mDrawerToggle.setDrawerIndicatorEnabled(true);
}else{
// Disable the drawer carat, and enable the back button
mDrawerToggle.setDrawerIndicatorEnabled(false);
getActionBar().setDisplayHomeAsUpEnabled(true);
}
}
I call this method from onResume() of each fragment, and pass it that particular fragments id. Alternatively, you can call it from the loadFragment()/loadArchives() method in your main activity. This works fine for me.
In your Navigation Activity
This activity controls all fragments in the app.
When preparing new fragments to replace others, I set the DrawerToggle
setDrawerIndicatorEnabled(false) like this:
private void loadArchives() {
PUCNewsArchive news_archive = new PUCNewsArchive();
//disable the toggle menu and show up carat
theDrawerToggle.setDrawerIndicatorEnabled(false);
getFragmentManager()
.beginTransaction()
.add(R.id.container_frame, news_archive, "news_archives")
.addToBackStack(null)
.commit();
}
in onCreateview of another fragment
call
// update the actionbar to show the up carat/affordance
getActivity().getActionBar().setDisplayHomeAsUpEnabled(true);
For More reference Check This Question
I tried above suggested codes But the "up" or "Back" arrow in left side of action bar was just not showing up in my app. But luckily I was able to fix that.
Brief code:
Function to start fragment while putting the current fragment to Back Stack:
public void startFragment(){
MyFrag myFrag = new MyFrag();
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.frag_container ,myFrag)
.addToBackStack(null)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.commit();
}
Function to turn ON or OFF "NavigationDrawer":
public void setNavigationDrawerState(boolean isEnabled) {
if ( isEnabled ) {
drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
toggle.setDrawerIndicatorEnabled(true);
toggle.syncState();
}
else {
drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
toggle.setDrawerIndicatorEnabled(false);
toggle.setHomeAsUpIndicator(R.drawable.ic_keyboard_backspace_white_24dp);
toggle.syncState();
}
I downloaded the drawable: ic_keyboard_backspace_white_24dp from Material.io
The complete answer:
Disabling navigation drawer, toggling home-button/up-indicator in fragments

In Fragment on back button pressed Activity is blank

I have an Activity and many fragments inflated in same FrameLayout
<FrameLayout
android:id="#+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent" />
example: mainActivity > any fragment (press back button) > activity is blank.
In onCreate:
layout = (FrameLayout)findViewById(R.id.content_frame);
layout.setVisibility(View.GONE);
When I start a fragment:
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, profileFragment);
ft.addToBackStack(null);
ft.commit();
layout.setVisibility(View.VISIBLE);
I suppose I need to make the frameLayout's visibility GONE again on back pressed, but how do I do this?
I tried onBackPressed and set layout.setVisibility(View.GONE); but I cannot go back through fragments, as I go directly to main page.
If you have more than one fragment been used in the activity or even if you have only one fragment then the first fragment should not have addToBackStack defined. Since this allows back navigation and prior to this fragment the empty activity layout will be displayed.
// fragmentTransaction.addToBackStack() // dont include this for your first fragment.
But for the other fragment you need to have this defined otherwise the back will not navigate to earlier screen (fragment) instead the application might shutdown.
#Override
public void onBackPressed() {
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
}
else {
int fragments = getSupportFragmentManager().getBackStackEntryCount();
if (fragments == 1) {
finish();
} else if (getFragmentManager().getBackStackEntryCount() > 1) {
getFragmentManager().popBackStack();
} else {
super.onBackPressed();
}
}
}
To add a fragment
getSupportFragmentManager().beginTransaction()
.replace(R.id.layout_main, dashboardFragment, getString(R.string.title_dashboard))
.addToBackStack(getString(R.string.title_dashboard))
.commit();
Sorry for the late response.
You don't have to add ft.addToBackStack(null); while adding first fragment.
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, profileFragment);
// ft.addToBackStack(null); --remove this line.
ft.commit();
// ... rest of code
If you want to track by the fragments you should override the onBackPressed method, like this
public void onBackPressed() {
if (getFragmentManager().getBackStackEntryCount() == 1) {
finish();
} else {
super.onBackPressed();
}
}
You can override onBackPressed and check to see if there is anything on the backstack.
#Override
public void onBackPressed() {
int fragments = getFragmentManager().getBackStackEntryCount();
if (fragments == 1) {
// make layout invisible since last fragment will be removed
}
super.onBackPressed();
}
Just don't add the first fragment to back stack
Here is the Kotlin code that worked for me.
val ft = supportFragmentManager.beginTransaction().replace(container, frag)
if (!supportFragmentManager.fragments.isEmpty()) ft.addToBackStack(null)
ft.commit()
On a recent personal project, I solved this by not calling addToBackStack if the stack is empty.
// don't add the first fragment to the backstack
// otherwise, pressing back on that fragment will result in a blank screen
if (fragmentManager.getFragments() != null) {
transaction.addToBackStack(tag);
}
Here's my full implementation:
String tag = String.valueOf(mCurrentSectionId);
FragmentManager fragmentManager = mActivity.getSupportFragmentManager();
Fragment fragment = fragmentManager.findFragmentByTag(tag);
if (fragment != null) {
// if the fragment exists then no need to create it, just pop back to it so
// that repeatedly toggling between fragments doesn't create a giant stack
fragmentManager.popBackStackImmediate(tag, 0);
} else {
// at this point, popping back to that fragment didn't happen
// So create a new one and then show it
fragment = createFragmentForSection(mCurrentSectionId);
FragmentTransaction transaction = fragmentManager.beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.replace(R.id.main_content, fragment, tag);
// don't add the first fragment to the backstack
// otherwise, pressing back on that fragment will result in a blank screen
if (fragmentManager.getFragments() != null) {
transaction.addToBackStack(tag);
}
transaction.commit();
}
irscomp's solution works if you want to end activity when back button is pressed on first fragment. But if you want to track all fragments, and go back from one to another in back order, you add all fragments to stack with:
ft.addToBackStack(null);
and then, add this to the end of onCreate() to avoid blank screen in last back pressed; you can use getSupportFragmentManager() or getFragmentManager() depending on your API:
FragmentManager fm = getSupportFragmentManager();
fm.addOnBackStackChangedListener(new OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
if(getSupportFragmentManager().getBackStackEntryCount() == 0) finish();
}
});
Final words: I don't suggest you to use this solution, because if you go from fragment1 to fragment 2 and vice versa 10 times, when you press back button 10 times it will do it in back order which users will not want it.
Almost same as Goodlife's answer, but in Xamarin.Android way:
Load fragment (I wrote helper method for that, but it's not necessary):
public void LoadFragment(Activity activity, Fragment fragment, string fragmentTitle = "")
{
var fragmentManager = activity.FragmentManager;
var fragmentTransaction = fragmentManager.BeginTransaction();
fragmentTransaction.Replace(Resource.Id.mainContainer, fragment);
fragmentTransaction.AddToBackStack(fragmentTitle);
fragmentTransaction.Commit();
}
Back button (in MainActivity):
public override void OnBackPressed()
{
if (isNavDrawerOpen()) drawerLayout.CloseDrawers();
else
{
var backStackEntryCount = FragmentManager.BackStackEntryCount;
if (backStackEntryCount == 1) Finish();
else if (backStackEntryCount > 1) FragmentManager.PopBackStack();
else base.OnBackPressed();
}
}
And isNavDrawerOpen method:
bool isNavDrawerOpen()
{
return drawerLayout != null && drawerLayout.IsDrawerOpen(Android.Support.V4.View.GravityCompat.Start);
}
I still could not fix the issue through getBackStackEntryCount() and I solved my issue by making the main page a fragment too, so in the end I have an activity with a FrameLayout only; and all other fragments including the main page I inflate into that layout. This solved my issue.
I had the same problem when dealing with Firebase's Ui Login screen. When back button was pressed it left a blank screen.
To solve the problem I just called finish() in my onStop() method for said Activity. Worked like a charm.
If you have scenario like me where a list fragment opens another details fragment, and on back press you first need to show the list fragment and then get out the whole activity then, addToBackStack for all the fragment transactions.
and then on the activity, do like this (courtesy: #JRomero's answer, #MSaudi's comment)
#Override
public void onBackPressed() {
int fragments = getFragmentManager().getBackStackEntryCount();
if (fragments == 1) {
// make layout invisible since last fragment will be removed
}
super.onBackPressed();
}
Just Comment or Remove transaction.addToBackStack(null) in your code.Below is code to change fragment in kotlin.
fun ChangeFragment(activity: MainActivity, fragment: Fragment) {
val transaction: FragmentTransaction =
activity.getSupportFragmentManager().beginTransaction()
transaction.replace(R.id.tabLayoutContainer, fragment)
transaction.commit()
}

Categories

Resources