I have a navigation drawer with only one activity and several fragments.
My main fragment is really heavy to load so I try to keep it in memory because if I don't do it the drawer will be laggy when I use it.
To do so I have a layout like this :
<RelativeLayout
android:id="#+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
</RelativeLayout>
<fragment
android:id="#+id/main_fragment"
android:name=".MainFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
They they the same place but in the code I replace the fragment layout with lighter fragment and show/hide the main fragment.
This is a showFragment method to is called when I click on the item in the drawer.
case 0: //Light Fragment
fragment = getSupportFragmentManager().findFragmentByTag("light");
if (fragment == null) {
fragment = ProfileFragment.newInstance();
}
getSupportFragmentManager().beginTransaction().hide(getSupportFragmentManager().findFragmentById(R.id.main_fragment)).commit();
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, fragment, "light").addToBackStack(null).commit();
break;
case 1: //Heavy Frag
if (getActiveFragment() != null) {
getSupportFragmentManager().beginTransaction().remove(getActiveFragment()).commit();
}
getSupportFragmentManager().beginTransaction().show(getSupportFragmentManager().findFragmentById(R.id.main_fragment)).addToBackStack(null).commit();
break;
Which works fine except when I push the back button. I try many implementation of the onBackPressed but I always end up with either the main fragment showing when it should be hidden (So 2 fragment in top of each other) and the main fragment simply doesn't show.
Bonus question: Is that the correct way of doing thing ? I mean to avoid the drawer to lag or should i completely change my implementation?
Thanks.
Well, this should be more of a comment but since I cannot comment, I have to answer.
The reason could be that the fragments are not going into the backstack properly or in the order as they should.
I once created a project wherein I used Navigation Drawer with fragments. If I understand you correctly, what you want is to go to the last open fragment.
Try using the following code: Put it in onBackPressed()
#Override
public void onBackPressed() {
if (getFragmentManager().getBackStackEntryCount() == 0) {
this.finish();
} else {
getFragmentManager().popBackStack();
}
}
Essentially, you are popping the last fragment that was added to the backstack.
Related
I have an app with a main activity which loads a navigation drawer, and a pair of fragments that load in that activity ...
In the navigation drawer I have 4 options A, B, C and D ... the first one loads FragmentA on my activity and the last 3 load FragmentB ..
FragmentA displays a list of elements and, upon selecting one of these elements FragmentB is used to load its content... I want to change the home (hamburger/drawer) icon on FragmentB for the up icon when initiating from FragmentA (and change the corresponding behavior to make a popstack on select).. I have no problem with this using setDisplayHomeAsUpEnabled(true), but since all this is occurring inside one activity if I then select one other option (say B) from the navigation drawer the up icon will still be showing (it its also showing on the popped fragment)...
if I use setDisplayHomeAsUpEnabled(false) all this do is hide the home/up button from the toolbar, I need to recover the home button and make sure this will be shown when FragmentB is initiated from the drawer menu ...
Does this problem ring a bell to anyone? or am I just using fragments the wrong way? .. any advice will be appreciated
EDIT
this is more or less what I have in code
In Main Activity .. as the onNavigationItemSelected(MenuItem item) for the drawer I have a something like this ...
switch(optionNumber) {
case 0:
fragment = FragmentA.newInstance(optionNumber);
break;
default:
fragment = FragmentB.newInstance(optionNumber);
break;
}
Fragment frag = fragmentManager.findFragmentByTag("current_fragment");
if (frag != null && frag.getClass() == FolderFragment.class){
((FolderFragment)frag).resetScroll();
}
fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
fragmentManager.beginTransaction().replace(R.id.content, fragment, "current_fragment").commit();
which selects the fragment to load according to the option selected..
In FragmentA I'm calling FragmentB with this ..
FragmentB fFragment = FragmentB.newInstance(position);
Bundle args = new Bundle();
args.putString("filter", "something"); fFragment.setArguments(args);
mActivity.getSupportFragmentManager().beginTransaction()
.replace(R.id.flContent, fFragment, "current_fragment")
.addToBackStack(null)
.commit();
Preserving the fragment in the stack
And in fragmentB inside onResume() function I got something like...
String filter = getArguments().getString("filter", null);
if (type != null) {
mActivity.setTitle(title);
mActivity.getSupportActionBar().setDisplayShowHomeEnabled(true);
}else {
/*mActivity.getSupportActionBar().setDisplayHomeAsUpEnabled(false);
mActivity.getSupportActionBar().setDisplayShowHomeEnabled(true);
mActivity.getSupportActionBar().setHomeButtonEnabled(true);
mActivity.getSupportActionBar().setIcon(R.mipmap.ic_menu);*/
}
So When I'm creating fragmentB I check for arguments and see if it comes from fragmentA or not ( I could also check the fragmentmanager backstack and see if there's something)... there I just change the drawer icon with setDisplayShowHomeEnabled(true) ... leaving the back arrow, if I return to FragmentA (via onBackPressed()) FragmentA shows the arrow and I need it to show the original drawer icon ... the same happens if I select an option from the drawer menu ...
Does this gives more clarity to my issue ?... I have some commented code there because it doesn't work .. if I activate the line with setDisplayHomeAsUpEnabled(false).. the icon just disappears from the activity (which is the intended result of the function as far as I know)...
After a while I finally found this post
Switching between Android Navigation Drawer image and Up caret when using fragments
I guess that when involving a Drawer in the interface you might need to handle this issue with that component... this post gave me the answer.
Particular notice to the last comment by Wolfram Rittmeyer
I write this question because I would like to know which is the best way to manage this context: I have a MainActivity with Navigation Drawer and whenever I select an item in Navigation Drawer, I create a new fragment and through the FragmentTransaction I replace the previous fragment with the new one.
Now, in every fragment I have an AsyncTask that performs some task (eg download data from web or perform a query on a local sqlite database).
My question is: how can I avoid to recreate every time the fragment and restart AsyncTask when I press an element in Navigation Drawer? Which is the best way to manage this situation?
This is the method that I use in MainActivity to display fragment when I press an item in Navigation Drawer:
private void displayView(int index) {
Fragment f = null;
switch(index) {
case 1:
f = Fragment1.newInstance();
break;
case 2:
f = Fragment2.newInstance();
break;
}
if(f != null) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.container, f);
ft.commit();
}
}
Thanks in advance!
Hiding a fragment is an alternative to removing it. Removal (if not added to the backstack) causes the fragment to be completely torn down (pause, stop, destroyView, destroy, detach). Hiding doesn't change the fragment's lifecycle state, it just makes it not visible. The FragmentTransaction methods are hide() and show(). Initially, you would add both your fragments, and then hide the unselected one. When a fragment is selected from the navigation drawer, if it is not already visible, you would hide the old selection and make the new selection visible. Something like this:
getSupportFragmentManager()
.beginTransaction()
.hide(oldSelectedFrag)
.show(newSelectedFrag)
.commit();
The visible/hidden status of a fragment is available using isHidden(). There is also a Fragment callback: onHiddenChanged(). Keep in mind that when a fragment is hidden it is still active in the sense of being in the started or resumed state. You may want to use the hidden status to disable refreshes or other actions that are only needed when it is visible. If the fragment has an option menu, you may also want disable that using setMenuVisibility() when the fragment is hidden. I don't think the FragmentManager does that for you automatically.
My android app has a registration flow setup with an Activity and I load the steps as fragments into the activity layout.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal"
tools:context="com.example.android.ui.RegisterActivity">
<LinearLayout
android:id="#+id/register_container"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"></LinearLayout>
</RelativeLayout>
Now each fragment class implements a public interface implemented in the RegisterActivity so that I know to load the next step fragment and I add the new fragment to the backstack
mFragmentTransaction.addToBackStack(mStepOne.TAG);
now it all works fine all the way through 4 steps and I can navigate back through the steps while retaining the inputted data in each fragment IF it stays in the same orientation (portait)
BUT once i change the orientation, the fragment views disappear
if I am up to step 3, I can still hit the back button and it will go back showing me the fragment that is meant to be there by viewing the backstack changed listener
mFragmentManager.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
if(mFragmentManager.getBackStackEntryCount() > 0) {
Log.d("BACK STACK", "TAG (name): " + mFragmentManager.getBackStackEntryAt( (mFragmentManager.getBackStackEntryCount() - 1)).getName());
}
}
});
The count and tags of the fragments is retained but not the views. The RegisterActivity onCreateView loads the terms fragment into the view and it is what remains in view when navigating back through the steps.
mFragmentManager = getSupportFragmentManager();
mFragmentTransaction = mFragmentManager.beginTransaction();
mTerms = new RegisterFragmentTerms();
mFragmentTransaction.add(R.id.register_container, mTerms);
mFragmentTransaction.commit();
It seems to me the views are loaded into the RegisterActivity on top of each other and cleared on orientation change. Is it possible to solve what I'm trying to do? or have I implemented it wrong? It's my first android app :)
Cheers
It seems to be caused by Activity recreation. Have you ever add android:configChanges="orientation|keyboardHidden|screenSize" in your AndroidManifest.xml to your Activity? , if you add that flag to you activity, it will not be destroyed when orientation changes and the fragments stack will not disappear.
I have an ActionBar activity. In this activity I have implemented Navigation Drawer from Android API.
One option of navigation drawer set a ListFragment with some elements inside its list.
When I click some elements I want to create a new fragment and set previous ListFragment to the stack. Also I want to destroy this new fragment by clicking ActionBar home button, in order to return to the previous ListFragment.
My problem comes here: When I click home button of the actionbar, drawer layout is displayed, instead of destroy the fragment... What should I do?
I have Overriden onOptionsItemSelected method in the fragment:
#Override
public boolean onOptionsItemSelected(MenuItem menuItem) {
switch (menuItem.getItemId()) {
case android.R.id.home:
getFragmentManager()
.popBackStack();
}
return (super.onOptionsItemSelected(menuItem));
}
Also I have set ListFragment to the backstack when Inflating the new fragment:
getFragmentManager()
.beginTransaction()
.setCustomAnimations(android.R.anim.slide_in_left, android.R.anim.slide_out_right)
.replace(R.id.activity_main_fragment_container, fragment)
.addToBackStack(null)
.commit();
Well, as the docs say here, popBackStack() is async. I suppose that the drawer layout is displayed because you call super.onOptionsItemSelected(menuItem).
I suggest you to return true for all cases you handle this selection by yourself (in this specific situation: case android.R.id.home:), and call getActivity().onBackPressed() (assuming that by pressing hardware back button the last fragment gets removed, as it should) instead of popping back stack directly. I've implemented a similar solution and it works for me.
I have an Activity with a Button and a FrameLayout in its layout.
When I click the Button I add the fragment to the Activity's View.
If I add the fragment to the Back stack with addToBackStack() when I click the Back button it dissapears.
I want to achieve the same functionality by clicking again the Button.
My code is this :
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
AddRemoveFragment Frag_A = new AddRemoveFragment();
FragmentManager fm1 = getSupportFragmentManager();
FragmentTransaction transaction = fm1.beginTransaction();
if ( state == 0 ) {
Log.i(TAG, "inside IF");
state=1;
transaction.add(R.id.fragment_container_1, Frag_A);
transaction.addToBackStack(null);
transaction.commit();
} else {
state=0;
Log.i(TAG, "inside ELSE");
//transaction.replace(R.id.fragment_container_1, Frag_A);
transaction.remove(Frag_A);
transaction.commit();
}
}
});
Both remove() and hide() do nothing.
From the reference I don't understand something more specific. Just says it removes the fragment from the container. Isn't this what I want?Remove the fragment from FrameLayout?
Edit: hope it has nothing to do with the support library. I saw someone having some problems with that. Here
XML :
<?xml version="1.0" encoding="utf-8"?>
<Button
android:id="#+id/button_frag_1"
android:layout_width="124dp"
android:layout_height="wrap_content"
android:text="#string/button_text_1" />
<FrameLayout
android:id = "#+id/fragment_container_1"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:layout_alignBottom="#+id/button_frag_1"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_toRightOf="#+id/button_frag_1" >
</FrameLayout>
Edit 2: I changed the code inside the else statement from transaction.replace(R.id.fragment_container_1, Frag_A); to transaction.remove(Frag_A); but still got the same functionality.
For fragments, first of all you need to remember one thing:
If you added your fragment in your XML layout, then it can't be removed, it can only be shown using the .show() method and hidden using the .hide() method. If on the other hand you create an instance of your fragment in your code then you should add it using the .add() method or remove it using the .remove() method.
As regard to your question, I dont think you need to add your fragment to back stack if you want to remove your fragment using your button (unless you want to keep the functionality of removing it using the 'back' button).
In addition I don't think you need to use replace, from the documentation of replace:
Replace an existing fragment that was added to a container. This is essentially the same as calling remove(Fragment) for all currently added fragments that were added with the same containerViewId and then add(int, Fragment, String) with the same arguments given here.
It means that it replaces the content of the container with the new fragment, so all you do is remove your fragment and add it again.
You should .add() you fragment when you want to show it and .remove() it when you dont.
UPDATE:
Following you second question, when I say that you can add you fragment in your xml I mean that you can write this:
<fragment
xmlns:map="http://schemas.android.com/apk/res-auto"
android:id="#+id/listfragment"
android:name="com.eadesign.yamba.TimeLineListFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
In your XML layout file inside your FrameLayout which is your fragment container, in this case you cant remove this fragment you can only hide it.
And just to clarify you will always have to provide some kind of layout which will be the container of your fragment/fragments.
as opposite to that you can do what your are doing in your code:
AddRemoveFragment Frag_A = new AddRemoveFragment();
transaction.add(R.id.fragment_container_1, Frag_A);
transaction.addToBackStack(null);
transaction.commit();
In this case the fragment can be removed.
UPDATE2:
Try to take this line: AddRemoveFragment Frag_A = new AddRemoveFragment(); outside of the setOnClickListener method scope. I think that your problem is the fact that you are creating a new instance of this fragment on every click of your button. In fact I would move this line FragmentManager fm1 = getSupportFragmentManager(); out side as well there is no need to get the instance of a SupportFragmentManager on each click of your button. You should do this once.