Handling hidden and shown fragments onbackpressed - android

I have an activity which hosts three Fragment's and I can switch between Fragment's using BottomNavigationView.The mechanism that I use to switch between Fragment's is using show and hide functions of FragmentTransaction instead of replace function of FragmentTransaction. I am doing so because I want some network operations to be done only once and also to inflate Layout only once.
The problem that I am facing using this mechanism is that when I start another Activity from any of the Fragment and then hit the back button the selectedItem of the BottomNavigationView and the Fragment shown are mismatching.
I was able to solve this problem though but I feel it has less efficiency. The procedure was that whenever I clicked a tab in BottomNavigation while switching Fragment's I gave it some predecided number and saved in a static variable(X) and whenever I clicked back button in the OnResume() method of the hosting activity I made a switch-case block using X to know which Fragment was visible before starting the new Activity and then finally making three FragmentTransaction's to show and hide required Fragment's.
protected void onResume() {
super.onResume();
if(selectedId!=63){
switch(selectedId){
case 0:if(bottomNavigationView.getSelectedItemId()==R.id.navigation_home){handleHomeFragmentVisibility();}
break;
case 1:if(bottomNavigationView.getSelectedItemId()==R.id.navigation_dashboard)
{handleDashboardFragmentVisibility();}
break;
case 2:if(bottomNavigationView.getSelectedItemId()==R.id.navigation_notifications)
{handleNotificationFragmentVisibility();}
break;
}
}
I feel using three FragmentTransaction's is costly and I was looking for some efficient way. Can you tell me one if you know ?
public void handleHomeFragmentVisibility(){
FragmentManager fragmentManager= getSupportFragmentManager();
if (fragmentManager.findFragmentByTag("home") != null) {
//if the fragment exists, show it.
fragmentManager.beginTransaction().show(fragmentManager.findFragmentByTag("home")).commit();
} else {
//if the fragment does not exist, add it to fragment manager.
Log.e(TAG,"homeFragmentAdded");
fragmentManager.beginTransaction().add(R.id.container, new HomeFragment(), "home").commit();
}
if (fragmentManager.findFragmentByTag("dashboard") != null) {
//if the other fragment is visible, hide it.
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag("dashboard")).commit();
}
if (fragmentManager.findFragmentByTag("requests") != null) {
//if the other fragment is visible, hide it.
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag("requests")).commit();
}
}
public void handleDashboardFragmentVisibility(){
FragmentManager fragmentManager= getSupportFragmentManager();
if (fragmentManager.findFragmentByTag("dashboard") != null) {
//if the fragment exists, show it.
fragmentManager.beginTransaction().show(fragmentManager.findFragmentByTag("dashboard")).commit();
} else {
//if the fragment does not exist, add it to fragment manager.
fragmentManager.beginTransaction().add(R.id.container, new DashboardFragment(), "dashboard").commit();
}
if (fragmentManager.findFragmentByTag("home") != null) {
//if the other fragment is visible, hide it.
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag("home")).commit();
}
if (fragmentManager.findFragmentByTag("requests") != null) {
//if the other fragment is visible, hide it.
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag("requests")).commit();
}
}
public void handleNotificationFragmentVisibility(){
FragmentManager fragmentManager= getSupportFragmentManager();
if (fragmentManager.findFragmentByTag("requests") != null) {
//if the fragment exists, show it.
fragmentManager.beginTransaction().show(fragmentManager.findFragmentByTag("requests")).commit();
} else {
//if the fragment does not exist, add it to fragment manager.
fragmentManager.beginTransaction().add(R.id.container, new NotificationFragment(), "requests").commit();
}
if (fragmentManager.findFragmentByTag("home") != null) {
//if the other fragment is visible, hide it.
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag("home")).commit();
}
if (fragmentManager.findFragmentByTag("dashboard") != null) {
//if the other fragment is visible, hide it.
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag("dashboard")).commit();
}
}
bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
FragmentManager fragmentManager = getSupportFragmentManager();
switch (item.getItemId()) {
case R.id.navigation_home:
selectedId=0;
handleHomeFragmentVisibility();
break;
case R.id.navigation_dashboard:
selectedId=1;
handleDashboardFragmentVisibility();
break;
case R.id.navigation_notifications:
selectedId=2;
handleNotificationFragmentVisibility();
break;
}
return true;
}
});

A first note on your code: Avoid boilerplate! Write only one method instead of three and use a signature of the type handleFragmentVisibility(String show, String hide1, String hide2, int container). In case the fragment to be shown is null, instantiate it by testing for show, something like:
Fragment newFragment = (show == "home") ? new HomeFragment() : (show == "dashboard") ? new DashboardFragment() : new NotificationFragment();
However, none of your fragments should ever get null through hiding (please check for yourself), since you don't remove them from your activity or replace them with other fragments. Instead of using show and hide you could also use attach and detach, both sets of methods keep state. I don't see an efficiency problem and you do indeed need to call three FragmentTransactions. It only can be done with less code:
public void handleFragmentVisibility(String show, String hide1, String hide2){
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction().show(fragmentManager.findFragmentByTag(show)).commit();
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag(hide1)).commit();
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag(hide2)).commit();
}
Please note, that although this method keeps the state of the fragment while hiding or detaching them, other events like orientation change still make it necessary that you take care of saving state in onSaveInstanceState(Bundle savedInstanceState).

Related

Fragments disappears after there are another heavy applications started

I have Activity with five fragments which switch by pressing button in BottomNavigationView:
bottomNavigationView.setOnNavigationItemSelectedListener(item -> {
switch (item.getItemId()) {
case R.id.first:
replaceFragment(FIRST_TAG);
return true;
case R.id.other:
// last four fragments
default:
return false;
}
});
in replaceFragment:
private void replaceFragment(#NonNull String tag) {
if (!checkFragment(tag)) {
addFragment(getFragmentByTag(tag), tag);
}
showFragment(tag);
}
сheckFragment:
private boolean checkFragment(String tag) {
return fm.findFragmentByTag(tag) != null;
}
addFragment:
private void addFragment(Fragment fragment, String tag) {
if (!checkFragment(tag)) {
fm.beginTransaction().add(R.id.frame, fragment, tag).hide(fragment).commit();
}
}
showFragment:
private void showFragment(String tag) {
// currentFragmentTag defines as FIRST_TAG in fields block
currentFragment = fm.findFragmentByTag(currentFragmentTag);
FragmentTransaction transaction = fm.beginTransaction();
if (currentFragment != null) {
transaction.hide(currentFragment);
}
transaction
.attach(getFragmentByTag(tag))
.show(getFragmentByTag(tag))
.commit();
currentFragmentTag = tag;
}
This was done to avoid load data in fragments every time when fragment is shown! All fragments added to FragmentManager only once and later there are only calls hide(previousFragment) and show(nextFragment).
This works well!
Before app goes to background and new heavy app is launched. In background activity of my app is destroyed and when I return to app there is only BottomNavigationView and empty display.
I checked in debug. And FragmentManaget.getActiveFragments() returns list of five fragments.
So, Activity is restored, FragmentManager is restored, FragmentManager contains fragment.
Why fragments not shown on display?
After skimming through your code what I can think of, is to set one of the five fragments as a default fragment to be displayed when the app runs.
Try something like:
bottomNavigationView.getMenu().findItem(R.id.first).setChecked(true);
Do this inside onCreate() or onResume();
Good luck!

Android coming from camera loses fragment view

I have implemented a formular which has some steps. Every step is in a new Fragment using a FrameLayout.
In the last step (FRAGMENT A) there is a Button. When this Button is clicked a new Fragment (FRAGMENT B) and the Camera Intent are launched.
Sometimes after the photo is taken and the Camera is dismissed, the FRAGMENT A is shown instead of the FRAGMENT B. When this happens, the UI elements are frozen, any field is clikable and the only way to continue using the app is closing the Form and start the process again.
I thought it was a OOM error so the Activity gets killed / restored and the last state wasn´t correctly stored.
I've tried to check that but the method onRestoreInstanceState() is not called. Also methods from the FRAGMENT B are called after the camera is closed whether it is shown or not. This is the code I called to open the FRAGMENT B and the camera:
BASE FRAGMENT
private void setCurrentFragment(int pos, boolean animate) {
showFragment(buildFragment(mCurrentPage), animate, animationFromRight);
....
}
private Fragment buildFragment(int pos) {
switch (mHasFamilyManager ? pos : pos + 1) {
............
case 3:
mCurrentFragment = PhotoVerificationFragment.newInstance();
if (mState.getAttachments().isEmpty()) {
switch (mState.getPictureSelector()) {
.....
case CAMERA:
onAddCameraPictureClicked();
break;
}
.....
return mCurrentFragment;
}
private void showFragment(final Fragment fragment, boolean animate,
final boolean animationFromRight) {
if (fragment != null) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
if (animate) {
if (animationFromRight) {
transaction.setCustomAnimations(R.anim.activity_slide_in_right,
R.anim.activity_slide_out_left);
} else {
transaction.setCustomAnimations(R.anim.activity_slide_in_left,
R.anim.activity_slide_out_right);
}
}
transaction.addToBackStack(null);
transaction.replace(R.id.container, fragment);
transaction.commit();
}
}
MainActivity
public void onAddCameraPictureClicked() {
startActivityForResult(takePictureIntent, requestCode);
.....
}
Does anybody have an idea? Thanks in advance!
Please try this:
transaction.add(R.id.container, fragment);
transaction.disallowAddToBackStack();
transaction.commit();
By using replace you are replacing fragments on top of each other, so it does not remove the previous fragment or it is adding the new fragment below the current one.
Using add you make sure that it will be always on top.
disallowAddToBackStack will make sure that you are not holding anything in the stack.

fragments sometimes overlap - Android

I have a navigation drawer and clicking on items shows/hides/creates full screen fragments.
For the most part, this code works great. But sometimes, maybe 1% of the time, I will get crazy full screen fragment overlapping when opening the app while it has already been running.
Is the problem with my code..? Or maybe something else in Android where it does not recognize I have the fragments with the tags already created?
Here is the relevant code for how I show/hide/create fragments:
#SuppressWarnings("StatementWithEmptyBody")
#Override
public boolean onNavigationItemSelected(MenuItem item) {
// Get to drawer layout so we can interact with it
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
// Get the fragment manager to remove/add fragments
FragmentManager fragmentManager = getSupportFragmentManager();
// Handle navigation view item clicks here.
int id = item.getItemId();
if (id == R.id.nav_profile) {
// Hide visible fragment
fragmentManager.beginTransaction().hide(getVisibleFragment()).commit();
// Check if the fragment exists first.
if(fragmentManager.findFragmentByTag("profileFragment") != null) {
// If the fragment exists, show it (no reason to recreate it).
fragmentManager.beginTransaction()
.show(fragmentManager.findFragmentByTag("profileFragment"))
.commit();
} else {
// If the fragment does not exist, add it to fragment manager with a tag to identify it.
// Create new fragment instance with required argument(s).
ProfileFragment fragment = ProfileFragment.newInstance();
fragmentManager.beginTransaction()
.add(R.id.content_frame, fragment, "profileFragment")
.commit();
}
// Set the title
mToolbarTitleTextView.setText(R.string.title_activity_profile);
} else if (id == R.id.nav_feed) {
// Hide visible fragment
fragmentManager.beginTransaction().hide(getVisibleFragment()).commit();
// Check if the fragment exists first.
if(fragmentManager.findFragmentByTag("feedFragment") != null) {
// If the fragment exists, show it (no reason to recreate it).
fragmentManager.beginTransaction()
.show(fragmentManager.findFragmentByTag("feedFragment"))
.commit();
} else {
// If the fragment does not exist, add it to fragment manager with a tag to identify it.
fragmentManager.beginTransaction()
.add(R.id.content_frame, new feedFragment(), "feedFragment")
.commit();
}
// Set the title
mToolbarTitleTextView.setText(R.string.title_activity_feed);
} else if (id == R.id.nav_notifications) {
// Hide visible fragment
fragmentManager.beginTransaction().hide(getVisibleFragment()).commit();
// Hide the post button
mPostButton.setVisibility(View.GONE);
// Check if the fragment exists first.
if(fragmentManager.findFragmentByTag("notificationsFragment") != null) {
// If the fragment exists, show it (no reason to recreate it).
fragmentManager.beginTransaction()
.show(fragmentManager.findFragmentByTag("notificationsFragment"))
.commit();
} else {
// If the fragment does not exist, add it to fragment manager with a tag to identify it.
fragmentManager.beginTransaction()
.add(R.id.content_frame, new NotificationsFragment(), "notificationsFragment")
.commit();
}
// Set the title
mToolbarTitleTextView.setText(R.string.title_activity_notifications);
}
mDrawerLayout.closeDrawer(GravityCompat.START);
return true;
}
// Useful method to hide the currently visible fragment
public Fragment getVisibleFragment(){
FragmentManager fragmentManager = MainActivity.this.getSupportFragmentManager();
List<Fragment> fragments = fragmentManager.getFragments();
if(fragments != null){
for(Fragment fragment : fragments){
if(fragment != null && fragment.isVisible())
return fragment;
}
}
return null;
}
EDIT: It is really hard to reproduce this error which makes it hard to debug. It seems to randomly happen.
Why hide and keep all the fragments with fragmentManager.beginTransaction().add(); you can avoid this error by keeping only one fragment in memory and avoiding the hassle of hiding fragments by using fragmentManager.beginTransaction().replace() and using the fragment lifecycle methods to store the fragment state if necessary.
Here is how I solved the problem. In my MainActivity I did this:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(null);
setContentView(R.layout.activity_main);
}
Basically what was happening is if I had 1+ fragments on the screen, if the android system ran low on resources while the app was in the background and shut it down, when restored, MainActivity.onCreate() would be called and it would re-instantiate all the fragments with the call
super.onCreate(savedInstanceState);
So I just made it null and this prevents from all those fragments to be recreated.
The reason they are overlapping is because they were all getting shown at once.
Definitely not the correct way to do it, but it solves my problem right now =P

Fragment Transaction load empty view but fragment is shown after rotating device

I am building a navigation drawer as designed by the google documentation however I have an issue where the fragment is not being replaced. http://developer.android.com/training/implementing-navigation/nav-drawer.html
When the app first loads, the default fragment is loaded.
Clicking on another item on the drawer list leaves an empty view
However on rotating the device, loads the fragment chosen.
public void selectNavActivty(int position){
// TODO Changing between the different screens selection
fragment = null;
switch (position) {
case 0:
fragment = OverLay.newInstance();
break;
case 1:
fragment = Dummy.newInstance();
break;
}
if(fragment != null) {
// attach added to handle viewpager fragments
FragmentTransaction trans = getSupportFragmentManager().beginTransaction();
trans.replace(R.id.content_frame, fragment).attach(fragment)
.addToBackStack(null);
trans.commit();
getFragmentManager().executePendingTransactions();
} else {
Log.d("Drawer Activity","Error in creating Fragment");
}
}
For navigation menu fragment transactions I use the following approach, this way the fragment will be added and placed on top.
String name = "myFragment";
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.content_frame, fragment, name)
.commit();
Look up the attach() function. It follows a different fragment lifecycle.
Also make sure that your layout files framelayout is visible.
Modify your code as below:
if(fragment != null) {
// attach added to handle viewpager fragments
FragmentTransaction trans = getSupportFragmentManager().beginTransaction();
trans.replace(R.id.content_frame, fragment);
trans.addToBackStack(null);
trans.commit();
} else {
Log.d("Drawer Activity","Error in creating Fragment");
}
If the solution doesn't work for you, share the xml code along with your fragment code.
After adding Fragment it will be added to activity state and its view
will be added to defined Container view. But by attaching nothing will
be displayed if fragment was not already added to UI. It just attaches
to fragment manager. However if view was already added to a container
in UI and detached after that, by attaching it will be displayed again
in its container. Finally you can use attach and detach if you want to
destroy fragment View temporarily and want to display and build its
view on future without losing its state inside activity.
https://stackoverflow.com/a/18979024/3329488
My solution is to tag all the fragment with unique tag on fragment replacement. Make sure you also assign a unique tag to the default fragment during it creation. A more efficient way is to identify the fragment before you recreate the same one.
public void selectNavActivty(int position){
// TODO Changing between the different screens selection
FragmentManager fragmentManager = getSupportFragmentManager();
fragment = fragmentManager.findFragmentById(R.id.content_frame);
String fragmentTag = null;
switch (position) {
case 0:
fragmentTag = "case0Tag"; // please change to better tag name
break;
case 1:
fragmentTag = "case1Tag"; // please change to better tag name
break;
default:
Log.d("Drawer Activity","Error in creating Fragment");
return;
}
if (fragmentTag != null && !fragment.getTag().equals(fragmentTag))
fragmentManager.beginTransaction().replace(R.id.content_fragment, fragment, tag).commit();
}
In my case after rotating a device a blank fragment was shown. I understood that in an Activity.onCreate() I always called creating a blank Fragment and after that a needed one. So I changed it's behaviour to this:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
openEmptyFragment()
openAnotherFragment()
}
}
I recommend to check savedInstanceState != null before adding new fragments, as written in Why won't Fragment retain state when screen is rotated?.

Handling ActionBar title with the fragment back stack?

I have an Activity where I load in a ListFragment and, upon clicking, it drills down a level and a new type of ListFragment is shown, replacing the original one (using the showFragment method below). This is placed on the back stack.
At the beginning, the activity shows the default title in the action bar (i.e. it's set automatically based on the application's android:label).
When showing the list for the next level in the hierarchy, the name of the item clicked on should become the action bar's title.
However, when pressing Back, I would like the original default title to be restored. This isn't something FragmentTransaction knows about, so the title isn't restored.
I've vaguely read about FragmentBreadCrumbs, but this seems to require using a custom view. I'm using ActionBarSherlock and would prefer to not have my own custom title view.
What is the best way of doing this? Is it possible without a load of boilerplate code and having to keep track of the titles shown along the way?
protected void showFragment(Fragment f) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.fragment_container, f);
ft.addToBackStack(null);
ft.commit();
}
In every fragment and every activity I change the title like this. This way the active title will always be correct:
#Override
public void onResume() {
super.onResume();
// Set title
getActivity().getActionBar()
.setTitle(R.string.thetitle);
}
There is some cases where onResume isn't called inside fragments. In some of these cases we can use:
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if(isVisibleToUser) {
// Set title
getActivity().getActionBar()
.setTitle(R.string.thetitle);
}
}
As the original answer is quite old, this might come of help as well. As the documentation states, one might want to register a listener to listen on the back stack changes in the hosting Activity:
getSupportFragmentManager().addOnBackStackChangedListener(
new FragmentManager.OnBackStackChangedListener() {
public void onBackStackChanged() {
// Update your UI here.
}
});
Then, identify the situation in the callback method and set a proper title, without accessing the ActionBar from the Fragment.
This is a more elegant solution as the Fragment doesn't have to know about the ActionBar existence and Activity is usually the place that is managing the backstack so having it handled over there seems to be more appropriate. Fragment should at all time be considered only by its own content, not the surroundings.
More on the topic in the documentation.
Let the controlling activity do all the work as follows:
Listen for backstack events (in onCreate() of activity):
// Change the title back when the fragment is changed
getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
Fragment fragment = getFragment();
setTitleFromFragment(fragment);
}
});
Get the current fragment from the container:
/**
* Returns the currently displayed fragment.
* #return
* Fragment or null.
*/
private Fragment getFragment() {
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.container);
return fragment;
}
Set the fragment inside the content view:
private void setFragment(Fragment fragment, boolean addToBackStack) {
// Set the activity title
setTitleFromFragment(fragment);
.
.
.
}
Warpzit is right. This also solves title problem when orientation of device is changed. Also if you use support v7 for action bar, you can get action bar from fragment like this :
#Override
public void onResume() {
super.onResume();
((ActionBarActivity)getActivity()).getSupportActionBar().setTitle("Home");
}
It is best to let the OS do as much of the work as possible.
Assuming each fragment is properly named using .addToBackStack("title") then
you can override onBackPressed something like this to achieve desired behavior:
// this example uses the AppCompat support library
// and works for dynamic fragment titles
#Override
public void onBackPressed() {
FragmentManager fragmentManager = getSupportFragmentManager();
int count = fragmentManager.getBackStackEntryCount();
if (count <= 1) {
finish();
}
else {
String title = fragmentManager.getBackStackEntryAt(count-2).getName();
if (count == 2) {
// here I am using a NavigationDrawer and open it when transitioning to the initial fragment
// a second back-press will result in finish() being called above.
mDrawerLayout.openDrawer(mNavigationDrawerFragment.getView());
}
super.onBackPressed();
Log.v(TAG, "onBackPressed - title="+title);
getSupportActionBar().setTitle(title);
}
}
I use a similar solution to Lee approach, but replacing onBackStackChanged() method instead.
First I set the fragment name when adding the transaction to the back stack.
getSupportFragmentManager().beginTransaction()
.replace(R.id.frame_content, fragment)
.addToBackStack(fragmentTitle)
.commit();
Then I override the onBackStackChanged() method and I call setTitle() with the last backstack entry name.
#Override
public void onBackStackChanged() {
int lastBackStackEntryCount = getSupportFragmentManager().getBackStackEntryCount() - 1;
FragmentManager.BackStackEntry lastBackStackEntry =
getSupportFragmentManager().getBackStackEntryAt(lastBackStackEntryCount);
setTitle(lastBackStackEntry.getName());
}
Use Fragments method:
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
It is called on every Fragment appearance, but onResume is not.
The best approach is to make use of the android provided Interface OnBackStackChangedListener method onBackStackChanged().
Lets say we have a navigation drawer with 4 options to which the user can navigate to. In that case we will have 4 fragments. Lets see the code first and then I will explain the working.
private int mPreviousBackStackCount = 0;
private String[] title_name = {"Frag1","Frag2","Frag3","Frag4"};
Stack<String> mFragPositionTitleDisplayed;
public class MainActivity extends ActionBarActivity implements FragmentManager.OnBackStackChangedListener
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
....
....
....
getSupportFragmentManager().addOnBackStackChangedListener(this);
mFragPositionTitleDisplayed = new Stack<>();
}
public void displayFragment() {
Fragment fragment = null;
String title = getResources().getString(R.string.app_name);
switch (position) {
case 0:
fragment = new Fragment1();
title = title_name[position];
break;
case 1:
fragment = new Fragment2();
title = title_name[position];
break;
case 2:
fragment = new Fragment3();
title = title_name[position];
break;
case 3:
fragment = new Fragment4();
title = title_name[position];
break;
default:
break;
}
if (fragment != null) {
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.container_body, fragment)
.addToBackStack(null)
.commit();
getSupportActionBar().setTitle(title);
}
}
#Override
public void onBackStackChanged() {
int backStackEntryCount = getSupportFragmentManager().getBackStackEntryCount();
if(mPreviousBackStackCount >= backStackEntryCount) {
mFragPositionTitleDisplayed.pop();
if (backStackEntryCount == 0)
getSupportActionBar().setTitle(R.string.app_name);
else if (backStackEntryCount > 0) {
getSupportActionBar().setTitle(mFragPositionTitleDisplayed.peek());
}
mPreviousBackStackCount--;
}
else{
mFragPositionTitleDisplayed.push(title_name[position]);
mPreviousBackStackCount++;
}
}
In the code shown we have the displayFragment() method. Here I display the fragment on the basis of option chosen from the navigation drawer.The variable position corresponds to the position of the item clicked from the ListView or RecyclerView in the navigation drawer. I set the actionbar title accordingly with getSupportActionBar.setTitle(title), where the title stores the appropriate title name.
Whenever we click the item from nav drawer a fragment is displayed depending on the item clicked to the user. But on the back end side this fragment is added to the backstack and the method onBackStachChanged(), gets hit. What I have done is that I have created a variable mPreviousBackStackCount and initialized it to 0. I have also created an additional stack which will store the action bar title names. Whenever I add a new fragment to the backstack, I add the corresponding title name to my created stack. On the opposite side whenever I press the back button onBackStackChanged() is called and I pop the last title name from my stack and set the title to the name derived by the peek() method of the stack.
Example:
Lets say our android backstack is empty:
Press Choice 1 from nav drawer:
onBackStachChanged() is called and the Fragment 1 is added to android backstack, backStackEntryCount is set to 1 and Frag1 is pushed to my stack and size of mFragPositionTitleDisplayed becomes 1.
Press Choice 2 from nav drawer:
onBackStachChanged() is called and the Fragment 2 is added to android backstack, backStackEntryCount is set to 2 and Frag2 is pushed to my stack and size of mFragPositionTitleDisplayed becomes 2.
Now we have 2 elements both in the android stack and my stack. When you press back button onBackStackChanged() is called and the value of backStackEntryCount is 1. The code enters the if part and pops out the last entry from my stack. So, the android backstack has only 1 fragment - "Fragment 1" and my stack has only 1 title - "Frag1". Now I just peek() the title from my stack and set the action bar to that title.
Remember: To set the action bat title use peek() and not pop() else your application will crash when you open more than 2 fragments and try to go back by pressing back button.
You can Solve with onKeyDown!
I have a bool
mainisopen=true <-- MainFragment is Visible
other Fragment mainisopen=false
and here is My Code:
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && mainisopen == false) {
mainisopen = true;
HomeFrag fragment = new HomeFrag();
FragmentTransaction fragmentTransaction =
getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.fragmet_cont, fragment);
fragmentTransaction.commit();
navigationView = (NavigationView) findViewById(R.id.nav_view);
navigationView.getMenu().findItem(R.id.nav_home).setChecked(true);
navigationView.setNavigationItemSelectedListener(this);
this.setTitle("Digi - Home"); //Here set the Title back
return true;
} else {
if (keyCode == KeyEvent.KEYCODE_BACK && mainisopen == true) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("Wollen sie die App schliessen!");
builder.setCancelable(true);
builder.setPositiveButton("Ja!", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
System.exit(1);
}
});
builder.setNegativeButton("Nein!", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(getApplicationContext(), "Applikation wird fortgesetzt", Toast.LENGTH_SHORT).show();
}
});
AlertDialog dialog = builder.create();
dialog.show();
return true;
}
return super.onKeyDown(keyCode, event);
}
}
As described here my solution is adding this code to MainActivity onCreate method(): and changing actionbar title
FragmentManager fragmentManager=getSupportFragmentManager();
fragmentManager.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
Fragment currentFragment = fragmentManager.findFragmentById(R.id.My_Container_1_ID);
currentFragment.onResume();
}
});
and changing actionbar title in fragment's onResume() method
#Override
public void onResume() {
super.onResume();
AppCompatActivity activity = (AppCompatActivity) getActivity();
ActionBar actionBar = activity.getSupportActionBar();
if(actionBar!=null) {
actionBar.setTitle("Fragment Title");
actionBar.setSubtitle("Subtitle");
}
}
To update the actionbar title on back press. Just simply put
getActivity.setTitle("title")
inside onCreateView method.

Categories

Resources