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.
Related
I have an application with a navigation drawer, one activity and many fragments. When I change the device orientation my main activity is recreate and it's my main fragment who appears, not the current fragment. Just like when I launch the app.
How can I restore the current fragment and maybe the data associated with the selected fragment ?
I Have seen this post but it doesn't work : Save fragment state with navigation drawer
Thanks in advance to those who can help me.
I've found that the fragment states are already persisted for you.
This is what I do: I check if savedInstanceState is null. If it is, I execute the FragmentManager transaction to display the main fragment. If it is not null, it means the fragment state is persisted. I don't do any kind of transaction, and the fragment is recreated for me from the persisted state.
if (savedInstanceState == null){
//Launch HomeFragment onStart...
Fragment fragment = new HomeFragment();
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.frame, fragment).commit();
}
I added this to onCreate in my Main Activity
Lets say I have 2 fragments, one of which has a text input field.
I switch between my fragments in the main activity with this:
public boolean onNavigationItemSelected(MenuItem item) {
// Handle navigation view item clicks here.
int id = item.getItemId();
if (id == R.id.nav_new) {
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, NewFragment(), fragment.getClass().getSimpleName())
.addToBackStack(Integer.toString(id))
.commit();
} ...
Notice I'm using framentTransactions replace method
On the current fragment with the input box, I type in some text. Then I use the nav bar to switch to a new fragment. I would expect that because I'm using replace, the old fragment no longer exists.
I navigate to the new fragment fine, but when I press the back button, the old fragment still has the text that I typed in previously
Why would this happen when using replace? Am I misunderstanding how fragment transactions work?
Does the onCreateView method of the fragment not get called again when I press back, thus essentially resetting the fragment?
You have to clear your textfied manually when you switching to another fragment
Because fragment replace is only replace fragment but still your old fragment is there without any change .
I have a problem that I've been dealing with for the last couple o days and don't seem to find an answer to it.
Description : I have a main activity which contains a navigation drawer. Each item of the navigation drawer (when clicked) creates a fragment. In that fragment, there is a listView of objects, which creates other fragments when clicked. In those fragments i have another listView of objects which opens other fragments. In other words, there series of fragment that open other fragment. Something like this:
http://s22.postimg.org/pddo5gsv5/backstack.png
In order to be able to get back to each fragment, I've implemented the addToBackstack("string") method.
My question is, how can I implement correct backstack for my application so that when i click a navigation Drawer item, all the fragments that have been added to backstack are cleared, without the one that the navigation Drawer item opens.
Any help would be appreciated. Thank you !
EDIT
Ok, it seems I managed to figure it out. Considering what advices i received from the replies, here's the solution I came up with:
#Override
public void onBackPressed() {
int count = getFragmentManager().getBackStackEntryCount();
if (count != 0) {
FragmentManager.BackStackEntry backEntry = getFragmentManager()
.getBackStackEntryAt(
getFragmentManager().getBackStackEntryCount() - 1);
if (backEntry.getName() == NAVIGATION) {
finish();
} else
getFragmentManager().popBackStack();
} else {
super.onBackPressed();
}
}
To put it in words: First, i added a backstack entry even for the top level fragments, given them a specific tag. The I have overridden the Activity's back button function so that when the last backstack entry is a top-level fragment to finish the activity (so that it not simply detach the fragment from activity, living it empty). Otherwise, if the last entry isn't an top-level fragment, execute a popBackStack.
PS: All non-top-level fragments are added to the backstack with a different tag then the top-level one. Also, i had to do a POP_BACK_STACK_INCLUSIVE in the navigation Drawer's click listener.
getFragmentManager().popBackStack(null,
FragmentManager.POP_BACK_STACK_INCLUSIVE);
Thank you all for the advices and hopefully this EDIT help other users.
You can use the following code to solve your problem:
getFragmentManager().beginTransaction()
.replace(R.id.fragment_container, fragment)
.addToBackStack(fragment_tag)
.commit();
In order to make the code above work, you have to create the fragments dynamically. As hardcoded fragments cannot be replaced. To do that, you can create a container (FrameLayout etc.) which in our example has the id fragment_container. Then, the code above will add the fragment in the container dynamically. Finally, you have to pass as parameter in the addToBackStack method the fragment_tag. That means, that this transaction will be added in the back stack. And finally, in order to get it from the backstack you have to use the code below:
getFragmentManager().popBackStack(fragment_tag, FragmentManager.POP_BACK_STACK_INCLUSIVE));
The POP_BACK_STACK_INCLUSIVE flag, insures that "all matching entries will be consumed until one that doesn't match is found or the bottom of the stack is reached. Otherwise, all entries up to but not including that entry will be removed."
You can clear the fragment backstack by using something like:
fragmentManager.popBackStack("string", FragmentManager.POP_BACK_STACK_INCLUSIVE);
and then you can addToBackstack and commit as usual. More info.
A code snippet that shows the way I normally use it in navigation drawers:
FragmentManager fragmentManager = getSupportFragmentManager();
if(clearBackStack) {
fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
FragmentTransaction ft = fragmentManager.beginTransaction();
ft.replace(R.id.content_frame, fragment);
if(!clearBackStack) {
ft.addToBackStack(null);
}
ft.commit();
Although I have been working with Android for years, this is my first time using Fragments in a real life project. So I am a bit confused :-P
This app has a NavigationDrawer with 4 main sections. I am using just one main Activity with a Layout that has a content View which I replace with Fragments. When an option in the NavigationDrawer is selected, I instantiate a Fragment and put it in that content View. I am doing something like this:
private class DrawerItemClickListener implements ListView.OnItemClickListener {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
selectItem(position);
}
}
public static int drawerSelectedOption = 0;
private void selectItem(int position) {
drawerSelectedOption = position;
Fragment fragment = null;
switch (position) {
case 0:
fragment = new StoresFragment();
break;
case 1:
//promos
case 2:
fragment = new TransactionsFragment();
break;
case 3:
break;
case 4:
fragment = new Settings();
break;
}
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit();
// update selected item and title, then close the drawer
mDrawerList.setItemChecked(position, true);
setTitle(mSectionTitles[position]);
mDrawerLayout.closeDrawer(mDrawerList);
}
More Fragments are pushed from within each one of the main NavigationDrawer Fragments. So each one of the main 'screens' have several sub screens inside like shown below.
Drawer __ Fragment 1.0 -- Fragment 1.1 -- Fragment 1.2
|__ Fragment 2.0 -- Fragment 2.1 -- Fragment 2.2
I am pushing those inner Fragments from the previous Fragment ActionBar menus like this:
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
OrderCreateFragment fragment = new OrderCreateFragment();
fragmentTransaction.add(R.id.content_frame, fragment);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
I am facing several problems and that's why I am looking for some advice about this approach:
I don't even know how to properly manage the ActionBar menu items. Every Fragment has its own menus and sometimes they are stuck in the bar (ie. if switching Fragments by tapping on the NavigationDrawer instead of going back with the Android back key)
StackBack should be popped completely when selecting one of the NavigationDrawer options. I suspect my ActionBar messy items are related to this because the Fragments are kept in the stack somehow.
So, can anyone please kindly advice whether is this a proper approach and how to address the issues with the StackBack and the ActionBar?
UPDATE:
Looks like clearing the whole stack on the selectItem method solved the 2 main issues. Though I am still interested in listening opinions about this approach.
Well , I reckon best way depends on the application needs. Considering 2 scenarios
A- App has requirement for navigationdrawer & multiple activities with nested/deep navigation .
Then best approach is to have a base activity which has a framelayout and navigationdrawer. All activities extends/inherits from this base Activity.
And for each activity inflate the respective layout and add to framelayout container of baseActivity programmatically.
The back stack navigation is easier.
B - App has requirement for navigationdrawer & only single tier navigation.
Then one can use the concept of dynamically adding and replacing the fragments on a single Activity which has navigation drawer.
http://developer.android.com/training/implementing-navigation/nav-drawer.html
This is the tutorial I used to learn navigation drawers. Found it pretty useful. It walks through process of setting up drawer, linking it to icons, and uploading fragments to the page.
In my app, I have the following architecture:
MainActivity
|_ FirstFragment
| |_ GridFragment
| |_ MapFragment
|
|_ SecondFragment
|
|_ ThirdFragment
Please note that:
MainActivity is using ActionBarSherlock and SlidingMenu.
SlidingMenu switches between fragments (FirstFragment, SecondFragment, ThirdFragment).
FirstFragment creates a button into the action bar to switch between grid and map mode. This is, to switch between its subfragments.
What I want to achieve
I need fragments need to preserve its state when switching between them. This is, if I am in FirstFragment and then select map mode (show MapFragment subfragment), and from the slide menu choose another option and come back, I should see the map again. Right now, it's resetting each fragment when selecting its section from the slide menu.
This isn't surprising, as at the moment I am commiting transactions with new FirstFragment() when the menu options are pressed.
However, I first tried to have references to each fragment into his parent. For example, MainActivity had three fragments members which, when commiting transactions, were checked if exist previously and instantiated if necessary. This worked fine until I added the second level of fragments, then it started throwing exceptions when committing the transaction (saying that the Activity was destroyed).
As you may have noticed, I am far from being an Android expert and need some guidance on this topic.
How can I preserve fragment states without having a reference for each of them?
Next thing I tried after posting the question, was to avoid keeping the fragments in local variables inside its container and using FragmentManager.getFragmentByTag() to access them at any given time, but the problem persisted, as FragmentTransaction.replace() was destroying the fragments.
My solution
As Luksprog pointed out in his comment, I had to manage all fragments manually. In order to achieve this, I had to go back to my former approach, where I had local variables for each fragment. Then, MainActivity does the following:
Instantiate its three fragments.
this.firstFragment = new FirstFragment();
this.secondFragment = new SecondFragment();
this.thirdFragment = new ThirdFragment();
Attach the three fragments, and hide all of them except the initial section.
getSupportFragmentManager()
.beginTransaction()
.add(R.id.content_frame, this.firstFragment)
.add(R.id.content_frame, this.secondFragment)
.add(R.id.content_frame, this.thirdFragment)
.hide(this.secondFragment)
.hide(this.thirdFragment)
.commit();
To switch content, SlideMenu is calling this function:
public void switchContent(Fragment newContent) {
if (newContent != null) {
getSupportFragmentManager()
.beginTransaction()
.detach(this.firstFragment)
.detach(this.secondFragment)
.detach(this.thirdFragment)
.attach(newContent)
.commit();
// Restore menu open gesture if map is not present
if (!(newContent instanceof firstFragment) && getSlidingMenu().getTouchModeAbove() != SlidingMenu.TOUCHMODE_FULLSCREEN)
getSlidingMenu().setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN);
// Set menu open gesture if map is present
if (newContent instanceof firstFragment && firstFragment.currentFragment == FirstFragment.MAP_FRAGMENT)
getSlidingMenu().setTouchModeAbove(SlidingMenu.TOUCHMODE_NONE);
getSlidingMenu().showContent();
}
}
Then, the fragments which has subfragments (FirstFragment) is doing the same but:
It's using getChildFragmentManager() instead of getSupportFragmentManager().
It's using show() and hide() to replace content, because when using detach() and attach() the map wasn't preserving it's coordinates.