Retain fragment in viewpager after rotating - android

Edit: I'm terrible at explaining these things so here is a video
http://sendvid.com/mefqxd8f
Context:
The application consists of a single Activity with a ViewPager that holds 3 tabs. Tab1 uses an interface called PageFragmentListener to switch between 2 different fragments in the same Tab when a button is pressed. When the app first loads, Fragment1 is inflated in Tab1. After clicking the button, Fragment2 is inflated in Tab1. I'd like Fragment1 to remain in Tab1 permanently after this.
The issue:
If Tab3 is currently active (visible to the user) and then the device is rotated Tab1 is completely destroyed. When it is recreated, it inflates Tab1 with Fragment1 instead of restoring Fragment2. In the video you'll see that Tab1 is blue instead of purple.
Question:
Is there a way to restore Fragment2 to Tab1 after it is destroyed rather than inflating Fragment1? I'm assuming savedInstanceState can be used somehow.
class MyAdapter extends FragmentStatePagerAdapter {
static final String TAG = "MyAdapter";
private final FragmentManager mFragmentManager;
public BaseFragment mFragmentA;
public BaseFragment mFragmentB;
/**
* PageFragmentListener for switching fragmentA.
*/
public PageFragmentListener mListenerA = new PageFragmentListener() {
#Override
public void onSwitchToNextFragment(final String id) {
mFragmentManager.beginTransaction().remove(mFragmentA).commit();
if (mFragmentA instanceof FragmentA)
{
mFragmentA = FragmentA2.newInstance(mListenerA); // => switch fragment a2
}
else if (mFragmentA instanceof FragmentA2){
mFragmentA = FragmentA.newInstance(mListenerA);
}
notifyDataSetChanged(); // notify changes
}
};
/**
* PageFragmentListener for switching fragmentB.
*/
public PageFragmentListener mListenerB = new PageFragmentListener() {
#Override
public void onSwitchToNextFragment(final String id) {
mFragmentManager.beginTransaction().remove(mFragmentB).commit();
if (mFragmentB instanceof ItemListFragment){ // current fragment is List Fragment
Bundle arguments = new Bundle();
arguments.putString(Constants.ARG_ITEM_ID, id); // selected item id
mFragmentB = ItemOneDetailFragment.newInstance(mListenerB); // switch detail fragment
mFragmentB.setArguments(arguments);
}else if (mFragmentB instanceof ItemOneDetailFragment) { // DetailFragment
mFragmentB = ItemListFragment.newInstance(mListenerB); // => switch list fragment
}
notifyDataSetChanged(); // notify changes
}
};
public MyAdapter(FragmentManager fm) {
super(fm);
Log.d(TAG, "MyAdapterConstructor");
mFragmentManager = fm;
List<Fragment> fragments = fm.getFragments();
if(fragments != null){
for(Fragment f : fragments){
if (f instanceof FragmentA || f instanceof FragmentA2){
mFragmentA = (BaseFragment) f;
}
if(f instanceof ItemListFragment || f instanceof ItemOneDetailFragment){
mFragmentB = (BaseFragment) f;
}
}
}
}
#Override
public Fragment getItem(int position) {
Log.d(TAG, "getItem()");
if (position == 0){ // Tab-1
if (!(mFragmentA instanceof FragmentA2)) {
mFragmentA = FragmentA.newInstance(mListenerA);
}
return mFragmentA;
}
if (position == 1) // Tab-2
return FragmentB.newInstance();
if (position == 2) { // Tab-3
if (!(mFragmentB instanceof ItemOneDetailFragment)){
mFragmentB = ItemListFragment.newInstance(mListenerB);
}
return mFragmentB;
}
else{
return null;
}
}
/**
* To set tab title.
* #param position the currently visible ViewPager fragment
* #return the title of the fragment
*/
#Override
public CharSequence getPageTitle(int position) {
Log.d(TAG, "getPageTitle()");
if (position == 0) { // Tab-1
return "Tab 1";
}
if (position == 1) { // Tab-2
return "Tab 2";
}
if (position == 2) { //Tab-3
return "Tab 3";
}
return null;
}
#Override
public int getCount() { // Count of Tabs
return 3;
}
#Override
public int getItemPosition(Object object) {
Log.i("Adapter", "ItemPosition>>>" + object.toString());
if (object instanceof ItemListFragment && mFragmentB instanceof ItemOneDetailFragment) { // fragment changed
return POSITION_NONE;
}
if (object instanceof ItemOneDetailFragment && mFragmentB instanceof ItemListFragment) { // fragment changed
return POSITION_NONE;
}
if (object instanceof FragmentA && mFragmentA instanceof FragmentA2) {
return POSITION_NONE;
}
if (object instanceof FragmentA2 && mFragmentA instanceof FragmentA){
return POSITION_NONE;
}
return POSITION_UNCHANGED;
}
}

Simple solution to retain the state of the fragment while rotate is by calling
setRetainInstance(true);
In order to add fragment into stack try like this
transaction.addToBackStack(fragment.getClass.getName());
to get previous fragment (may be on while calling of onBackPressed()) try this
getFragmentManager.popBackStack();

My (poor) solution was to create a Static Class Boolean of MainActivity called hasSavedInstance. I set this to true in onCreate() of MainActivity as follows:
if (savedInstanceState == null){
Log.d(TAG, "onCreate() FIRST");
}
else{
Log.d(TAG, "onCreate() ADDITIONAL");
hasSavedInstance = true;
}
I then use this value to determine which Fragment to return in MyAdapter:
#Override
public Fragment getItem(int position) {
Log.d(TAG, "getItem()");
if (position == 0){ // Tab-1
Log.d(TAG, "hasSavedInstance="+MainActivity.hasSavedInstance);
if (mFragmentA == null && MainActivity.hasSavedInstance){
mFragmentA = FragmentA2.newInstance(mListenerA);
}
else if (!(mFragmentA instanceof FragmentA2)) {
mFragmentA = FragmentA.newInstance(mListenerA);
}
return mFragmentA;
}
This seems to work but I feel like setting this flag is a very inelegant way of working around this problem.

Related

Problems when reloading fragment in ViewPager

I have problem with fragments in ViewPager. In my app I have 4 tabs implemented with FragmentStatePagerAdapter.
Here is my SectionsPageAdapter.java
public class SectionsPageAdapter extends FragmentStatePagerAdapter {
private String fragment0 = "";
private Fragment fragAt0;
private FragmentManager manager;
private String[] tabNames;
Context context;
public SectionsPageAdapter(FragmentManager fm, Context ctx, String lastFrag) {
super(fm);
context = ctx;
tabNames = ctx.getResources().getStringArray(R.array.tabs_name);
this.manager = fm;
this.listener = new fragmentChangeListener();
fragment0 = lastFrag;
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return ActivityTypeFragment.newInstance(0);
case 1:
return EventsFragment.newInstance();
case 2:
return CalendarFragment.newInstance();
case 3:
if(fragAt0 == null){
switch(fragment0){
case "Chart":
fragAt0 = ChartFragment.newInstance(listener);
break;
case "Hour":
fragAt0 = HourChartFragment.newInstance(listener);
break;
case "Daily":
fragAt0 = DailyChartFragment.newInstance(listener);
break;
default:
fragAt0 = ChartFragment.newInstance(listener);
}
}
return fragAt0;
default:
return ActivityTypeFragment.newInstance(0);
}
}
#Override
public int getCount() {
return 4;
}
#Nullable
#Override
public CharSequence getPageTitle(int position) {
return tabNames[position];
}
#Override
public int getItemPosition(#NonNull Object object) {
if (object instanceof ChartFragment && fragAt0 instanceof DailyChartFragment
|| object instanceof HourChartFragment && fragAt0 instanceof DailyChartFragment)
{
return POSITION_NONE;
}
else if (object instanceof DailyChartFragment && fragAt0 instanceof ChartFragment
|| object instanceof HourChartFragment && fragAt0 instanceof ChartFragment){
return POSITION_NONE;
}
else if (object instanceof HourChartFragment && fragAt0 instanceof ChartFragment)
{
return POSITION_NONE;
}
else if (object instanceof ChartFragment && fragAt0 instanceof HourChartFragment
|| object instanceof DailyChartFragment && fragAt0 instanceof HourChartFragment)
{
return POSITION_NONE;
}
return POSITION_NONE;
}
public interface nextFragmentListener {
void fragment0Changed(String newFragmentIdentification, FragmentManager fragmentManager);
}
private nextFragmentListener listener;
private final class fragmentChangeListener implements nextFragmentListener {
#Override
public void fragment0Changed(String fragment, FragmentManager fm) {
fragment0 = fragment;
manager.beginTransaction().remove(fragAt0).commitNow();
switch (fragment) {
case "Chart":
fragAt0 = ChartFragment.newInstance(listener);
break;
case "Daily":
fragAt0 = DailyChartFragment.newInstance(listener);
break;
case "Hour":
fragAt0 = HourChartFragment.newInstance(listener);
break;
}
SectionsPageAdapter.this.notifyDataSetChanged();
}
}
public String getFragmentName()
{
return fragment0;
}
}
So in last tab I have a fragment - ChartFragment, where I have Spinner with options to select another chart. And when user selects another chart, this another chart reloads in this tab by calling method fragment0Changed that I have in my SectionsPageAdapter
Class. I found this solution in https://stackoverflow.com/a/27304530/1573414.
Here is my function onItemSelected which I have in every fragment that I want to be appeared in Charts tab
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (view != null) {
String selectedMenu = ((TextView) view).getText().toString();
if (selectedMenu == getString(R.string.best_appealing_chart)) {
LoadBestAppealingChart();
} else if (selectedMenu == getString(R.string.hour_chart)) {
chartListener.fragment0Changed("Hour", getActivity().getSupportFragmentManager());
}
else
{
chartListener.fragment0Changed("Daily", getActivity().getSupportFragmentManager());
}
}
else {
LoadBestAppealingChart();
}
}
So, everything works fine until I rotate device, after that selecting another chart does not work. App throwing the exception:
java.lang.IllegalStateException: Fragment host has been destroyed
at android.support.v4.app.FragmentManagerImpl.ensureExecReady(FragmentManager.java:2183)
at android.support.v4.app.FragmentManagerImpl.execSingleAction(FragmentManager.java:2211)
at android.support.v4.app.BackStackRecord.commitNow(BackStackRecord.java:643).
I’ve read the article https://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html,but in my case I have nor asynchronus methods where I would replace a fragment, nor I call it in Activity lifecycle methods. I make a fragment transaction in a onSelectionChanged method of my spinner. What I tried: using commitNowAllowingStateLoss() do not make things better, it just didn’t throw an exception, fragments still do not reloading; passing getSupportFragmentManager() parameter to fragment0Changed method doesn’t help either, but doing this does not throw any exception even if i use simple commitNow() method. But what helps is when after rotation I choose first or second tab of my app then go to my fourth tab(Charts tab), then I can switch(reload) my fragments without any problem. Interesting thing that switching to third tab(Calendar tab) does not help either. After switching to third tab and returning to fourth tab switching fragments still do not reloaded.
I think that its a common mistake on Android to communicate with Fragment passing listener. Listener on Fragment must be implemented by the Activity or a child Fragment. So you have to make your Activity or Fragment implements NextFragmentListener... Second, never make a reference to a Fragment on ViewPager, you are leaking the old fragment0
public class SubAdapter extends FragmentStatePagerAdapter
{
/* just pass the behavior in super() call so every time resume method will call so that you can reload any thing you want
But you should use subclass extending */
private String tile_list[];
public SubAdapter (FragmentManager fm, String[] tile_list)
{
super(fm,FragmentStatePagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
this.tile_list = tile_list;
}
#Override
public Fragment getItem(int position)
{
Fragment fragment;
switch (position)
{
case 0:
fragment=new new_fragment();
break;
default:
fragment=new new_fragment();
}
return fragment;
}
#Override
public int getCount()
{
return tile_list.length;
}
#Nullable
#Override
public CharSequence getPageTitle(int position)
{
return tile_list[position];
}
}

Restoring fragments and their states in viewpager on rotation

Working on app that needs to dynamically add and remove list fragments from viewpager, when tapped on an item in the list new fragment adds to viewpager and when swipe back fragment needs to be removed, this works good but when i rotate the screen i get double instances of fragments in the pager, rotate it again and it quadruples the instances.
At the same time i need to persist state of the fragments (list position, number of items loaded, etc) this means overriding onsaveinstancestate in fragments and saving relevant data in the bundle to restore on recreation.
I managed to solve the issue of double instances by clearing the adapter and calling notifydatasetchanged but then i lose saving states in fragment since onsaveinstance is not called for obvious reasons and if i don't clear the adapter it just doubles the instances. I have seen the same behavior in Dropbox app when entering and leaving folders.
This is custom pager adapter implementation i am using
/**
* Implementation of {#link PagerAdapter} that
* uses a {#link Fragment} to manage each page. This class also handles
* saving and restoring of fragment's state.
*
* <p>This version of the pager is more useful when there are a large number
* of pages, working more like a list view. When pages are not visible to
* the user, their entire fragment may be destroyed, only keeping the saved
* state of that fragment. This allows the pager to hold on to much less
* memory associated with each visited page as compared to
* {#link FragmentPagerAdapter} at the cost of potentially more overhead when
* switching between pages.
*
* <p>When using FragmentPagerAdapter the host ViewPager must have a
* valid ID set.</p>
*
* <p>Subclasses only need to implement {#link #getItem(int)}
* and {#link #getCount()} to have a working adapter. They also should
* override {#link #getItemId(int)} if the position of the items can change.
*/
public abstract class UpdatableFragmentPagerAdapter extends PagerAdapter {
private final FragmentManager fragmentManager;
private final LongSparseArray<Fragment> fragmentList = new LongSparseArray<>();
private final LongSparseArray<Fragment.SavedState> savedStatesList = new LongSparseArray<>();
#Nullable private FragmentTransaction currentTransaction = null;
#Nullable private Fragment currentPrimaryItem = null;
public UpdatableFragmentPagerAdapter(#NonNull FragmentManager fm) {
this.fragmentManager = fm;
}
/**
* Return the Fragment associated with a specified position.
*/
public abstract Fragment getItem(int position);
#Override public void startUpdate(#NonNull ViewGroup container) {
if (container.getId() == View.NO_ID) {
throw new IllegalStateException("ViewPager with adapter " + this + " requires a view id");
}
}
#Override #NonNull public Object instantiateItem(ViewGroup container, int position) {
long tag = getItemId(position);
Fragment fragment = fragmentList.get(tag);
// If we already have this item instantiated, there is nothing
// to do. This can happen when we are restoring the entire pager
// from its saved state, where the fragment manager has already
// taken care of restoring the fragments we previously had instantiated.
if (fragment != null) {
return fragment;
}
if (currentTransaction == null) {
currentTransaction = fragmentManager.beginTransaction();
}
fragment = getItem(position);
// restore state
final Fragment.SavedState savedState = savedStatesList.get(tag);
if (savedState != null) {
fragment.setInitialSavedState(savedState);
}
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
fragmentList.put(tag, fragment);
currentTransaction.add(container.getId(), fragment, "f" + tag);
return fragment;
}
#Override public void destroyItem(ViewGroup container, int position, #NonNull Object object) {
Fragment fragment = (Fragment) object;
int currentPosition = getItemPosition(fragment);
int index = fragmentList.indexOfValue(fragment);
long fragmentKey = -1;
if (index != -1) {
fragmentKey = fragmentList.keyAt(index);
fragmentList.removeAt(index);
}
//item hasn't been removed
if (fragment.isAdded() && currentPosition != POSITION_NONE) {
savedStatesList.put(fragmentKey, fragmentManager.saveFragmentInstanceState(fragment));
} else {
savedStatesList.remove(fragmentKey);
}
if (currentTransaction == null) {
currentTransaction = fragmentManager.beginTransaction();
}
currentTransaction.remove(fragment);
}
#Override public void setPrimaryItem(ViewGroup container, int position, #Nullable Object object) {
Fragment fragment = (Fragment) object;
if (fragment != currentPrimaryItem) {
if (currentPrimaryItem != null) {
currentPrimaryItem.setMenuVisibility(false);
currentPrimaryItem.setUserVisibleHint(false);
}
if (fragment != null) {
fragment.setMenuVisibility(true);
fragment.setUserVisibleHint(true);
}
currentPrimaryItem = fragment;
}
}
#Override public void finishUpdate(ViewGroup container) {
if (currentTransaction != null) {
currentTransaction.commitNowAllowingStateLoss();
currentTransaction = null;
}
}
#Override public boolean isViewFromObject(#NonNull View view, #NonNull Object object) {
return ((Fragment) object).getView() == view;
}
#Override public Parcelable saveState() {
Bundle state = null;
if (savedStatesList.size() > 0) {
// save Fragment states
state = new Bundle();
long[] stateIds = new long[savedStatesList.size()];
for (int i = 0; i < savedStatesList.size(); i++) {
Fragment.SavedState entry = savedStatesList.valueAt(i);
stateIds[i] = savedStatesList.keyAt(i);
state.putParcelable(Long.toString(stateIds[i]), entry);
}
state.putLongArray("states", stateIds);
}
for (int i = 0; i < fragmentList.size(); i++) {
Fragment f = fragmentList.valueAt(i);
if (f != null && f.isAdded()) {
if (state == null) {
state = new Bundle();
}
String key = "f" + fragmentList.keyAt(i);
fragmentManager.putFragment(state, key, f);
}
}
return state;
}
#Override public void restoreState(#Nullable Parcelable state, ClassLoader loader) {
if (state != null) {
Bundle bundle = (Bundle) state;
bundle.setClassLoader(loader);
long[] fss = bundle.getLongArray("states");
savedStatesList.clear();
fragmentList.clear();
if (fss != null) {
for (long fs : fss) {
savedStatesList.put(fs, bundle.getParcelable(Long.toString(fs)));
}
}
Iterable<String> keys = bundle.keySet();
for (String key : keys) {
if (key.startsWith("f")) {
Fragment f = fragmentManager.getFragment(bundle, key);
if (f != null) {
f.setMenuVisibility(false);
fragmentList.put(Long.parseLong(key.substring(1)), f);
} else {
Timber.w("Bad fragment at key %s", key);
}
}
}
}
}
/**
* Return a unique identifier for the item at the given position.
* <p>
* <p>The default implementation returns the given position.
* Subclasses should override this method if the positions of items can change.</p>
*
* #param position Position within this adapter
* #return Unique identifier for the item at position
*/
public long getItemId(int position) {
return position;
}
}
This is the implementation of adapter
class FolderPagerAdapter extends UpdatableFragmentPagerAdapter {
private final FragmentManager fragmentManager;
// Sparse array to keep track of registered fragments in memory
private List<Fragment> addedFragments;
FolderPagerAdapter(FragmentManager fm) {
super(fm);
this.fragmentManager = fm;
}
void init() {
if (addedFragments == null) {
addedFragments = new ArrayList<>();
}
addedFragments.clear();
addedFragments.add(CollectionsListFragment.newInstance());
notifyDataSetChanged();
}
#Override public Fragment getItem(int position) {
return addedFragments.get(position);
}
#Override public long getItemId(int position) {
return addedFragments.get(position).hashCode();
}
#Override public int getCount() {
return addedFragments.size();
}
//this is called when notifyDataSetChanged() is called
#Override public int getItemPosition(Object object) {
//// refresh all fragments when data set changed
Fragment fragment = (Fragment) object;
if (fragment instanceof CollectionFragment) {
return POSITION_UNCHANGED;
} else {
int hashCode = fragment.hashCode();
for (int i = 0; i < addedFragments.size(); i++) {
if (addedFragments.get(i).hashCode() == hashCode) {
return i;
}
}
}
return PagerAdapter.POSITION_NONE;
}
void removeLastPage() {
addedFragments.remove(addedFragments.size() - 1);
notifyDataSetChanged();
}
void addCollectionFragment(CollectionFragment collectionFragment) {
addedFragments.add(collectionFragment);
notifyDataSetChanged();
}
void addFolderFragment(FolderFragment folderFragment) {
addedFragments.add(folderFragment);
notifyDataSetChanged();
}
void restoreFragments(List<PagerFolderCollectionModel> pagesList) {
if (!pagesList.isEmpty()) {
for (int i = 0; i < pagesList.size(); i++) {
if (i == 0) {
addedFragments.add(CollectionFragment.newInstance(pagesList.get(0).getItemId()));
} else {
addedFragments.add(FolderFragment.newInstance(pagesList.get(i).getItemName()));
}
}
notifyDataSetChanged();
}
}
void removeAll() {
addedFragments.clear();
notifyDataSetChanged();
}
}
and a holder pojo which i am using to save in onsaveinstancestate in activity and restore on rotation
public class PagerFolderCollectionModel implements Parcelable {
public static final Parcelable.Creator<PagerFolderCollectionModel> CREATOR =
new Parcelable.Creator<PagerFolderCollectionModel>() {
#Override public PagerFolderCollectionModel createFromParcel(Parcel source) {
return new PagerFolderCollectionModel(source);
}
#Override public PagerFolderCollectionModel[] newArray(int size) {
return new PagerFolderCollectionModel[size];
}
};
private String itemId;
private String itemName;
public PagerFolderCollectionModel(String itemId, String itemName) {
this.itemId = itemId;
this.itemName = itemName;
}
protected PagerFolderCollectionModel(Parcel in) {
this.itemId = in.readString();
this.itemName = in.readString();
}
public String getItemId() {
return itemId;
}
public String getItemName() {
return itemName;
}
#Override public int describeContents() {
return 0;
}
#Override public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.itemId);
dest.writeString(this.itemName);
}
}
onsaveinstance method in activity
#Override protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(STATE_SELECTED_OPTION, selectedDrawerOption);
outState.putBoolean(STATE_SHOW_GRID_OPTION, isShowGridOption);
outState.putParcelableArrayList(STATE_SHOWN_FRAGMENTS,
(ArrayList<PagerFolderCollectionModel>) adapteritemslist);
Timber.e("save");
}
The requirement is that the first item in the adapter is always collection fragment and folder fragments are added and removed on demand (tap or swipe back)
Is there a solution for this (implementing pager adapter differently, using custom views in adapter...)? Does anybody knows how this is done in Dropbox app?
Can you please try to use the
setRetainInstance(boolean retain)
in the onCreateView method of your fragment. Set it to true.
It Control whether a fragment instance is retained across Activity re-creation (such as from a configuration change).
Have a look at this resources:
How does viewPager retain fragment states on orientation change?
Fragment in ViewPager on Fragment doesn't reload on orientation change
https://stackoverflow.com/a/27316052/2930101
Try to search for the fragments in the fragment manager first when you return them in the ViewPager.
If you manage to solve your issue I would be interested in your solution!
I have managed to fix this by editing this code
#Override public long getItemId(int position) {
return addedFragments.get(position).hashCode();
}
The issue was that the hashCode is generated and different on each rotation and since the requirement is not to change the position of pages i have just removed this method. It works now as expected, you can add and remove fragments with restoring state on orientation change.

Android - Fragment inside a Fragment inside a ViewPager problems

In my application I have a View pager that rotates 4 Fragments. Some of those fragments have other fragments inside them. The first time Viewpager starts a Fragment it works fine, but if I rotate around and return to the previous fragment, the inner fragments fail to show ( and I have noticed that getActivity() in them returns null).
For example, the app starts, I move through viewpager and see all the main fragments and they appear fine, then I scroll back, every main fragment that has inner fragments does not show properly.
I use "FragmentStatePagerAdapter" for my ViewPager and the code is below (variables names are for example).
private class ContentPagerAdapter extends FragmentStatePagerAdapter {
public ContentPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Parcelable saveState() {
return null;
}
#Override
public Fragment getItem(int pos) {
Fragment fragment = null;
try {
if (pos == POS1) {
if (fragment1 == null) {
fragment1 = Fragment1.newInstance();
}
fragment = fragment1;
} else if (pos == POS2) {
if (fragment2 == null) {
fragment2 = Fragment2.newInstance(something);
}
fragment = fragment2;
} else if (pos == POS3) {
if ( fragment3 == null ) {
fragment3 = Fragment3.newInstance(something3);
}
fragment = fragment3;
} else { //default
if (fragment4 == null) {
fragment4 = Fragment4.newInstance(something4);
}
fragment = fragment4;
}
}catch (Exception e) {
Log.e(AppConstants.APP_TAG, "Cannot create fragment", e);
finish();
}
return fragment;
}
#Override
public int getCount() {
int count = 4;
return count;
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
}
When instantiating the PagerAdapter, use getChildFragmentManager() instead of getFragmentManager(). If more info needed, similiar problem was solved here ViewPager in TabFragment not loading second time

ViewPager, FragmentStatePagerAdapter and Infinite Scrolling

What I'm trying to achieve:
An Activity with a ViewPager that displays Fragments for a list of Objects in the Adapter (FragmentStatePagerAdapter).
Initially the Activity loads N (lets say 5) objects from the SQLite DB into the Adapter. These objects are chosen with some randomness.
When the user is reaching the end of the list, the activity shall load M (let M be 3) more objects from the DB, add them to the adapter and call notifyDataSetChanged(). When adding them, I check if the new Objects already exist in the list and if they do, the pre-existing one gets removed and the loaded one gets added to the list's tail.
Thus, I'm trying to achieve something like an infinite scrolling ViewPager (NOT a "circular" ViewPager as I want new Objects to be fetched constantly, instead of going back to the begging of the list).
I have some working code and included a sample for the pattern I'm following down bellow. However, I keep getting this exception and have no idea why:
IllegalStateException: Fragment MyObjectFragment{id...} is not currently in the FragmentManager
at android.app.FragmentManagerImpl.saveFragmentInstanceState(FragmentManager.java:553)
at android.support.v13.app.FragmentStatePagerAdapter.destroyItem(FragmentStatePagerAdapter.java:140)
at android.support.v4.ViewPager.populate(ViewPager.java:1002)
...
Code Sample:
The Activity:
public class MyActivitty extends FragmentActivity {
public MyPagerAdapter adapter;
public ViewPager pager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_acyivity_layout);
pager = (ViewPager) findViewById(R.id.viewPager);
ArrayList<MyObject> myObjects = new ArrayList<MyObject>();
// loadInitialObjectsFromDB(int N) goes to SQLite DB and loads the N first objects to show on the ViewPager...
myObjects = loadInitialObjectsFromDB(5);
// Adapter will use the previously fetched objects
adapter = new MyPagerAdapter(this, getFragmentManager(), myObjects);
pager.setAdapter(adapter);
pager.setOffscreenPageLimit(2);
// (...)
}
// (...)
}
The PagerAdapter:
public class MyPagerAdapter extends FragmentStatePagerAdapter implements
ViewPager.OnPageChangeListener {
private MyActivity context;
private ArrayList<MyObject> objectList;
private int currentPosition = 0;
// (...)
public MyPagerAdapter(MyActivity context, FragmentManager fragmentManager, ArrayList<MyObject> objects)
{
super(fragmentManager);
this.context = context;
this.objectList = objects;
}
#Override
public Fragment getItem(int position)
{
MyObject object = objectList.get(position);
return MyObjectFragment.newInstance(position, object);
}
#Override
public int getItemPosition(Object object){
MyObjectFragment frag = (MyObjectFragment) object;
MyObject object = frag.getMyObject();
for(int i = 0; i < objectList.size(); i++)
{
if(objectList.get(i).getId() == object.getId())
{
return i;
}
}
return PagerAdapter.POSITION_NONE;
}
#Override
public int getCount()
{
return objectList.size();
}
#Override
public void onPageSelected(int position)
{
currentPosition = position;
}
// (...)
}
#Override
public void onPageScrollStateChanged(int state)
{
switch(state)
{
case ViewPager.SCROLL_STATE_DRAGGING:
// (...)
break;
case ViewPager.SCROLL_STATE_IDLE:
// if we are reaching the "end" of the list (while scrolling to the right), load more objects
if(currentPosition <= position && position >= answerList.size()-1)
{
// Function in MyActivity that fetches N more objects.
// and adds them to this adapter's ArrayList<MyObject> objectList
// it checks for duplicates so that if the fetched object was already in the back of the list, it is shown again
context.getMoreObjects(3);
notifyDataSetChanged();
}
getMoreQuestions(currentPosition);
case ViewPager.SCROLL_STATE_SETTLING:
break;
}
}
My Fragment:
public class MyObjectFragment extends Fragment {
// Object to represent
private MyObject object;
public static Fragment newInstance(MyActivity context,
int position, MyObject object) {
MyObjectFragment frag = new MyObjectFragment();
Bundle args = new Bundle();
args.putParcelable("Object", object);
frag.setArguments(args);
return frag;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
this.currentInflater = inflater;
final View rootView = inflater.inflate(
R.layout.fragment_my_object, container, false);
// get object from Bundle, set UI, events, etc...
}
// (...)
}
Any idea on why am I getting this Exception? It seems like the FragmentStatePagerAdapter is trying to destroy an item that no longer exists, but I don't understand why.
EDIT 1:
If I comment my #Override getItemPosition(Object object), I don't get the exception anymore. However, I need to override getItemPosition because there is a use case in which the user deletes the currently shown Object causing it to disappear from the adapter's array and forcing the getItemPosition to return POSITION_NONE if the item doesn't exist anymore.
EDIT 2:
Now I do know that this exception only happens when I remove items from my adapter's objectList. I have two situations where MyObject instances are deleted from the objectList:
When the getMoreObjects() adds fetches an object from the DB that was already in the objectList, I delete it and re-add it to the head of the list. I do this to avoid having objects with the same Id in the objectList, as their Id is used by the getItemPosition() to know if they exist and their position.
Before returning, getMoreObjects(), removes the N first objects from the list. I do know that the FragmentStatePagerAdapter already saves memory by only keeping in memory fragments for some of the objects, but I still would like to avoid growing my objectList too much. For now, I have this line commented, as it's not that important.
Solved with the help of this question which itself points at this issue.
FragmentStatePagerAdapter caches the fragments and their saved states in two ArrayLists: mFragments and mSavedState. But when the fragments' order changes (as could happen in my case), there's no mechanism for reordering the elements of mFragments and mSavedState. Therefore, the adapter will provide the wrong fragments to the pager.
I've adapted the code provided in that and changed the import from app.support.v4.Fragment to android.app.Fragment.
public abstract class MyFragmentStatePagerAdapter extends PagerAdapter {
private static final String TAG = "FragmentStatePagerAdapter";
private static final boolean DEBUG = true;
private final FragmentManager mFragmentManager;
private FragmentTransaction mCurTransaction = null;
private long[] mItemIds = new long[] {};
private ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();
private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
private Fragment mCurrentPrimaryItem = null;
public MyFragmentStatePagerAdapter(FragmentManager fm) {
mFragmentManager = fm;
mItemIds = new long[getCount()];
for (int i = 0; i < mItemIds.length; i++) {
mItemIds[i] = getItemId(i);
}
}
/**
* Return the Fragment associated with a specified position.
*/
public abstract Fragment getItem(int position);
/**
* Return a unique identifier for the item at the given position.
*/
public int getItemId(int position) {
return position;
}
#Override
public void notifyDataSetChanged() {
long[] newItemIds = new long[getCount()];
for (int i = 0; i < newItemIds.length; i++) {
newItemIds[i] = getItemId(i);
}
if (!Arrays.equals(mItemIds, newItemIds)) {
ArrayList<Fragment.SavedState> newSavedState = new ArrayList<Fragment.SavedState>();
ArrayList<Fragment> newFragments = new ArrayList<Fragment>();
for (int oldPosition = 0; oldPosition < mItemIds.length; oldPosition++) {
int newPosition = POSITION_NONE;
for (int i = 0; i < newItemIds.length; i++) {
if (mItemIds[oldPosition] == newItemIds[i]) {
newPosition = i;
break;
}
}
if (newPosition >= 0) {
if (oldPosition < mSavedState.size()) {
Fragment.SavedState savedState = mSavedState.get(oldPosition);
if (savedState != null) {
while (newSavedState.size() <= newPosition) {
newSavedState.add(null);
}
newSavedState.set(newPosition, savedState);
}
}
if (oldPosition < mFragments.size()) {
Fragment fragment = mFragments.get(oldPosition);
if (fragment != null) {
while (newFragments.size() <= newPosition) {
newFragments.add(null);
}
newFragments.set(newPosition, fragment);
}
}
}
}
mItemIds = newItemIds;
mSavedState = newSavedState;
mFragments = newFragments;
}
super.notifyDataSetChanged();
}
#Override
public void startUpdate(ViewGroup container) {
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
// If we already have this item instantiated, there is nothing
// to do. This can happen when we are restoring the entire pager
// from its saved state, where the fragment manager has already
// taken care of restoring the fragments we previously had instantiated.
if (mFragments.size() > position) {
Fragment f = mFragments.get(position);
if (f != null) {
return f;
}
}
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
Fragment fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
if (mSavedState.size() > position) {
Fragment.SavedState fss = mSavedState.get(position);
if (fss != null) {
fragment.setInitialSavedState(fss);
}
}
while (mFragments.size() <= position) {
mFragments.add(null);
}
fragment.setMenuVisibility(false);
mFragments.set(position, fragment);
mCurTransaction.add(container.getId(), fragment);
return fragment;
}
public void destroyItemState(int position) {
mFragments.remove(position);
mSavedState.remove(position);
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment) object;
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
//position = getItemPosition(object);
if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
+ " v=" + ((Fragment)object).getView());
if (position >= 0) {
while (mSavedState.size() <= position) {
mSavedState.add(null);
}
mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment));
if(position < mFragments.size()){
mFragments.set(position, null);
}
}
mCurTransaction.remove(fragment);
}
#Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment)object;
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
mCurrentPrimaryItem.setMenuVisibility(false);
}
if (fragment != null) {
fragment.setMenuVisibility(true);
}
mCurrentPrimaryItem = fragment;
}
}
#Override
public void finishUpdate(ViewGroup container) {
if (mCurTransaction != null) {
mCurTransaction.commitAllowingStateLoss();
mCurTransaction = null;
mFragmentManager.executePendingTransactions();
}
}
#Override
public boolean isViewFromObject(View view, Object object) {
return ((Fragment)object).getView() == view;
}
#Override
public Parcelable saveState() {
Bundle state = new Bundle();
if (mItemIds.length > 0) {
state.putLongArray("itemids", mItemIds);
}
if (mSavedState.size() > 0) {
Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];
mSavedState.toArray(fss);
state.putParcelableArray("states", fss);
}
for (int i=0; i<mFragments.size(); i++) {
Fragment f = mFragments.get(i);
if (f != null) {
String key = "f" + i;
mFragmentManager.putFragment(state, key, f);
}
}
return state;
}
#Override
public void restoreState(Parcelable state, ClassLoader loader) {
if (state != null) {
Bundle bundle = (Bundle)state;
bundle.setClassLoader(loader);
mItemIds = bundle.getLongArray("itemids");
if (mItemIds == null) {
mItemIds = new long[] {};
}
Parcelable[] fss = bundle.getParcelableArray("states");
mSavedState.clear();
mFragments.clear();
if (fss != null) {
for (int i=0; i<fss.length; i++) {
mSavedState.add((Fragment.SavedState)fss[i]);
}
}
Iterable<String> keys = bundle.keySet();
for (String key: keys) {
if (key.startsWith("f")) {
int index = Integer.parseInt(key.substring(1));
Fragment f = mFragmentManager.getFragment(bundle, key);
if (f != null) {
while (mFragments.size() <= index) {
mFragments.add(null);
}
f.setMenuVisibility(false);
mFragments.set(index, f);
} else {
Log.w(TAG, "Bad fragment at key " + key);
}
}
}
}
}
}
Credit for the original code goes to user #UgglyNoodle.
Then, instead of using FragmentStatePagerAdapter I use the MyFragmentStatePagerAdapter from above and override getItemPosition() and getItemId() consistently with getItem().

FragmentPagerAdapter is not removing items (Fragments) correctly

I have implemented the FragmentPagerAdapter and and using a List<Fragment> to hold all fragments for my ViewPager to display. On addItem() I simply add an instantiated Fragment and then call notifyDataSetChanged(). I am not sure if this is necessary or not.
My problem simply...
start with fragment 1
[Fragment 1]
add new Fragment 2
[Fragment 1] [Fragment 2]
remove Fragment 2
[Fragment 1]
add new Fragment 3
[Fragment 1] [Fragment 2]
When adding new fragments everything seems great. Once I remove a fragment and then add a newly instantiated fragment the old fragment is still displayed. When i go a .getClass.getName() it is giving me Fragment 3's name however I still see Fragment 2.
I believe this might be an issue with instantiateItem() or such but I thought the adapter was to handle that for us. Any suggestions would be great.
adapter code...
public class MyPagerAdapter extends FragmentPagerAdapter {
public final ArrayList<Fragment> screens2 = new ArrayList<Fragment>();
private Context context;
public MyPagerAdapter(FragmentManager fm, Context context) {
super(fm);
this.context = context;
}
public void removeType(String name){
for(Fragment f: screens2){
if(f.getClass().getName().contains(name)){ screens2.remove(f); return; }
}
this.notifyDataSetChanged();
}
public boolean addSt(String tag, Class<?> clss, Bundle args){
if(clss==null) return false;
if(!clss.getName().contains("St")) return false;
if(!args.containsKey("cId")) return false;
boolean has = false;
boolean hasAlready = false;
for(Fragment tab: screens2){
if(tab.getClass().getName().contains("St")){
has = true;
if(tab.getArguments().containsKey("cId"))
if(tab.getArguments().getLong("cId") == args.getLong("cId")){
hasAlready = true;
}
if(!hasAlready){
// exists but is different so replace
screens2.remove(tab);
this.addScreen(tag, clss, args, C.PAGE_ST);
// if returned true then it notifies dataset
return true;
}
}
hasAlready = false;
}
if(!has){
// no st yet exist in adapter
this.addScreen(tag, clss, args, C.PAGE_ST);
return true;
}
return false;
}
public boolean removeCCreate(){
this.removeType("Create");
return false;
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE; //To make notifyDataSetChanged() do something
}
public void addCCreate(){
this.removeCCreate();
Log.w("addding c", " ");
this.addScreen("Create C", CreateCFragment.class, null, C.PAGE_CREATE_C);
}
public void addScreen(String tag, Class<?> clss, Bundle args, int type){
if(clss!=null){
screens2.add(Fragment.instantiate(context, clss.getName(), args));
}
}
#Override
public int getCount() {
return screens2.size();
}
#Override
public Fragment getItem(int position) {
return screens2.get(position);
}
}
I realize the code uses some "ghetto" means of determining the fragment type however I wrote this code strictly for testing functionality. Any help or ideas would be great as it seems that not many people ventured into the world of FragmentPagerAdapters.
I got same problem,and my solution was overring the method "destroyItem" as following.
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
FragmentManager manager = ((Fragment)object).getFragmentManager();
FragmentTransaction trans = manager.beginTransaction();
trans.remove((Fragment)object);
trans.commit();
}
It's work for me,does anybody have another solutions?
Updated:
I found those code made Fragment removed unnecessary,so I added a condition to avoid it.
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (position >= getCount()) {
FragmentManager manager = ((Fragment) object).getFragmentManager();
FragmentTransaction trans = manager.beginTransaction();
trans.remove((Fragment) object);
trans.commit();
}
}
Updated this post and included my solution (if someone can improve let me know)
Ok i've now solved my problem in a hackish way, but yeah it's working ;). If someone can improve my solution please let me know. For my new solution i now use a CustomFragmentStatePagerAdapter but it doesn't save the state like it should and stores all the Fragments in a list. This can cause a memory problem if the user has more than 50 fragments, like the normal FragmentPagerAdapter does. It would be great if someone can add the State-thing back to my solution without removing my fixes. Thanks.
So here's my CustomFragmentStatePagerAdapter.java
package com.tundem.webLab.Adapter;
import java.util.ArrayList;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.PagerAdapter;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
public abstract class CustomFragmentStatePagerAdapter extends PagerAdapter {
private static final String TAG = "FragmentStatePagerAdapter";
private static final boolean DEBUG = false;
private final FragmentManager mFragmentManager;
private FragmentTransaction mCurTransaction = null;
public ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();
public ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
private Fragment mCurrentPrimaryItem = null;
public CustomFragmentStatePagerAdapter(FragmentManager fm) {
mFragmentManager = fm;
}
/**
* Return the Fragment associated with a specified position.
*/
public abstract Fragment getItem(int position);
#Override
public void startUpdate(ViewGroup container) {}
#Override
public Object instantiateItem(ViewGroup container, int position) {
// If we already have this item instantiated, there is nothing
// to do. This can happen when we are restoring the entire pager
// from its saved state, where the fragment manager has already
// taken care of restoring the fragments we previously had instantiated.
// DONE Remove of the add process of the old stuff
/* if (mFragments.size() > position) { Fragment f = mFragments.get(position); if (f != null) { return f; } } */
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
Fragment fragment = getItem(position);
if (DEBUG)
Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
if (mSavedState.size() > position) {
Fragment.SavedState fss = mSavedState.get(position);
if (fss != null) {
try // DONE: Try Catch
{
fragment.setInitialSavedState(fss);
} catch (Exception ex) {
// Schon aktiv (kA was das heißt xD)
}
}
}
while (mFragments.size() <= position) {
mFragments.add(null);
}
fragment.setMenuVisibility(false);
mFragments.set(position, fragment);
mCurTransaction.add(container.getId(), fragment);
return fragment;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment) object;
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
mCurTransaction.remove(fragment);
/*if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object + " v=" + ((Fragment)
* object).getView()); while (mSavedState.size() <= position) { mSavedState.add(null); } mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment));
* mFragments.set(position, null); mCurTransaction.remove(fragment); */
}
#Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment) object;
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
mCurrentPrimaryItem.setMenuVisibility(false);
}
if (fragment != null) {
fragment.setMenuVisibility(true);
}
mCurrentPrimaryItem = fragment;
}
}
#Override
public void finishUpdate(ViewGroup container) {
if (mCurTransaction != null) {
mCurTransaction.commitAllowingStateLoss();
mCurTransaction = null;
mFragmentManager.executePendingTransactions();
}
}
#Override
public boolean isViewFromObject(View view, Object object) {
return ((Fragment) object).getView() == view;
}
#Override
public Parcelable saveState() {
Bundle state = null;
if (mSavedState.size() > 0) {
state = new Bundle();
Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];
mSavedState.toArray(fss);
state.putParcelableArray("states", fss);
}
for (int i = 0; i < mFragments.size(); i++) {
Fragment f = mFragments.get(i);
if (f != null) {
if (state == null) {
state = new Bundle();
}
String key = "f" + i;
mFragmentManager.putFragment(state, key, f);
}
}
return state;
}
#Override
public void restoreState(Parcelable state, ClassLoader loader) {
if (state != null) {
Bundle bundle = (Bundle) state;
bundle.setClassLoader(loader);
Parcelable[] fss = bundle.getParcelableArray("states");
mSavedState.clear();
mFragments.clear();
if (fss != null) {
for (int i = 0; i < fss.length; i++) {
mSavedState.add((Fragment.SavedState) fss[i]);
}
}
Iterable<String> keys = bundle.keySet();
for (String key : keys) {
if (key.startsWith("f")) {
int index = Integer.parseInt(key.substring(1));
Fragment f = mFragmentManager.getFragment(bundle, key);
if (f != null) {
while (mFragments.size() <= index) {
mFragments.add(null);
}
f.setMenuVisibility(false);
mFragments.set(index, f);
} else {
Log.w(TAG, "Bad fragment at key " + key);
}
}
}
}
}
}
Here's my normal FragmentAdapter.java
package com.tundem.webLab.Adapter;
import java.util.LinkedList;
import java.util.List;
import android.support.v4.app.FragmentManager;
import com.tundem.webLab.fragments.BaseFragment;
import com.viewpagerindicator.TitleProvider;
public class FragmentAdapter extends CustomFragmentStatePagerAdapter implements TitleProvider {
public List<BaseFragment> fragments = new LinkedList<BaseFragment>();
private int actPage;
public FragmentAdapter(FragmentManager fm) {
super(fm);
}
public void setActPage(int actPage) {
this.actPage = actPage;
}
public void addItem(BaseFragment fragment) {
// TODO if exists don't open / change to that tab
fragments.add(fragment);
}
public BaseFragment getActFragment() {
return getItem(getActPage());
}
public int getActPage() {
return actPage;
}
#Override
public BaseFragment getItem(int position) {
if (position < getCount()) {
return fragments.get(position);
} else
return null;
}
#Override
public int getCount() {
return fragments.size();
}
#Override
public String getTitle(int position) {
return fragments.get(position).getTitle();
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
}
And this is the way i delete a Fragment. (I know it's a bit more than only .remove() ). Be free to improve my solution, you can also add this code somewhere in the adapter so yeah. It's up to the user who tries to implement this. I use this in my TabHelper.java (A class which handles all tab operations like delete, add, ...)
int act = Cfg.mPager.getCurrentItem();
Cfg.mPager.removeAllViews();
Cfg.mAdapter.mFragments.remove(act);
try {
Cfg.mAdapter.mSavedState.remove(act);
} catch (Exception ex) {/* Already removed */}
try {
Cfg.mAdapter.fragments.remove(act);
} catch (Exception ex) {/* Already removed */}
Cfg.mAdapter.notifyDataSetChanged();
Cfg.mIndicator.notifyDataSetChanged();
Description of the Cfg. thing. I save the reference to those objects in a cfg, class so i can always use them without the need of a special Factory.java ...
Yeah. i hope i was able to help. Feel free to improve this, but let me know so i can improve my code too.
Thanks.
If i missed any code let me know.
My old answer also works but only if you have different Fragments. FileFragment, WebFragment, ... Not if you use one of those fragmenttypes twice.
I got it pseudo working for now. It's a really dirty solution and i'm still searching for a better one. Please help.
I changed the code, where i delete a tab to this one:
public static void deleteActTab()
{
//We set this on the indicator, NOT the pager
int act = Cfg.mPager.getCurrentItem();
Cfg.mAdapter.removeItem(act);
List<BaseFragment> frags = new LinkedList<BaseFragment>();
frags = Cfg.mAdapter.fragments;
Cfg.mPager = (ViewPager)Cfg.act.findViewById(R.id.pager);
Cfg.mPager.setAdapter(Cfg.mAdapter);
Cfg.mIndicator.setViewPager(Cfg.mPager);
Cfg.mAdapter.fragments = frags;
if(act > 0)
{
Cfg.mPager.setCurrentItem(act-1);
Cfg.mIndicator.setCurrentItem(act-1);
}
Cfg.mIndicator.notifyDataSetChanged();
}
If someone can improve this code let me know. If someone can tell us the real answer for that problem. please add it here. There are many many people who experience this issue. I added a reputation of 50 for the one who solve it. I can also give a donation for the one who solve it.
Thanks
Maybe this answer helps you.
Use FragmentStatePagerAdapter instead of FragmentPagerAdapter.
Because FragmentPagerAdapter does not destory the views. For more information read this answer.
Taking "the best of both worlds" (I mean the answers by #Tericky Shih and #mikepenz) we have it short and simple:
public class MyPagerAdapter extends FragmentPagerAdapter {
public ArrayList<Fragment> fragments = new ArrayList<Fragment>();
...
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
if (position >= getCount()) fm.beginTransaction().remove((Fragment) object).commit();
}
#Override
public int getItemPosition(Object object) {
if (fragments.contains(object)) return fragments.indexOf(object);
else return POSITION_NONE;
}
}
The main difference is that if some fragment is not changed you don't have to destroy its view and don't have to return POSITION_NONE for it. At the same time, I've encountered a situation when ViewPager was holding a reference to an item which was already destroyed, therefore the check if (fragments.contains(object)) helps to determine if that item is not needed anymore.
I had a situation similar to yours. I recently needed to add and remove Fragments from the ViewPager. In the first mode, I have Fragments 0, 1, and 2 and in the second mode I have Fragments 0 and 3. I want Fragment 0 to be same for both modes and hold over information.
All I needed to do was override FragmentPagerAdapter.getItemId to make sure that I returned a unique number for each different Fragment - the default is to return "position". I also had to set the adapter in the ViewPager again - a new instance will work but I set it back to the same instance. Setting the adapter results in the ViewPager removing all the views and trying to add them again.
However, the trick is that the adapter only calls getItem when it wants to instantiate the Fragment - not every time it shows it. This is because they are cached and looks them up by the "position" returned by getItemId.
Imagine you have three Fragments (0, 1 and 2) and you want to remove "1".
If you return "position" for getItemId then removing Fragment 1 will not work because when you try to show Fragment 2 after deleting Fragment 1 the pager/adapter will think it's already got the Fragment for that "position" and will continue to display Fragment 1.
FYI: I tried notifyDataSetChanged instead of setting the adapter but it didn't work for me.
First, the getItemId override example and what I did for my getItem:
public class SectionsPagerAdapter extends FragmentPagerAdapter
{
...
#Override
public long getItemId(int position)
{
// Mode 1 uses Fragments 0, 1 and 2. Mode 2 uses Fragments 0 and 3
if ( mode == 2 && position == 1 )
return 3;
return position;
}
#Override
public Fragment getItem(int position)
{
if ( mode == 1 )
{
switch (position)
{
case 0:
return <<fragment 0>>;
case 1:
return <<fragment 1>>;
case 2:
return <<fragment 2>>;
}
}
else // Mode 2
{
switch (position)
{
case 0:
return <<fragment 0>>;
case 1:
return <<fragment 3>>;
}
}
return null;
}
}
Now the change of mode:
private void modeChanged(int newMode)
{
if ( newMode == mode )
return;
mode = newMode;
// Calling mSectionsPagerAdapter.notifyDataSetChanged() is not enough here
mViewPager.setAdapter(mSectionsPagerAdapter);
}
Didn't work out for me. My solution was put FragmentStatePagerAdapter.java in my project, renamed to FragmentStatePagerAdapter2.java.
In destroyItem(), I changed a little based on error logs. From
// mFragments.set(position, null);
to
if (position < mFragments.size())mFragments.remove(position);
Maybe you don't have the same problem, just check the log.Hope this helps someone!
After a lot of trying, i got it to work so that it removes or attaches a third fragment at the end position correctly.
Object fragments[] = new Object[3];
int mItems = 2;
MyAdapter mAdapter;
ViewPager mPager;
public void addFragment(boolean bool) {
mAdapter.startUpdate(mPager);
if (!bool) {
mAdapter.destroyItem(mPager, 2, fragments[2]);
mItems = 2;
fNach = false;
}
else if (bool && !fNach){
mItems = 3;
mAdapter.instantiateItem(mPager,2);
fNach = true;
}
mAdapter.finishUpdate(mPager);
mAdapter.notifyDataSetChanged();
}
public class MyAdapter extends FragmentPagerAdapter {
MyAdapter(FragmentManager fm) {
super(fm);
}
#Override
public int getCount() {
return mItems;
}
#Override
public CharSequence getPageTitle(int position) {
... (code for the PagerTitleStrip)
}
#Override
public Fragment getItem(int position) {
Fragment f = null;
switch (position) {
case 0:
f = new Fragment1();
break;
case 1:
f = new Fragment2();
break;
case 2:
f = new Fragment3();
break;
}
return f;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Object o = super.instantiateItem(container,position);
fragments[position] = o;
return o;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
System.out.println("Destroy item " + position);
if (position >= getCount()) {
FragmentManager manager = ((Fragment) object).getFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
ft.remove((Fragment) object);
ft.commit();
}
}
}
Some clarification: to get the object reference to call destroyItem, i stored the objects returned from instantiateItem in an Array. When you are adding or removing fragments, you have to announce it with startUpdate, finishUpdate and notifyDataSetChanged. The number of items have to be changed manually, for adding you increase it and instantiate it, getItem creates it then. For deletion, you call destroyItem, and in this code, it is essential the position >= mItems, because destroyItem is also called if a fragment goes out of the cache. You don't want to delete it then. The only thing which doesn't work is the swiping animation. After removing the last page, the "cannot swipe left" animation is not restored correctly on the new last page. If you swipe it, a blank page is shown and it bounces back.
The real problem is that FragmentPagerAdapter uses the position of the fragment in your list as ID. So if you add a new List or simply remove items "instantiateItem" item will find different fragments for new items in list...
#Override
public Object instantiateItem(ViewGroup container, int position) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
final long itemId = getItemId(position);
// Do we already have this fragment?
String name = makeFragmentName(container.getId(), itemId);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
mCurTransaction.attach(fragment);
} else {
fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));
}
if (fragment != mCurrentPrimaryItem) {
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
}
return fragment;
}
and
private static String makeFragmentName(int viewId, long id) {
return "android:switcher:" + viewId + ":" + id;
}
and
* Return a unique identifier for the item at the given position.
* <p>
* <p>The default implementation returns the given position.
* Subclasses should override this method if the positions of items can change.</p>
*
* #param position Position within this adapter
* #return Unique identifier for the item at position
*/
public long getItemId(int position) {
return position;
}
I have been having this same issue until I dawned on me, I was creating my PagerView from within another Fragment and not the main activity.
My solution was to pass the ChildFragment Manager into the constructor of the Fragment(State)PagerAdapter and not the Fragment Manager of the parent Fragment.
Using the ChildFragmentManager all the Fragments created by the ViewPagerAdapter are cleaned up automatically when the parent Fragment is destroyed.

Categories

Resources