Android Navigation Drawer wrong toolbar title onResume - android

I have a Navigation Drawer in the main activity of my app. On the onCreate method of the activity I initialize one of the fragments like this :
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
MenuItem menuItem = navigationView.getMenu().findItem(R.id.menu_history);
openFragment(menuItem);
}
public void openFragment(MenuItem menuItem){
Fragment newFragment = null;
switch (menuItem.getItemId()){
case R.id.menu_history :
newFragment = new HistoryFragment();
break;
//.....
}
if (newFragment != null){
//Replace content frame in activity_main.xml with newFragment
getSupportFragmentManager().beginTransaction()
.replace(R.id.content_frame, newFragment)
.commit();
menuItem.setChecked(true);
getSupportActionBar().setTitle(menuItem.getTitle());
}
drawerLayout.closeDrawers();
}
This all works well, the fragment appears on startup with the title on the toolbar being "History". But when the app goes into onPause and then onResume the toolbar title switches from "History" to the app name. I suspect this is an issue with onResume not opening the fragment / returning to its previous state correctly, because when I added the following lines to onResume the issue stopped:
#Override
protected void onResume() {
super.onResume();
MenuItem menuItem = navigationView.getMenu().findItem(R.id.menu_history);
openFragment(menuItem);
}
That solution seems to fix it but that means it has to reload the fragment with the animations every time the app is resumed, which isn't optimal. Any ideas on how to fix this?
If it helps, to recreate the issue I found useful to make the app split screen, because it always calls onResume. Thanks.

Inside your openFragment() method, you write:
getSupportActionBar().setTitle(menuItem.getTitle());
This is the only thing that changes your toolbar's title.
Note that this code has nothing to do with your Fragments, per se. When your activity is destroyed and recreated, the active Fragment will be successfully (automatically) destroyed and recreated by the FragmentManager... but your openFragment() method won't run again and so nothing will update your toolbar's title.
There are numerous ways you could solve this. Probably the right thing to do is update your toolbar's title from within one of your Fragment's lifecycle methods.
Edit: A reasonable place to update your toolbar's title would be in your fragment's onActivityCreated() method. This will run both when the fragment is first added and during recreation. Something like:
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
((AppCompatActivity) getActivity()).getSupportActionBar().setTitle("hello world");
}

If you do not want to re-create the fragment each time, you should use saveInstanceState like mentioned here: https://stackoverflow.com/a/17135346/1505074

Related

Exiting from Activity with only fragments

My app contains one empty activity and a couple of fragments. The onCreate of the activity replaces the empty view in activity_main.xml with a MainFragment that contains some buttons. Each button launches a separate fragment, and user can navigate from one fragment to another, etc.
On the press of back key, the current fragment correctly gets replaced with the previous fragment, until you get to the MainFragment. When user presses back from MainFragment, it hides the main fragment and you see the white empty background of the main activity. But I want to exit from the activity at this point, as that would be the sensible behaviour.
I am able to achieve this by calling super.onBackPressed() for a second time from onBackPressed if there are no fragments left in the fragment manager.
#Override
public void onBackPressed() {
super.onBackPressed();
FragmentManager manager = getSupportFragmentManager();
List<Fragment> fragments = manager.getFragments();
if (fragments == null || fragments.size() == 0) {
Log.d(TAG, "No more fragments: exit");
super.onBackPressed();
}
}
Is this acceptable thing to do - would it create any issues in the activity workflow? Is there a better/standard way to handle this scenario?
There is no problem to do that, but probably it would be easier if when you add the main fragment to the activity you do NOT call .addToBackStack()
You don't really need to override onBackPressed in your Activity. I would suggest implementing a method for adding fragments in your Activity:
protected void addFragment(Fragment fragment, boolean addToBackStack) {
String tag = fragment.getClass().getName(); //It's optional, may be null
FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction()
.add(R.id.your_container_id, fragment, tag);
if (addToBackStack) {
transaction.addToBackStack(tag);
}
transaction.commit();
}
And modify your onCreate method of activity like in the following snippet:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
// Add your fragment only if it is a first launch,
// otherwise it will be restored by system
addFragment(new YourFirstFragment(), false);
}
}
For all other fragments use:
addFragment(new OtherFragment(), true);

Callback when Fragment is replaced and when it is visible again

I've been researching this topic but so far no luck. Basically I'm replacing a fragment (A) with another one (B) using FragmentTransaction.replace. In this other fragment (B) I have a 'Cancel' button in the toolbar which when pressed pops back to the previous transaction (A) by calling getActivity().getSupportFragmentManager().popBackStackImmediate().
The problem is I need to update the Activity toolbar to display a different title when I'm showing fragment A and fragment B. I can't seem to find a method which gets called in fragment A whenever I go from A -> B -> A to inform me that it is visible again. The idea is to set the toolbar title in this callback which I cannot seem to find.
Can anyone point me in the right direction please?
Cheers.
Edit:
Method I call to replace the fragment with another one is as follows:
public static void replaceFragment(FragmentActivity parentActivity, int fragmentToReplaceId, Fragment withFragment, Integer enterAnim, Integer exitAnim)
{
FragmentManager fragmentManager;
FragmentTransaction transaction;
fragmentManager = parentActivity.getSupportFragmentManager();
transaction = fragmentManager.beginTransaction();
if ( (null != enterAnim) &&
(null != exitAnim) )
{
transaction.setCustomAnimations(enterAnim, exitAnim);
}
transaction.replace(fragmentToReplaceId, withFragment);
transaction.addToBackStack(null);
transaction.commit();
}
You can inform by overriding method onResume() in fragment and sending the message to activity or changing the Toolbar directly.
#Override
public void onResume() {
super.onResume();
((AppCompatActivity) getActivity()).getSupportActionBar().setTitle("Title");
}
In one activity, when replace A ---> B (A and B both are fragments), can use this call-back:
#Override
public void onAttachFragment(Fragment fragment) {
}
Solved by creating a simple static method in fragment A as follows:
public static void updateActivityTitle(FragmentActivity activity)
{
activity.setTitle(kActivityTitle);
}
Then I'm calling this method in fragment B as follows:
// cancel button has been pressed
private void cancel()
{
INV_CustomersFragment.updateActivityTitle(getActivity());
getActivity().getSupportFragmentManager().popBackStackImmediate();
}
Not ideal but it gets the job done. Any other solution involving a proper callback would be better
Update:
A better solution is the one described by #Rani at Fragment's onResume() not called when popped from backstack. This is more elegant and more maintainable, in fact I implemented this solution in my project. Compared to an equivalent solution for iOS this is still messy if you ask me, still seems the way to go.

Add fragment and update actionbar title

I know that question about refreshing actionbar title has already been answered.
But my problem is quiet different.
I use fragments with add method and not with replace method for some reasons. So previous fragment are not destroy and when back, previous fragment aren't not recreating.
This is my configuration :
Fragment A with title "FragA" > Fragment B with title "FragB"
When I go back to Fragment A from Fragment B the actionbar title should be "FragA" but it stay "FragB".
The problem is with add method Fragment A is not recreating and I didn't find event to refresh it.
The only simple solution I found for now is :
1- fragB.OnResume : save previous action bar title
2- fragB.OnDestroyView : restore previous actionbar title
With this solution, the result is ok, but I found this solution is not very clean. Is there a better way to refresh actionbartitle using add method with fragments ?
You can override your onBackPressed of your activity and each time you pressed it you then get the name of the fragment from the backstack to know which fragment you current at.
sample:
#Override
public void onBackPressed() {
super.onBackPressed();
int framentCount = this.getFragmentManager().getBackStackEntryCount();
if(framentCount != 0)
{
FragmentManager.BackStackEntry backEntry=getFragmentManager().getBackStackEntryAt(framentCount-1);
String str=backEntry.getName(); //the tag of the fragment
if(str.equals("fragA"))
//set the actionbar title to FragA
else if(str.equals("fragB"))
//set the actionbar title to FragB
}
FragA myFragA = (FragA)getFragmentManager().findFragmentByTag("MY_FRAGMENTA_A_TAG");
if (myFragA.isVisible()) {
//action bar.title="title first fragment"
}
}
Now to know which fragment is which you need to put a tag to your fragment when you add / replace it to the backstack. Also make sure that you call addToBackStack to put the fragments to the backstack.
FragmentTransaction.add(int containerViewId, Fragment fragment, String tag)
FragmentTransaction.replace(int containerViewId, Fragment fragment, String tag)
Call the below line of code in all your fragments onResume() callback. you wouldnt need to save the title.
((YourFragmentActivity) getActivity()).setActionBarTitle(YOUR_TITLE);
it should help.
Put a public static string called tag on your fragments, then use this where the tag is. More maintainability.
Why - This means if you want to change the tag, you only have to change it in one place, less refactoring. (Its used also on add, replace functions - see Rod's answer if confused)
My implementation of the solution is similar to how Rod solved the issue in his edited answer except a lot less code.
#Override
public void onBackPressed() {
super.onBackPressed();
try {
if (getFragmentManager().findFragmentByTag(FragA.tag) != null) {
if (getFragmentManager().findFragmentByTag(FragA.tag).isVisible()) {
getActionBar().setTitle(R.string.FragA_title);
}
}
if (getFragmentManager().findFragmentByTag(FragB.tag) != null) {
if (getFragmentManager().findFragmentByTag(FragB.tag).isVisible()) {
getActionBar().setTitle(R.string.FragB_title);
}
}
}
catch (NullPointerException e) {
}
}
eg of static tag use
FragmentTransaction.add(int containerViewId, Fragment fragment, FragA.tag)
#Override
public void onStop() {
super.onStop();
((AppCompatActivity) getActivity()).getSupportActionBar().setTitle("Title");
}
Override the onStop() method in your current fragment, and gives the title name of your previous fragment.

After orientation change, optionsmenu of fragment doesn't disappear

I implemented my layout based on this tutorial: http://android-developers.blogspot.hu/2011/02/android-30-fragments-api.html
The differences are:
I have different fragments to show, based on the choice in the left
list
The "details fragments" (those that come to the right) have different options menus
My problem is that if I have already selected something from the left and then rotate the phone to portrait, the last optionsmenu is still there and is visible.
I think the problem comes from the last active "details" fragment is recreated after the orientation change. to test it I created these two methods:
#Override
public void onStart() {
super.onStart();
setHasOptionsMenu(true);
}
#Override
public void onStop() {
super.onStop();
setHasOptionsMenu(false);
}
And I'm showing the right fragment like this:
case R.id.prefs_medicines:
if (mDualPane) {
// Check what fragment is shown, replace if needed.
View prefsFrame = getActivity().findViewById(R.id.preferences);
if (prefsFrame != null) {
// Make new fragment to show this selection.
MedicineListF prefF = new MedicineListF();
// Execute a transaction, replacing any existing
// fragment with this one inside the frame.
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.preferences, prefF);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
}
} else {
// Otherwise we need to launch a new activity to display
// the dialog fragment with selected text.
Intent intent = new Intent();
intent.setClass(getActivity(), MedicinePrefsActivity.class);
startActivity(intent);
}
break;
in one of my "details" fragment. when I debugged it, the onstart was called after the rotation.
The problem in pictures:
1: in landscape it's OK
Landscape mode http://img834.imageshack.us/img834/8918/error1d.png
2: in portrait: optionsmenu not needed
Portrait mode http://img860.imageshack.us/img860/8636/error2r.png
How can I get rid of the optionsmenu in portrait mode?
I had the same problem, and resolved it by setting setHasOptionsMenu(true) in the fragment only when savedInstanceState is null. If onCreate gets a bundle then the fragment is being restored in an orientation change to portrait, so don't display the menu.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState == null) {
setHasOptionsMenu(true);
}
}

hidden backstack fragments re-shown on configuration change

I am building a one activity-multiple fragments application. I add to the backstack after every transaction. After a couple of hiding and showing fragments and then I rotate the phone, all the fragments added on the container were restored and every fragment is on top of the other.
What can be the problem? Why is my activity showing the fragments I have previously hidden?
I am thinking of hiding all the previously-hidden-now-shown fragments but is there a more 'graceful' way of doing this?
Use setRetainInstance(true) on each fragment and your problem will disappear.
Warning: setting this to true will change the Fragments life-cycle.
While setRetainInstance(true) resolves the issue, there may be cases where you don't want to use it.
To fix that, setup a boolean attribute on the Fragment and restore the visibility:
private boolean mVisible = true;
#Override
public void onCreate(Bundle _savedInstanceState) {
super.onCreate(_savedInstanceState);
if (_savedInstanceState!=null) {
mVisible = _savedInstanceState.getBoolean("mVisible");
}
if (!mVisible) {
getFragmentManager().beginTransaction().hide(this).commit();
}
// Hey! no setRetainInstance(true) used here.
}
#Override
public void onHiddenChanged(boolean _hidden) {
super.onHiddenChanged(_hidden);
mVisible = !_hidden;
}
#Override
public void onSaveInstanceState(Bundle _outState) {
super.onSaveInstanceState(_outState);
if (_outState!=null) {
_outState.putBoolean("mVisible", mVisible);
}
}
Once the configuration changes (e.g. screen orientation), the instance will be destroyed, but the Bundle will be stored and injected to the new Fragment instance.
I had the same problem. you should check source code in the function onCreateView() of your activity.
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_main);
if(savedInstanceState == null){//for the first time
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
FragmentExample fragment = new FragmentExample();
fragmentTransaction.add(R.id.layout_main, fragment);
fragmentTransaction.commit();
}else{//savedInstanceState != null
//for configuration change or Activity UI is destroyed by OS to get memory
//no need to add Fragment to container view R.id.layout_main again
//because FragmentManager supported add the existed Fragment to R.id.layout_main if R.id.layout_main is existed.
//here is one different between Fragment and View
}
}
activity_main.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/layout_main">
You might want to try to use the replace() function rather than hide and show. I had the same problem when I started using Fragments and using the replace function really helped manage the Fragments better. Here is a quick example:
fragmentManager.replace(R.id.fragmentContainer, desiredFragment, DESIRED_FRAGMENT_TAG)
.addToBackStack(null)
.commit();

Categories

Resources