I have viewpager that has 4 fragments.
I have set the offscreenPageLimit to 4 and did not override getItemPosition of the adapter because I don't want them to be re-created every time when I change tabs.
Now, there will be a scenario when I want a particular fragment to be re-created (it means to call onCreateView).
That is before I select or when I select that fragment again.
How to do this?
Please try this adapter. This recover to re-create fragment again.
public class ViewPagerAdapter extends FragmentStatePagerAdapter {
private Fragment[] fragments;
private String[] titleList;
private int index;
public ViewPagerAdapter(FragmentManager fm, String[] titleList){ // titleList is list of title of fragments
super(fm);
fragments = new Fragment[titleList.length];
this.titleList = titleList;
}
#Override
public Fragment getItem(int position) {
Fragment fragment;
if (fragments[position] == null){
YourFragment yourFragment = new YourFragment();
fragment = yourFragment;
fragments[position] = fragment;
return fragment;
}else{
return fragments[position];
}
}
#Override
public int getCount() {
return titleList.length;
}
#Override
public CharSequence getPageTitle(int position) {
return titleList[position].toUpperCase();
}
}
Related
I've a ViewPager (Parent ViewPager) in my activity with 4 fragments. One of the fragments implements a sliding ViewPager (Child ViewPager) with automatic sliding.
When I swipe the parent ViewPager to, say, tab 3 and then I go back to the first fragment which contains the sliding ViewPager, some of the slide disappears and some of it stays. When I slide the ViewPager (the child ViewPager) back and forth couple of times, it gets restored to its original state.
I assume the fragment state of the sliding view pager is getting lost when the parent ViewPager is being slid.
Here's the parent ViewPager's adapter:
public class ViewPagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> mFragmentList = new ArrayList<>();
private final List<String> mFragmentTitleList = new ArrayList<>();
public ViewPagerAdapter(FragmentManager manager) {
super(manager);
}
#Override
public Fragment getItem(int position) {
return mFragmentList.get(position);
}
#Override
public int getCount() {
return mFragmentList.size();
}
public void addFragment(Fragment fragment, String title) {
mFragmentList.add(fragment);
mFragmentTitleList.add(title);
}
#Override
public CharSequence getPageTitle(int position) {
return mFragmentTitleList.get(position);
}
}
Here's the child ViewPager's adapter (slider):
class sliding_PageAdapters extends FragmentPagerAdapter {
int mNumOfTabs;
public sliding_PageAdapters(FragmentManager fm, int numTabs) {
super(fm);
this.mNumOfTabs = numTabs;
}
#Override
public void destroyItem(ViewGroup viewPager, int position, Object object) {
viewPager.removeView((View) object);
}
#Override
public Fragment getItem(int position) {
Bundle ar = new Bundle();
FragmentHomeImages fragment = new FragmentHomeImages();
switch (position) {
case 0:
ar.putString("number", "0");
fragment.setArguments(ar);
return fragment;
case 1:
ar.putString("number", "1");
fragment.setArguments(ar);
return fragment;
case 2:
ar.putString("number", "2");
fragment.setArguments(ar);
return fragment;
case 3:
ar.putString("number", "3");
fragment.setArguments(ar);
return fragment;
default:
return null;
}
}
#Override
public int getCount() {
return mNumOfTabs;
}
}
The child ViewPager is being called with getChildFragmentManager() here:
sliding_pagerAdapter = new sliding_PageAdapters(getChildFragmentManager(), 4);
And the parent ViewPager is being called with getSupportFragmentManager() here:
adapter = new ViewPagerAdapter(getSupportFragmentManager());
I can think of one problem. On the parent viewpager call setOffscreenPageLimit(limit).
here limit is the number of pages that should be retained to either side of the current page in the view hierarchy in an idle state. Pages beyond this limit will be recreated from the adapter when needed. So set the appropriate limit based on your use-case.
viewPager = (ViewPager) findViewById(R.id.pager);
viewPager.setAdapter(new MyAdapter(fragmentManager));
viewPager.setOffscreenPageLimit(2);
I have TabLayout and ViewPager with 2 fragments, each of them is a recyclerView that load data from firebase.
All works fine. But when I change screen orientation ViewPager recreate each fragment and fragments load data again.
Is it possible and how to save fragment state in ViewPager?
But I do not want to override onSaveInstanceState method for creating data set and then restore it. I want something like find fragment by tag when check existing fragment.
Activity
public class MainActivity extends BaseBroadcastActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
...
mViewPager = (ViewPager) findViewById(R.id.viewpager);
setupViewPager(mViewPager);
TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs);
tabLayout.setupWithViewPager(mViewPager);
}
private void setupViewPager(ViewPager viewPager) {
Adapter adapter = new Adapter(getSupportFragmentManager());
adapter.addFragment(MainListFragment.newInstance(connected, "Notices"), "Notices");
adapter.addFragment(MainListFragment.newInstance(connected, "Posts"), "Posts");
viewPager.setAdapter(adapter);
}
static class Adapter extends FragmentPagerAdapter {
private final List<Fragment> mFragments = new ArrayList<>();
private final List<String> mFragmentTitles = new ArrayList<>();
public Adapter(FragmentManager fm) {
super(fm);
}
public void addFragment(Fragment fragment, String title) {
mFragments.add(fragment);
mFragmentTitles.add(title);
}
#Override
public Fragment getItem(int position) {
return mFragments.get(position);
}
#Override
public int getCount() {
return mFragments.size();
}
#Override
public CharSequence getPageTitle(int position) {
return mFragmentTitles.get(position);
}
}
In MainListFragment I only create recyclerView and load data from firebase.
Thank You!
EDIT
Thank You for responding!
I find my error. What's happen when we create FragmentPageAdapter at first time and when we recreate activity:
FragmentPageAdapter call instantiateItem method where it find fragment by tag and if fragment exists adapter use it, but if fragment does not exist adapter call getItem method and create fragment.
In my situation fragments are not recreated, but data is refreshing because I refresh them in onCreateView method in fragment, but now I am edit onCreateView:
if (savedInstanceState == null) {
retrieveData();
}
It should be done in the way that you return new instance always in getItem(). I would also rather use FragmentStatePagerAdapter especially when you have more pages.
You also need to save every internal state that can be lost in onSaveInstanceState(). If you don't want to reload, you'll need to save the data. If you have some data in POJOs, you can add Serializable interface to them to serialize them easily into bundle.
There is also setRetainInstance() on fragment, but it always caused some weird behavior for me, so I'm not using it.
static class Adapter extends FragmentStatePagerAdapter {
public Adapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
Fragment fragment=null;
switch (position) {
case 0: fragment=MainListFragment.newInstance(mConnected, "Notices"); //don't forget to save mConnected in savedInstanceState
case 1: fragment=MainListFragment.newInstance(mConnected, "Posts");
}
return fragment;
}
#Override
public int getCount() {
return 2;
}
#Override
public CharSequence getPageTitle(int position) {
String title="";
switch (position) {
case 0: title="Notices";
case 1: title="Posts";
}
return title;
}
I have viewpager with some fragments and i set to each fragment my model class according which UI of fragment should be edited. That model class i set to fragment at constructor before i add that fragment to FragmentStatePagerAdapter. My problem is that after rotating screen or when system kills my application and i restore it, my fragment are recreated by system and my model class instance is lost. Even if I set new FragmentStatePagerAdapter, fragments created by system are visible. Is there any right way of achive that i create new instances of my fragments and place them into viewpager? I tried so many thinks like removing fragments from FragmentManager, adding Adapter to viewpager again, change FragmentStatePagerAdapter only to FragmentPagerAdapter and nothing helped.
My activity class with loading model classes in loader from some resource:
List<Fragment> visibleFragment = new ArrayList<Fragment>();
for (Screen screen : wizard.screens) {
if (screen.visible) {
visibleFragment.add(new ScreenUI(screen));
}
}
viewPager.setAdapter(null);
viewPager.setAdapter(new WizardPagerAdapter(getSupportFragmentManager(), visibleFragment));
My FragmentStatePagerAdapter
public class WizardPagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> mFragments;
private final int count;
public WizardPagerAdapter(FragmentManager fm, List<Fragment> fragments) {
super(fm);
mFragments = fragments;
count = mFragments.size();
}
#Override
public Fragment getItem(int index) {
return mFragments.get(index);
}
#Override
public int getCount() {
return count;
}
}
try add in Manifest android:configChanges="orientation|keyboardHidden|screenSize" to activity.
OR something like that
public class ViewPagerTaskAdapter extends FragmentStatePagerAdapter {
Map<Integer, FragmentViewTask> fragmentsTask = new HashMap<Integer, FragmentViewTask>();
#Override
public Fragment getItem(int arg0) {
FragmentViewTask myFragment = FragmentViewTask.newInstance(arg0,
tasks.get(arg0));
fragmentsTask.put(arg0, myFragment);
return myFragment;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
fragmentsTask.remove(position);
}
public FragmentViewTask getFragment(int key) {
return fragmentsTask.get(key);
}
#Override
public int getCount() {
if (tasks != null)
return tasks.size();
return 0;
}
}
Have you tried using onRetainInstance(true) on your Fragments?
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
I'm working with a FragmentStatePagerAdapter using this example.
The MyAdapter class is implemented as follows:
public static class MyAdapter extends FragmentStatePagerAdapter {
public MyAdapter(FragmentManager fm) {
super(fm);
}
#Override
public int getCount() {
return NUM_ITEMS;
}
#Override
public Fragment getItem(int position) {
return ArrayListFragment.newInstance(position);
}
}
The ListFragment class includes the following method to create a new instance:
/**
* Create a new instance of CountingFragment, providing "num"
* as an argument.
*/
static ArrayListFragment newInstance(int num) {
ArrayListFragment f = new ArrayListFragment();
// Supply num input as an argument.
Bundle args = new Bundle();
args.putInt("num", num);
f.setArguments(args);
return f;
}
When I create a new fragment state pager adapter in my activity, getItem is called, which in turn calls the newInstance method in the ListFragment class. This is great when I want to create a new fragment.
But it's not clear to me how to modify getItem (if even needed) to get the fragment object when it already exists and the user pages from, for example, page 2 to page 1. I'd like my Activity to retrieve that existing, previously created fragment so that it can run an inner class AsyncMethod, which resides in the fragment class.
I think the solution is much simpler than the answers provided.
If you take a look at the source of FragmentStatePagerAdapter.instantiateItem(), you'll notice that instantiateItem() handles this logic for you, and thus your implementation of getItem() should always return a new instance.
So in order to return an existing Fragment, simply do (assuming you're calling from ViewPager):
Fragment f = (Fragment) getAdapter().instantiateItem(this, getCurrentItem());
While #imiric's solution is very concise, it still bothers me that it requires knowledge of the implementation of the class. My solution involves adding the following to your adapter, which should behave nicely with a FragmentStatePagerAdapter possibly destroying fragments:
private SparseArray<WeakReference<Fragment>> mFragments = new SparseArray<>();
#Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment f = (Fragment) super.instantiateItem(container, position);
mFragments.put(position, new WeakReference<>(f)); // Remember what fragment was in position
return f;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
mFragments.remove(position);
}
public Fragment getFragment(int position) {
WeakReference<Fragment> ref = mFragments.get(position);
Fragment f = ref != null ? ref.get() : null;
if (f == null) {
Log.d(TAG, "fragment for " + position + " is null!");
}
return f;
}
I have implemented something similar to what you have. I extended the FragmentPagerAdapter class like so:
public class ContactsFragmentPagerAdapter extends FragmentPagerAdapter {
ActionBar mActionBar;
private List<Fragment> mFragments;
public ContactsFragmentPagerAdapter(FragmentManager fm, List<Fragment> fragments) {
super(fm);
mFragments = fragments;
}
#Override
public int getCount() {
return mFragments.size();
}
#Override
public Fragment getItem(int position) {
return mFragments.get(position);
}
public void setActionBar(ActionBar bar) {
mActionBar = bar;
}
}
Notice I have added an argument to the constructor to pass in the List of Fragment objects. This way the getItem() method of this class can return any class that extends Fragment or any of its subclasses and not just one specific class ArrayListFragment like you have done.
In the Activity where I instantiate my subclass of FragmentPagerAdapter I have passed in the list of Fragment objects:
Class the instantiates the FragmentPagerAdapter
public final class ContactManager extends Activity {
private ContactsFragmentPagerAdapter mAdapter;
private ViewPager mPager;
public ActionBar mActionBar;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.contact_manager);
List<Fragment> fragments = new Vector<Fragment>();
fragments.add(Fragment.instantiate(this, ContactsListFragment.class.getName()));
fragments.add(Fragment.instantiate(this, GroupsListFragment.class.getName()));
mAdapter = new ContactsFragmentPagerAdapter(this.getFragmentManager(), fragments);
mPager = (ViewPager) findViewById(R.id.pager);
mPager.setAdapter(mAdapter);
mPager.setOnPageChangeListener(new OnPageChangeListener() {
#Override
public void onPageScrollStateChanged(int arg0) {}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {}
#Override
public void onPageSelected(int arg0) {
mActionBar.getTabAt(arg0).select();
}
});
}
}
By accessing the variable "fragments", you can access a previously created Fragment so that you can run methods of that Fragment.
public class MyPagerAdapter extends FragmentStatePagerAdapter {
final Context m_context;
final WeakReference<Fragment>[] m_fragments;
public DetailPagerAdapter(FragmentManager fm) {
super(fm);
m_context = ...
m_fragments = new WeakReference[enter size here];
}
#Override
public Fragment getItem(int position) {
final Fragment fragment = instantiate your fragment;
m_fragments[position] = new WeakReference<Fragment>(fragment);
return fragment;
}
#Override
public int getCount() {
return ...
}
public Fragment getFragment(final int position) {
return m_fragments[position] == null ? null :
m_fragments[position].get();
}
}
Do not need modify the FragmentPagerAdapter, because fragments are cached by the FragmentManager. So you must need find inside it. Use this function to find the fragment by pager adapter position.
public Fragment findFragmentByPosition(int position) {
FragmentPagerAdapter fragmentPagerAdapter = getFragmentPagerAdapter();
return getSupportFragmentManager().findFragmentByTag(
"android:switcher:" + getViewPager().getId() + ":"
+ fragmentPagerAdapter.getItemId(position));
}
Sample code for v4 support api.
I think this could help:
class HomePagerAdapter(fragmentManager: FragmentManager) : FragmentStatePagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
private val fragmentConstructors: List<() -> Fragment> by lazy {
listOf(
::AuctionLotsFragment,
::ChatListFragment,
::MarketHomeFragment,
::WalletSectionsFragment,
::SettingsFragment
)
}
private val fragments: HashMap<Int, WeakReference<Fragment>> by lazy {
HashMap()
}
override fun getCount(): Int = fragmentConstructors.size
override fun getItem(position: Int): Fragment {
return fragments[position]?.get() ?: this.createFragmentForPosition(position)
}
private fun createFragmentForPosition(position: Int): Fragment {
val fragment = fragmentConstructors[position].invoke()
fragments[position] = WeakReference(fragment)
return fragment
}
}
I have a FragmentList with some items. If I click an item the appropriate Fragment will open which contains a Viewpager. No problem so far but if I go back to the List and click the item again the method getItem(int position) is not called anymore.
Here a link with a similar problem:
Display fragment viewpager within a fragment
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
view = inflater.inflate(R.layout.activity_fragment_runtime, container, false);
mViewPager = (ViewPager) view.findViewById(R.id.pager);
indicator = (TitlePageIndicator) view.findViewById(R.id.indicator);
MyFragmentPagerAdapter mMyFragmentPagerAdapter = new MyFragmentPagerAdapter(getActivity().getSupportFragmentManager());
mViewPager.setAdapter(mMyFragmentPagerAdapter);
indicator.setViewPager(mViewPager);
return view;
}
class MyFragmentPagerAdapter extends FragmentStatePagerAdapter implements TitleProvider {
public MyFragmentPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public String getTitle(int position) {
return muxbusTitles[position];
}
#Override
public Fragment getItem(int location) {
return muxbusFragmentList.get(location);
}
#Override
public int getCount() {
return muxbusTitles.length;
}
}
return view;
}
Edit:
If someone is interested,the solution was to extends from PagerAdapter instead of FragmentStatePagerAdpater because the Viewpager is already in a Fragment..
I did have similar problem and after scratching my head for quite some time i made an my own understanding (assumption) that ViewPager is keeping the reference of its child fragments into memory even the hosted Fragment or Activity is destroyed (didn't tested with Activity). So i wrote this method freeFragmentsFromViewPager(). You need to call it from onDestroy() of your Fragment.
/**
* This method free the child fragments from ViewPager as ViewPager keeps the reference
* of child fragments into memory and next time when you again come to visit view pager it won't show the fragment layout
* because it has the reference to the fragment whilst the fragment's view was already destroyed.
*/
private void freeFragmentsFromViewPager()
{
for(int i = 0; i < TabsFragmentPagerAdapter.fragments.length; i++)
{
Fragment fragment = TabsFragmentPagerAdapter.fragments[i];
if(fragment != null)
{
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.remove(fragment);
fragmentTransaction.commit();
TabsFragmentPagerAdapter.fragments[i] = null;
}
}
}
And this is my Adapter code
public class TabsFragmentPagerAdapter extends FragmentPagerAdapter
{
public static final int NUM_TABS = 3;
public static Fragment[] fragments = new Fragment[NUM_TABS];
public TabsFragmentPagerAdapter(FragmentManager fm)
{
super(fm);
}
#Override
public Fragment getItem(int position)
{
Log.e("TabsFragmentPagerAdapter", "getItem");
Fragment fragment = TabsFragmentFactory.newInstace(position);
fragments[position] = fragment;
return fragment;
}
#Override
public int getCount()
{
return NUM_TABS;
}
#Override
public CharSequence getPageTitle(int position)
{
return TabsFragmentFactory.getName(position);
}