Viewpager fragments not shown after the first time - android

I have an activity with 3 fragments (A, B, C). Fragment A consists of a ViewPager with 2 ListFragments. The user can tap on an item in any of the listfragments and by doing so, goes to fragment B.
In fragment A I do:
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
pagerAdapter = new PagerAdapter(getActivity().getSupportFragmentManager());
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragmentA, container, false);
vpPager = (ViewPager)view.findViewById(R.id.vpPager);
vpPager.setOffscreenPageLimit(2);
vpPager.setAdapter(pagerAdapter);
vpPager.addOnPageChangeListener(this);
return view;
}
And the PagerAdapter is as follows:
private class PagerAdapter extends FragmentPagerAdapter {
private final ListFragment1 lf1 = ListFragment1 .newInstance();
private final ListFragment2 lf2 = ListFragment2 .newInstance();
public PagerAdapter(android.support.v4.app.FragmentManager fm) {
super(fm);
}
#Override
public android.support.v4.app.Fragment getItem(int position) {
switch (position) {
case 0: return lf1;
case 1: return lf2;
default: return null;
}
}
}
The first time the activity is shown, the viewpager list fragments are displayed correctly.
The 2 viewpager fragments load data from a db, and I do this only once (when the fragments are created).
The user can tap on an item and fragment B is displayed. If the user presses Back, fragment A is shown. However the list fragments are not shown (already an instance of them still exists).
Could it be that the view has been destroyed, even though instances exist?
What is wrong here? Is there a better approach?
EDIT
If I use newInstance in the pager adapter, I get an IllegalStateException: not attached to activity. This is because I start an async task as follows:
#Override
public void onPageSelected(int position) {
Fragment fragment = pagerAdapter.getItem(position);
if (fragment instanceof IPagedFragment) {
((IPagedFragment) fragment).onShown();
}
}
And onShown is:
#Override
public void onShown() {
myTask= new MyTask();
myTask.execute((Void)null);
}
When can I start the task so that I can be 100% sure that the fragment is attached to the activity and that the view has been created (I need to get listview, etc. from the layout).

You have to use ChildFragmentManager like below.
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
pagerAdapter = new PagerAdapter(getChildFragmentManager()); //here used child fragment manager
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragmentA, container, false);
vpPager = (ViewPager)view.findViewById(R.id.vpPager);
vpPager.setOffscreenPageLimit(2);
vpPager.setAdapter(pagerAdapter);
vpPager.addOnPageChangeListener(this);
return view;
}
It works like charm in my code with viewpager and fragment.

Just now I solved it after struggling for whole day, by using getChildFragmentManager()
pass this as a parameter to the pagerAdapter. and it will work.
while using pagerAdapter in fragment use :
PagerAdapter adapter = new PagerAdapter(getChildFragmentManager());
and in case of activity use getFragmentManager()
PagerAdapter adapter = new PagerAdapter(getFragmentManager());

You're creating ListFragment1 and ListFragment2 using the Activity FragmentManager, while you should use the Fragment FragmentManager. So, modify the pagerAdapter = new PagerAdapter(getActivity().getSupportFragmentManager()); with pagerAdapter = new PagerAdapter(getChildFragmentManager());. In this way, the fragments of the view pager will be 'bound' to the fragment hosting the viewpager. Moreover, you should not keep any reference to fragments inside the viewpager: it's something that Android already manage. Try with:
private class PagerAdapter extends FragmentPagerAdapter {
public PagerAdapter(android.support.v4.app.FragmentManager fm) {
super(fm);
}
#Override
public android.support.v4.app.Fragment getItem(int position) {
switch (position) {
case 0: return ListFragment1.newInstance();
case 1: return ListFragment2.newInstance();
default: return null;
}
}
}
By the way, the vpPager.setOffscreenPageLimit(2); is unuseful since you have just 2 pages and this is a method that I've never used even when I have many fragments to manage, since it requires memory.
About your update: remove any logic related to ViewPager handling the fragment. If you need to start an AsyncTask within your Fragment, you can do it using one of the methods of Fragment lifecycle: onResume(), onCreateView() and so on.
class IPagedFragment extends Fragment {
public void onResume() {
super.onResume();
myTask= new MyTask();
myTask.execute((Void)null);
}
}
and please, remove the private final ListFragment1 lf1 = ListFragment1 .newInstance();. Trust me, it's not a good idea since you have a strong reference to your Fragments.
I've built a simple project that you can use as reference implementation. You can download the source code from my dropbox.

use getChildFragmentManager() instead of supportFragmentManager()

If any of the solutions above doesn't work, you can try a workaround by posting (delayed) to the pager view instance an additional notifyDataSetChanged call of the adapter:
vpPager.post(new Runnable() {
#Override
public void run() {
pagerAdapter.notifyDataSetChanged();
}
});
or
vpPager.postDelayed(new Runnable() {
#Override
public void run() {
pagerAdapter.notifyDataSetChanged();
}
}, 100 /* you have to find out the best delay time by trying/adjusting */);

Try overriding the getItemPosition method in your FragmentPagerAdapter:
#Override
public int getItemPosition(Object object) {
return PagerAdapter.POSITION_NONE;
}

If you experience this with Kotlin, it will be like this.
val fragmentAdapter = FragmentPageAdapter(childFragmentManager)

You shouldn't keep references to fragments in your FragmentPagerAdapter. You should always call newInstance in getItem() call, for example:
#Override
public android.support.v4.app.Fragment getItem(int position) {
switch (position) {
case 0: return ListFragment1.newInstance();
case 1: return ListFragment2.newInstance();
default: return null;
}
}
The data you load from the database should be stored in the fragment itself. The adapter will restore the state of fragments (setOffscreenPageLimit(2)).
You are losing your fragments because the items (fragments) are instantiated by the FragmentManager you provide, and it creates fragments based on tags. So it can happen that it creates a new instance of the fragment you already keep, just with different tag.
See FragmentPagerAdapter source code (check instantiateItem() method):
https://android.googlesource.com/platform/frameworks/support/+/refs/heads/master/v13/java/android/support/v13/app/FragmentPagerAdapter.java
Also see this answer:
keep instances of fragments inside FragmentPagerAdapter

On PagerAdapter class override the method setPrimaryItem,
which is called when there's a change in the pager, i would give it a shot.
I would create something like :
private class PagerAdapter extends FragmentPagerAdapter {
private final ListFragment1 lf1 = ListFragment1 .newInstance();
private final ListFragment2 lf2 = ListFragment2 .newInstance();
public PagerAdapter(android.support.v4.app.FragmentManager fm) {
super(fm);
}
#Override
public android.support.v4.app.Fragment getItem(int position) {
switch (position) {
case 0: return lf1;
case 1: return lf2;
default: return null;
}
}
#Override
public int getCount() {
return 2;
}
#Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
super.setPrimaryItem(container, position, object);
if (position == 0)
lf1.updateUI(); //Refresh what you need on this fragment
else if (position == 1)
lf2.updateUI();
}
}
You're missing getCount() as well.
I'm not sure offscreen has any use, but its probably not an issue. vpPager.setOffscreenPageLimit(2)
One more thing, i would also remove vpPager.addOnPageChangeListener(this), there's no use for this, an it might cause you some issues.
Whatever you need to do, you can pull it off without it, by overriding the pagination, you might "ruin" some of the standard pagination(since the super isn't called)

Related

Using setCurrentItem when slidetab is inside a fragment

My application has to swipe in between fragments when a button is pressed. If I was hosting swipe tab and view pager inside an activity, I would do something like this.
((ParentActivityName) getActivity()).setCurrentItem(2, true);
Now I have a parent Fragment that hosts the slide tabs. It has the following method to set current child Fragment to viewpager.
public void setCurrentItem (int item, boolean smoothScroll) {
pager.setCurrentItem(item, smoothScroll);
}
On Click of "Next" button in one of the sliding tab Fragments, I am trying to call the method as
new FragUserRegistration().setCurrentItem(1,true);
But is simply returning a null object reference error. Any help would be much appreciated.
I worked it out simply by calling viewpager from parent Fragment to each sliding tab fragments and then the associated setCurrentItem method.
viewPager = (ViewPager)getActivity().findViewById(R.id.pager);
viewPager.setCurrentItem(int position, true);
//four swipe-able fragments so position - -> 1-3 (total count 4)
I am not sure where your click happens to set the current item.
Maybe you'll find this useful:
Your MainFragment:
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
//init view
MainViewPageAdapter mainViewPageAdapter = new MainViewPageAdapter(getChildFragmentManager());
yourViewPager.setAdapter(mainViewPageAdapter);
return view;
}
private class MainViewPageAdapter extends FragmentStatePagerAdapter {
private static final int MAX_COUNT = 2;
public MainViewPageAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
Fragment fragment = null;
switch (position) {
case 1:
fragment = YourChildFragmentOne.getInstance();
break;
case 2:
fragment = YourChildFragmentTwo.getInstance();
break;
}
return fragment;
}
#Override
public int getCount() {
return MAX_COUNT;
}
}
In your ChildFragment(s):
(or create an abstract ChildFragment, which handles the click listener and create two instances of the abstract one)
private OnChildFragmentClickListener mClickListener;
#Override
public void onAttach(Context context) {
super.onAttach(context);
mClickListener = (OnChildFragmentClickListener) getParentFragment();
}
//call somewhere in your ChildFragment mClickListener.onChildFragClick(index);
public interface OnChildFragmentClickListener{
void onChildFragClick(int index);
}
Now let your MainFragment implement OnChildFragmentClickListener and call there:
#Override
public void onChildFragClick(int index){
yourViewPager.setCurrentItem(index);
}

Using viewpager with specific fragments

I am creating an app that is using a viewpager to slide between 4 specific fragments.
All the examples of viewpager I have read so far, create new fragments each time the getPosition method of FragmentPagerAdapter is called. So it's something like:
return FragmentA.newInstance();
What I have done is the following:
In the main activity
public static final int FRAGMENTS = 4;
public static final String FRAGMENT_LIST ="LIST";
public static final String FRAGMENT_SETTINGS = "SETTINGS";
public static final String FRAGMENT_MAP = "MAP";
public static final String FRAGMENT_TICKET = "TICKET";
MainAdapter _adapter;
ViewPager _pager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
getSupportFragmentManager()
.beginTransaction()
.add(new FragmentMap(), FRAGMENT_MAP)
.add(new FragmentList(), FRAGMENT_LIST)
.add(new FragmentTicket(), FRAGMENT_TICKET)
.add(new FragmentSettings(), FRAGMENT_SETTINGS)
.commit();
}
_adapter = new MainAdapter(getSupportFragmentManager());
_pager = (ViewPager) findViewById(R.id.pager);
_pager.setAdapter(_adapter);
}
and in the adapter:
public class MainAdapter extends FragmentPagerAdapter {
FragmentManager _manager;
public MainAdapter(FragmentManager fm) {
super(fm);
_manager = fm;
}
#Override
public Fragment getItem(int position) {
return _manager.getFragments().get(position);
}
#Override
public int getCount() {
return ActivityMain.FRAGMENTS;
}
}
This raises an exception because the adapter is trying to change the tag of each fragment in getItem
My questions are:
a) Is it incorrect to always use the same fragment every time? I have seen no example that uses the above method or a similar one, they always create a new instance in the getItem method
b) If I wish for fragments to have some persistence, then does that mean that I should store the data that should be held by each fragment in static variables and always create new instances that use those variables?
a) You must create a new instance in the getItem() method, this method is not called every time you switch fragment from your viewpager.
I recommend you to use your adapter like
public class MainAdapter extends FragmentPagerAdapter {
public MainAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
case 0 :
return new FragmentMap();
case 1 :
return new FragmentList();
case 2 :
return new FragmentTicket();
case 3 :
return new FragmentSettings();
default :
return null;
}
#Override
public int getCount() {
return ActivityMain.FRAGMENTS;
}
}
b) Fragments in FragmentPagerAdapter are persistents, and they will be recreate only if you switch several fragment in your ViewPager. You can set the refresh limit by _pager.setOffscreenPageLimit(3); for exemple if you never want to recreate your fragments in your case.
For more information : http://developer.android.com/reference/android/support/v4/view/ViewPager.html#setOffscreenPageLimit(int)
a) I think it's perfectly ok. They always create ne instance because it is easier and in many cases (like image gallery) it is better.
b) Static variables get lost if you send the app to backround and then return to it. Shared prefferences or bundle should do the work.
You should use FragmentStatePagerAdapter. If you fragmentstatepager adapter, you can remove or add fragment dynamically.

Fragment in ViewPager not restored after popBackStack

Problem
A Fragment is not reattached to its hosting ViewPager after returning from another fragment.
Situation
One Activity hosting a Fragment whose layout holds a ViewPager (PageListFragment in the example below). The ViewPager is populated by a FragmentStateViewPagerAdapter. The single Fragments hosted inside the pager (PageFragment in the example below) can open sub page lists, containing a new set of pages.
Behaviour
All works fine as long as the back button is not pressed. As soon as the user closes one of the sub PageLists the previous List is recreated, but without the Page that was displayed previously. Swiping through the other pages on the parent PageList still works.
Code
A sample application can be found on github:
Activity
public class MainActivity extends FragmentActivity {
private static final String CURRENT_FRAGMENT = MainActivity.class.getCanonicalName() + ".CURRENT_FRAGMENT";
public static final String ARG_PARENTS = "Parents";
public void goInto(String mHostingLevel, String mPosition) {
Fragment hostingFragment = newHostingFragment(mHostingLevel, mPosition);
addFragment(hostingFragment);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
addBaseFragment();
}
private void addBaseFragment() {
Fragment hostingFragment = newHostingFragment("", "");
addFragment(hostingFragment);
}
private Fragment newHostingFragment(String mHostingLevel, String oldPosition) {
Fragment hostingFragment = new PageListFragment();
Bundle args = new Bundle();
args.putString(ARG_PARENTS, mHostingLevel + oldPosition +" > ");
hostingFragment.setArguments(args);
return hostingFragment;
}
private void addFragment(Fragment hostingFragment) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragmentSpace, hostingFragment, CURRENT_FRAGMENT);
transaction.addToBackStack(null);
transaction.commit();
}
}
PageListFragment
public class PageListFragment extends Fragment {
private String mParentString;
public PageListFragment() {
// Required empty public constructor
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_hosting, container, false);
}
#Override
public void onResume() {
mParentString = getArguments().getString(MainActivity.ARG_PARENTS);
ViewPager viewPager = (ViewPager) getView().findViewById(R.id.viewPager);
viewPager.setAdapter(new SimpleFragmentStatePagerAdapter(getFragmentManager(),mParentString));
super.onResume();
}
private static class SimpleFragmentStatePagerAdapter extends FragmentStatePagerAdapter {
private String mHostingLevel;
public SimpleFragmentStatePagerAdapter(FragmentManager fm, String hostingLevel) {
super(fm);
this.mHostingLevel = hostingLevel;
}
#Override
public android.support.v4.app.Fragment getItem(int position) {
PageFragment pageFragment = new PageFragment();
Bundle args = new Bundle();
args.putString(MainActivity.ARG_PARENTS, mHostingLevel);
args.putInt(PageFragment.ARG_POSITION, position);
pageFragment.setArguments(args);
return pageFragment;
}
#Override
public int getCount() {
return 5;
}
}
}
PageFragment
public class PageFragment extends Fragment {
public static final String ARG_POSITION = "Position";
private String mHostingLevel;
private int mPosition;
public PageFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View contentView = inflater.inflate(R.layout.fragment_page, container, false);
setupTextView(contentView);
setupButton(contentView);
return contentView;
}
private void setupTextView(View contentView) {
mPosition = getArguments().getInt(ARG_POSITION);
mHostingLevel = getArguments().getString(MainActivity.ARG_PARENTS);
TextView text = (TextView) contentView.findViewById(R.id.textView);
text.setText("Parent Fragments " + mHostingLevel + " \n\nCurrent Fragment "+ mPosition);
}
private void setupButton(View contentView) {
Button button = (Button) contentView.findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
openNewLevel();
}
});
}
protected void openNewLevel() {
MainActivity activity = (MainActivity) getActivity();
activity.goInto(mHostingLevel, Integer.toString(mPosition));
}
}
After a lengthy investigation it turns out to be a problem with the fragment manager.
When using a construct like the one above the fragment transaction to reattach the fragment to the page list is silently discarded. It is basically the same problem that causes a
java.lang.IllegalStateException: Recursive entry to executePendingTransactions
when trying to alter the fragments inside the FragmentPager.
The same solution, as for problems with this error, is also applicable here. When constructing the FragmentStatePagerAdapter supply the correct child fragment manager.
Instead of
viewPager.setAdapter(new SimpleFragmentStatePagerAdapter(getFragmentManager(),mParentString));
do
viewPager.setAdapter(new SimpleFragmentStatePagerAdapter(getChildFragmentManager(),mParentString));
See also: github
What Paul has failed to mention is, if you use getChildFragmentManager, then you will suffer the "blank screen on back pressed" issue.
The hierarchy in my case was:
MainActivity->MainFragment->TabLayout+ViewPager->AccountsFragment+SavingsFragment+InvestmentsFragment etc.
The problem I had was that I couldn't use childFragmentManagerfor the reason that a click on the item Account view (who resides inside one of the Fragments of the ViewPager) needed to replace MainFragment i.e. the entire screen.
Using MainFragments host Fragment i.e. passing getFragmentManager() enabled the replacing, BUT when popping the back-stack, I ended up with this screen:
This was apparent also by looking at the layout inspector where the ViewPager is empty.
Apparently looking at the restored Fragments you would notice that their View is restored but will not match the hierarchy of the popped state. In order to make the minimum impact and not force a re-creation of the Fragments I re-wrote FragmentStatePagerAdapter with the following changes:
I copied the entire code of FragmentStatePagerAdapter and changed
#NonNull
#Override
public Object instantiateItem(#NonNull 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;
}
}
...
}
with
#NonNull
#Override
public Object instantiateItem(#NonNull 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) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
mCurTransaction.detach(f);
mCurTransaction.attach(f);
return f;
}
}
...
}
This way I am effectively making sure that that the restored Fragments are re-attached to the ViewPager.
Delete all page fragments, enabling them to be re-added later
The page fragments are not attached when you return to the viewpager screen as the FragmentStatePagerAdapter is not re-connecting them. As a work-around, delete all the fragments in the viewpager after popbackstack() is called, which will allow them to be re-added by your initial code.
[This example is written in Kotlin]
//Clear all fragments from the adapter before they are re-added.
for (i: Int in 0 until adapter.count) {
val item = childFragmentManager.findFragmentByTag("f$i")
if (item != null) {
adapter.destroyItem(container!!, i, item)
}
}

ViewPager onPageSelected for first page

So it appears that when using a ViewPager, the onPageSelected listener does not get called for the first page same issue as this.
I have some logic that populates some more expensive UI elements for the currently selected page and this works when page is changed, but it doesn't work for the first page.
If I set the current item after the listener, the callback gets fired for the first page, but the view has not been initialized yet, so I can't manipulate it:
// Inside PagerAdapter.instantiateItem
ViewHolder vh = new ViewHolder();
cursor.moveToPosition(position);
vh.view = adapter.newView(context, cursor, null);
// Set position as tag so we can retrieve it with findViewByTag
vh.view.setTag(position);
((ViewPager) collection).addView(vh.view,0);
return vh;
// Inside MyActivity.onCreate
pagerAdapter = new SingleMessagePagerAdapter(this, cursor);
pager = (ViewPager)findViewById(R.id.message_pager);
pager.setAdapter(pagerAdapter);
pager.setOnPageSelectedListener(this);
pager.setCurrentItem(selectedItem);
// Inside MyActivity.onPageSelected
// Retrieve tagged view
View view = pager.findViewWithTag(position);
Here view ends up being null because PagerAdapter.instantiateItem has not yet been run. So I guess my question is, at which point in the activity lifecycle can I be certain that the ViewPager has initialized the view? I tried doing this inside Activity.onAttachedToWindow and Activity.onResume but it appears both of these get fired before PagerAdapter.instantiateItem.
I'm wondering why you don't just have the logic you mention in the Fragment itself rather than the hosting Activity. ViewPager buffers a couple of fragments either side of the current one so they're set up in the background and ready to go when the user swipes to them. Putting the logic in onPageSelected would mean bypassing this functionality and not doing the heavy lifting until the user swipes to the page.
Assuming for some reason you can't do the above, why not use an Interface with a callback function. Trigger the callback in the fragment's onCreateView function to let the Activity know it's fully instantiated.
try to use fragments!
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter() {
super(getSupportFragmentManager());
}
#Override
public Fragment getItem(int i) {
Fragment fr = null;
if (i==0)
fr = new sec0frag();
else if (i==1)
fr = new sec1frag();
else if (i==2)
fr = new sec2frag();
return fr;
}
#Override
public int getCount() {
return 3;
}
}
and create 3 fragments classes
e.g. :
public static class sec0frag extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// here is initialize for first time to view pages.!
}
}
My solution was to extend pager adapter and create an interface inside it. Then make the adapter call the interface only once after creating the adapter. Inside interface callback you can call onPageChanged method without having nullpointerexception. Add this class to your project and extend your adapter from it. Dont forget to set listener to adapter before setting adapter to viewpager. Adapter class below:
public abstract class ExtendedPagerAdapter extends FragmentPagerAdapter {
private boolean instantiated;
private AdapterListener adapterListener;
public interface AdapterListener {
void onAdapterInstantiated();
}
public ExtendedPagerAdapter(FragmentManager fragmentManager) {
this(fragmentManager, null);
}
public ExtendedPagerAdapter(FragmentManager fragmentManager, AdapterListener adapterListener) {
super(fragmentManager);
this.adapterListener = adapterListener;
instantiated = false;
}
#Override
public void finishUpdate(ViewGroup container) {
super.finishUpdate(container);
if (!instantiated) {
instantiated = true;
if (adapterListener != null) {
adapterListener.onAdapterInstantiated();
}
}
}
public void setAdapterInstantiatedListener(AdapterListener adapterListener) {
this.adapterListener = adapterListener;
}
}
Activity code:
adapter = new ViewPagerAdapter(getChildFragmentManager());
adapter.setAdapterInstantiatedListener(new ExtendedPagerAdapter.AdapterListener() {
#Override
public void onAdapterInstantiated() {
onPageSelected(viewPager.getCurrentItem());
}
});
viewPager.addOnPageChangeListener(this);
viewPager.setAdapter(adapter);

Referencing Fragments inside ViewPager

I have a problem with referencing my Fragments inside a ViewPager. I would like to do it because from my activity I'd like to refresh a fragment at a specified position (e.g. currently displayed fragment).
Currently I have something like this:
public static class MyPagerAdapter extends FragmentPagerAdapter {
private static final String TAG = "MyPagerAdapter";
private static HashMap<Integer, EventListFragment> mPageReferenceMap = new HashMap<Integer, EventListFragment>();
public MyPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public int getCount() {
return NUM_ITEMS;
}
#Override
public Fragment getItem(int position) {
Log.i(TAG, "getItem: "+position);
int dateOffset = position-1;
EventListFragment mFragment = EventListFragment.newInstance(dateOffset);
mPageReferenceMap.put(position, mFragment);
return mFragment;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
Log.i(TAG, "destroyItem: "+position);
mPageReferenceMap.remove(position);
super.destroyItem(container, position, object);
}
public EventListFragment getFragment(int key) {
Log.i(TAG, "Size of pager references: "+mPageReferenceMap.size());
return mPageReferenceMap.get(key);
}
}
The problem is that the destroyItem() gets called more often than getItem(), so I'm left with null references. If I don't use destroyItem() to clear references to destroyed fragments... well I reference fragments that don't exist.
Is there any nice way to reference fragments that are created with EventListFragment mFragment = EventListFragment.newInstance(dateOffset);? Or what should I do to refresh a fragment inside a ViewPager from my activity (from options menu to be precise)?
I managed to solve it. The trick was to make a reference list inside Activity, not PagerAdapter. It goes like this:
List<WeakReference<EventListFragment>> fragList = new ArrayList<WeakReference<EventListFragment>>();
#Override
public void onAttachFragment (Fragment fragment) {
Log.i(TAG, "onAttachFragment: "+fragment);
if(fragment.getClass()==EventListFragment.class){
fragList.add(new WeakReference<EventListFragment>((EventListFragment)fragment));
}
}
public EventListFragment getFragmentByPosition(int position) {
EventListFragment ret = null;
for(WeakReference<EventListFragment> ref : fragList) {
EventListFragment f = ref.get();
if(f != null) {
if(f.getPosition()==position){
ret = f;
}
} else { //delete from list
fragList.remove(f);
}
}
return ret;
}
Of course your fragment has to implement a getPosition() function, but I needed something like this anyway, so it wasn't a problem.
Thanks Alex Lockwood for your suggestion with WeakReference!
Two things:
Add the following line in your Activity's onCreate method (or wherever you initialize your ViewPager):
mPager.setOffscreenPageLimit(NUM_ITEMS-1);
This will keep the additional off-screen pages in memory (i.e. preventing them from being destroyed), even when they aren't currently being shown on the screen.
You might consider implementing your HashMap so that it holds WeakReference<Fragment>s instead of the Fragments themselves. Note that this would require you to change your getFragment method as follows:
WeakReference<Fragment> weakRef = mPageReferenceMap.get(position);
return (weakRef != null) ? weakRef.get() : null;
This has nothing to do with your problem... it's just something I noticed and thought I would bring to your attention. Keeping WeakReferences to your Fragments will allow you to leverage the garbage collector's ability to determine reachability for you, so you don't have to do it yourself.

Categories

Resources