Restoring the ViewPager after the application is destroyed - android

The Activity contains a ViewPager with multiple tabs populated by Fragments. When the system unloads the application from memory due to lack of resources, when the application is restored, the same Activity is restored, instances of the fragments that existed at the time of destruction are attached to it. At the same time, Activity.onCreate contains the standard logic for creating and filling the ViewPager with fragments:
ViewPagerAdapter pagerAdapter = new ViewPagerAdapter(getSupportFragmentManager(), ProjectActivity.this);
for (ProjectData projectData : projectData) {
pagerAdapter.addFragment(ProjectFragment.newInstance(projectData));
}
viewPager.setAdapter(pagerAdapter);
As a result, the Activity contains references to the newly created fragment instances, and the old (restored) fragment instances are attached to it.
At the moment, a decision has been made - at the start of the Activity, check whether the stack of fragments is empty, if not, delete existing fragments, and standardly fill the adapter with new instances of fragments:
FragmentManager fragmentManager = getSupportFragmentManager();
List<Fragment> oldFragments = fragmentManager.getFragments();
if (!oldFragments.isEmpty()) {
FragmentTransaction transaction = fragmentManager.beginTransaction();
for (Fragment fragment: oldFragments) {
transaction.remove(fragment);
}
transaction.commit();
fragmentManager.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
for (ProjectData projectData : projectData) {
pagerAdapter.addFragment(ProjectFragment.newInstance(projectData));
}
viewPager.setAdapter(pagerAdapter);
I suspect that this is not the right solution, but how to solve this issue in a standard way?
I also do not understand why when calling viewPager.setAdapter(pagerAdapter) new instances of fragments loaded into the adapter do not go through their life cycle until you clear the fragment stack.

Related

State of nested Fragments is lost when re-attached to their fragment container in bottom tab navigation

I am working on an app that has the following UI structure:
One Activity, called FoodActivity
This Activity implements bottom navigation. Hence, can show three categories of food: fruit, meat and fish
The Activity has a big Fragment container, where I attach the Fragment for fruit, meat and fish as the user interacts with the bottom tabs.
Each outer Fragment (fish, meat and fish) presents navigation between Fragments: it can show a list of fruits, and the the detail for the selected fruit.
Hence, the outer Fragments have another Fragment container in where I attach Fragments for the fruit list or fruit detail.
So, it's one main Activity, which has a big Fragment container where I swap Fragments, which in turn nest other Fragments
To switch from one outer Fragment to another using the tabs (ie: switch from fruit to meat), I perform a Fragment Transaction in the outer Fragment container:
private void switchFragment(Fragment fragment, String fragmentTag) {
final FragmentManager fm = getSupportFragmentManager();
final FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.fragment_outer, fragment, fragmentTag);
ft.commit();
}
The problem is that when switching the first-level Fragments, the state of their ChildFragmentManager is not kept.
For example, imagine that:
- I start in FuitFragment
- The FruitFragment, when created, attaches the nested FruitListFragment
- The user navigates in the nested Fragment container, from FruitListFragment to FruitDetailFragment
- The user switches to another tab
- The user switches back to the fruit tab
- The child `FragmentManager* of the FuitFragment does not automatically put FruitDetailFragment in the nested fragment container.
More specifically, when switching back to the outer Fragment:
onCreate is called, with savedInstance == null
onCreateView is called, with savedInstance == null
Hence, as the outer Fragment onCreate method is called, and I cannot tell whether or not to attach the child Fragment (as one usually does with Activities).
Moreover if I don't attach it (using an instance variable to check if I'm switching back to it) the child Fragment container will be just empty.
Experiments and notes
I have experimented that if I add the outer fragment transaction to the backstack when attaching it, when switching back to it, onCreate method is NOT called and the child FragmentManager will remember and attach the fragment it had before.
However, I cannot add it to the backstack as per Google's specifications on bottom navigation.
Setting setRetainInstace to true does not make any effect.
So, what should I do for properly restoring the state of the child FragmentManager?
Am I doing something wrong, or is it that everything around nested Fragments in Android is not well supported (or a bit broken) and I simply should not provide navigation using nested Fragments?
As Abbas pointed out, the problem was that I was using replace to switch between fragments.
I have changed to code in the Activity that puts the outer Fragment, and it works:
private void showChildFragment(int itemId) {
final FragmentManager fragmentManager = getSupportFragmentManager();
final FragmentTransaction transaction = fragmentManager.beginTransaction();
final Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.fragment_outer);
if (currentFragment != null) {
Log.v(TAG, "Detaching item #" + currentFragment);
currentFragment.setMenuVisibility(false);
currentFragment.setUserVisibleHint(false);
transaction.detach(currentFragment);
}
// Do we already have this fragment?
final String tag = makeFragmentTag(container.getId(), itemId);
Fragment fragment = fragmentManager.findFragmentByTag(tag);
if (fragment == null) {
fragment = createFragmentForViewId(itemId);
Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
transaction.add(container.getId(), fragment, tag);
} else {
Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
transaction.attach(fragment);
}
fragment.setMenuVisibility(true);
fragment.setUserVisibleHint(true);
transaction.commitAllowingStateLoss();
fragmentManager.executePendingTransactions();
}
private Fragment createFragmentForViewId(int itemId) {
switch (itemId) {
case FRAGMENT_ID_LIBRARY:
return LibraryNavigationFragment.createInstance();
case FRAGMENT_ID_FEED:
return WebAppFragment.createInstance("feed");
case FRAGMENT_ID_SUGGEST:
return WebAppFragment.createInstance("suggest");
default:
throw new IllegalArgumentException();
}
}
This code is almost copy pasted from android.support.v4.app.FragmentPagerAdapter as ViewPagers using Fragments work like I wanted to.
With getChildFragmentManager() it won't crash.
private void switchFragment(Fragment fragment, String fragmentTag) {
final FragmentManager fm = getChildFragmentManager();
final FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.fragment_outer, fragment, fragmentTag);
ft.commit();
}

Fragment in ViewPager loses its view

I started a new project using tabbed activity of Android Studio. It created an activity with ViewPager and 3 fragments = fragment1, fragment2 and fragment3.
I changed fragment1 to have a listView and a TextView, that I initialize when the fragment is created. The data in the listView is read from an SQL database.
When the application starts, fragment1 is displayed and the listView shows the data read from SQL.
When I swipe from fragment1 to fragment2 and back, fragment1 shows the listview properly. However, when I swipe to fragment3 and back to fragment1 the listview data is lost and I get a blank screen, although the textview shows properly.
When I change the application to have only 2 fragments it does not happen and I can swipe from fragment1 to fragment2 and back many times without any loss of data. The moment I add the 3rd fragment, the data is lost on first swipe to fragment3, but is not lost if I swipe between fragment 1 and fragment2 only.
Any idea why is it happening?
This is happening because by default ViewPager has a function named setOffscreenPageLimit(int i). This tells ViewPager how many of the Fragments need to store in memory. By default it is '2'. So when you swipe to Fragment 2 from Fragment 1 it will still keep Fragment 1 in memory and won't destroy it from memory. But when you swipe to Fragment 3 it will have Fragment 2 and Fragment 3 in memory so it will remove Fragment 1.
That's why when you come back to your first Fragment it have lost its views or data.
Try to use
mViewPager.setOffscreenPageLimit(your total fragment count +1);
Or much better solution
Do not replace your fragment when you swipe in ViewPager. Push it to the backstack and when you comeback to it, Check for the backstack and if fragment is there then just Pop it and display it.
Here is the code:
public void pushFragments(String tag, Fragment fragment) {
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
if (manager.findFragmentByTag(tag) == null) {
ft.add(R.id.frame_container, fragment, tag);
}
String tagOne="com........Frag_one"; //your fragment class name
String tagTwo="com........Frag_two";
String tagThree="com........Frag_three";
String tagFour="com........Frag_profile";
Fragment fragmentOne = manager.findFragmentByTag(tagOne);
Fragment fragmentTwo = manager.findFragmentByTag(tagTwo);
Fragment fragmentThree = manager.findFragmentByTag(tagThree);
Fragment fragmentFour = manager.findFragmentByTag(tagFour);
manager.executePendingTransactions();
// Hide all Fragment
if (fragmentOne != null) {
ft.hide(fragmentOne);
}
if (fragmentTwo != null) {
ft.hide(fragmentTwo);
}
if (fragmentThree != null) {
ft.hide(fragmentThree);
}
if (fragmentFour != null) {
ft.hide(fragmentFour);
}
// Show current Fragment
if (tag.equals(tagOne)) {
if (fragmentOne != null) {
ft.show(fragmentOne);
}
}
if (tag.equals(tagTwo)) {
if (fragmentTwo != null) {
ft.show(fragmentTwo);
}
}
if (tag.equals(tagThree)) {
if (fragmentThree != null) {
ft.show(fragmentThree);
}
}
if (tag.equals(tagFour)) {
if (fragmentFour != null) {
ft.show(fragmentFour);
}
}
ft.commit();
}
And Here is how you call it:
Fragment fragment = new Frag_one();
pushFragments(fragment.getClass().getName(),fragment);
Issue solved. The problem is due to the adapter keeping 3 fragments in memory, as pointed by #Daniel Nugent. The data in the pages is loaded from DB so that when the view is recreated, when fragments are destroyed and recreated as the user swipes, the listview adapter has lost the data, and one has to repopulate it. The problem does not appear on simple examples because usually the data presented is static and is loaded in the onCreateView method.

Two android ViewPagers in the fragment backstack are not coming up properly after activity is recreated

My Android app has only one activity and all the fragments are added or replaced in one framelayout.
The issue is when I have two different viewpagers in the fragment backstack and the activity is recreated the second viewpager comes up as the first viewpager.
Below is how I add the fragments to the backstack in the order that they happen so 3 is the fragment that comes up after the activity is recreated.
setupPlacesEventMainView(), this is a viewpager which uses a FragmentStatePagerAdapter.(shows up properly after activity is recreated)
setupPlaceDetailViewPager, this is a viewpager which uses a FragmentStatePagerAdapter. (ISSUE: PlacesEventMainViewPager shows up here instead of PlaceDetailViewPager. The issue occurs even if I don't navigate into the EventSpecials)
setupEventSpecials(), this is a listfragment. (shows up properly after activity is recreated)
Add PlacesEventMainViewPager to backstack
public void setupPlacesEventMainView()
{
clearBackStack();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
PlacesEventMainViewPager placesEventMainViewPager = new PlacesEventMainViewPager();
transaction.replace(R.id.fragment_container, placesEventMainViewPager, PlacesEventMainViewPager.class.getSimpleName());
transaction.commit();
}
Add PlaceDetailViewPager to backstack
public void setupPlaceDetailViewPager(Event event) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
PlaceDetailViewPager placeDetailViewPager = new PlaceDetailViewPager();
placeDetailViewPager.setEvent(event);
transaction.add(R.id.fragment_container, placeDetailViewPager,PlaceDetailViewPager.class.getSimpleName());
transaction.addToBackStack(PlaceDetailViewPager.class.getSimpleName());
transaction.commit();
}
Add SpecialsList to backstack
public void setupEventSpecials(Agenda[] agendas) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
SpecialsList specialsList = new SpecialsList();
specialsList.setAgendas(agendas);
transaction.add(R.id.fragment_container, specialsList,SpecialsList.class.getSimpleName());
transaction.addToBackStack(SpecialsList.class.getSimpleName());
transaction.commit();
}
What the fragments contain.
PlacesEventMainViewPager contains 3 listfragments.
PlaceDetailViewPager contains 2 listfragments
SpecialsList is 1 listfragment.
I can reach the PlaceDetailViewPager fragment by clicking on one of the items in the PlacesEventMainViewPager fragment. I can reach the SpecialsList by clicking a button in the PlaceDetailViewPager fragment.
Please let me know if you need any more details. Thanks!
You never add PlacesEventMainViewPager to the backstack which makes PlaceDetailViewPager the first one in the backstack if you listed them in order.

How to replace fragment properly using navigation drawer

I am using navigation drawer and it is simple to use. I am not providing the complete code but providing you detail which could be easy for you to understand my problem. I am using fragments these are about 8 in numbers and I am replacing them with one an other. But here comes a problem
I am replacing them on click event of the navigation drawer. but there are two main problems
After replacement , I can see the previous fragment in the background. does replace method just call the new fragment over it ? if yes then what should I do to old fragment not be visible in the background of my new fragment.
When I click navigation drawer Item , it loads the specific fragment successfully. but keeping in that fragment when I click to that specific item again it loads this fragment again and again. For example if drawer item num 3 opens fragment MyBook , then by clicking item num three 2 or many times would open fragment that much time.
So please some one answer me how to cure my app for such kind of actions which I described above.
I tried like this. Its working fine me
FragmentManager frgmanager = getFragmentManager();
frgmanager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
FragmentTransaction frgTransaction = frgmanager.beginTransaction();
if(subitem.equalsIgnoreCase("subitem")){
Frag1 frg1 =new Frag1(mCtx);
frgTransaction.replace(R.id.inflate_layout, frg1);
}else if(subitem1.equalsIgnoreCase("subitem1")){
Frag2 frg2 =new Frag2(mCtx);
frgTransaction.replace(R.id.inflate_layout, frg2);
}else{
Frag2 frg3 =new Frag3(mCtx);
frgTransaction.replace(R.id.inflate_layout, frg3);
}
frgTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
frgTransaction.commit();
you can use addtobackstack in fragmentstranstion object.like
FragmentManager manager = getFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.replace(R.id.bodyfragment, new AnotherFragment());
transaction.addtoBackStack(null).commit();
Use replace-method of FragmentTransaction instead of add (http://developer.android.com/guide/components/fragments.html#Transactions)
FragmentManager manager = getFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.replace(R.id.bodyfragment, new AnotherFragment());
transaction.commit();
To avoid re-instantiating the fragment, keep track of the current open fragment and only do a fragment transaction, if we next-to-be-opened fragment is a different one than the current.
This may achieved like the following:
class MyActivity ... {
private String currentFragment;
private void openNewFragment(Fragment fragment) {
String newFragment = fragment.getClass().getSimpleName();
if (newFragment.equals(currentFragment)){
// new fragment already shown
return;
}
// Fragment transaction etc here:
}
}
Note that this only compares fragments based in their class name. Sometimes this might not be unique, e.g. if there is a DetailFragment class which displays information about an entity. Which entities details to show may depend on intent arguments.
The above code however will then prevent opening DetailFragment for Entity=1 if currently details for Entity=2 are shown. For these scenarios the information about the fragment kept needs to be extended (e.g. storing a Reference or WeakReference to the fragment instance itself).

OnCreateView called multiple times / Working with ActionBar and Fragments

I switched part of my App from Activities to Fragments so that I can use the neat ActionBar tabs.
However, after completing the transition I ran into an issue: whenever I switch to another tab, that Fragment gets created all over again. Both onCreate and onCreateView get called every time I get to a tab.
I have 4 tabs, each of which is meant to open one of these fragments:
Fragment ShopFragment = new WebActivity();
Fragment SearchFragment = new SearchActivity(context);
Fragment StoreFragment = new StoreLocatorActivity(context, this);
Fragment BlogsFragment = new BlogsActivity(context, this);
Here's my code for the listener:
class MyTabsListener implements ActionBar.TabListener {
public Fragment fragment;
public MyTabsListener(Fragment fragment) {
this.fragment = fragment;
}
#Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
ft.hide(fragment);
}
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
ft.replace(R.id.fragment_container, fragment);
}
#Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
}
}
Could someone please point me in the right direction?
When you call FragmentTransaction.replace(...), Android will effectively perform a sequence of FragmentTransaction.remove(...) (for all Fragments currently added to that container) and FragmentTransaction.add(...) (for your supplied Fragment). Removing a Fragment from the FragmentManager will cause the Fragment to be destroyed and its state will no longer be managed. Most noticeably, when you re-add the Fragment all of the views will have been reset. Note: since you are reusing the same Fragment instance, the Fragment will still keep the value any instance variables.
One solution to this problem would be to use FragmentTransaction.detach(Fragment) and FragmentTransaction.attach(Fragment) when switching. This will cause the Fragment views to be recreated (onDestroyView() & onCreateView() will be called), but the instance state bundle will be saved and given back to you between calls and so the view state can be maintained. This is the approach taken by FragmentPagerAdapter when it tries to switch between Fragments.
Alternatively, you could allow the Fragments to be destroyed, but maintain their saved state for them independently. This would use less memory, at the expense of a slower switching time. Methods of note would be FragmentManager.saveFragmentInstanceState(Fragment) and FragmentManager.setInitialSavedState(Fragment.SavedState), in conjuction with adding/removing. This is the approach taken by FragmentStatePagerAdapter.
You can have a look at the source for FragmentPagerAdapter and the source for FragmentStatePagerAdapter for implementation hints.
There is the show/hide option just so the fragments would not need to be repainted/recreated and the onCreate() and onCreateView() won't be reinvoked.

Categories

Resources