ViewPager onPageSelected for first page - android

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);

Related

Textview not updating on first Tab unless scrolled to 3rd Tab

I have a tablayout (with 3 tabs) with viewpager and fragments.
I m trying to send the parsed Json data from MainActivity( When searchview data submitted ) to show in the textview of tabs fragments
See this Image link
The data is succesfully parsing but textview with data(in first tab) is not showing unless scrolled to 3rd tab
//Passing data from MainActivity
public String getMyData() {
return meaning;
}
//Setting value to textview from Fragment
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View v=inflater.inflate(R.layout.fragment_meaning, container, false);
MainActivity mainActivity= (MainActivity) getActivity();
assert mainActivity != null;
String data= mainActivity.getMyData();
TextView textView=v.findViewById(R.id.textVIew);
textView.setText(data);
return v;
}
Want to able to show data changes instantly as it is parsed, instead of scrolling to 3rd tab to see changes
Here are some steps that might help you.
On the ViewPager adaptor you have created, make the fragment objects. like below
FragmentOne fragOne; // this should be global
On the viewPager adaptor, do some thing like this,
fragOne = new FragmentOne() // whatever your implementation is.
Then after fetching the data from the server,
if ( fragOne != null ) {
fragOne.setValueOnView( " your data to be passed" );
}
and on the FragmentOne, create a function called setValueOnView
void setValueOnView(String yourString) {
v.findViewById(R.id.textVIew).setText(yourString);
}
And one more thing, while initializing the fragment onCreateView, create an object of View
View v; // global variable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
v=inflater.inflate(R.layout.fragment_meaning, container, false);
Use this approach for other fragments as well
Inside getItem() method in ViewPager class use Fragment constructors with String parameter
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
FragmentOne tab1 = new FragmentOne("string parameter");
return tab1;
case 1:
FragmentTwo tab2 = new FragmentTwo("string parameter");
return tab2;
case 2:
FragmentThree tab3 = new FragmentThree("string parameter");
return tab3;
default:
return null;
}
}
Inside your Fragment:
public FragmentOne(String stringParameter) {
yourLocalVariable = stringParameter; // yourLocalVariable is declared inside Fragment class;
//now you can setText() for your TextView inside onViewCreated()
}
Of course you pass your String from MainActivity to ViewPager like you did earlier.
Use Observer
public class FragmentObserver extends Observable {
#Override
public void notifyObservers() {
setChanged(); // Set the changed flag to true, otherwise observers won't be notified.
super.notifyObservers();
}
}
Activity:
public class MyActivity extends Activity {
private MyAdapter mPagerAdapter;
#Override
public void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.my_activity);
ViewPager pager = (ViewPager) findViewById(R.id.pager);
mPagerAdapter = new MyAdapter();
pager.setAdapter(mPagerAdapter);
}
private void updateFragments() {
mPagerAdapter.updateFragments();
}
}
Viewpager adapter
public class MyAdapter extends FragmentPagerAdapter {
private Observable mObservers = new FragmentObserver();
public MyAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
mObservers.deleteObservers(); // Clear existing observers.
Fragment fragment = new MyFragment();
if(fragment instanceof Observer)
mObservers.addObserver((Observer) fragment);
return fragment;
}
public void updateFragments() {
mObservers.notifyObservers();
}
}
Your Fragment
public class MyFragment extends Fragment implements Observer {
/* Fragment related stuff... */
#Override
public void update(Observable observable, Object data) {
View root = getView();
// Update your views here.
}
}
You will get data to update method even your fragment already loaded

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);
}

Viewpager fragments not shown after the first time

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)

Android Fragments in a ViewPager

I'm kinda confused about the whole Fragment-way-of-thinking. I've followed a tutorial on how to create a ViewPager with Fragments like the Google Play app.
I have TabFragment class like this one:
public class SwipeyTabFragment extends SherlockFragment {
#Override
public void onCreate(Bundle b) {
super.onCreate(b);
Log.e("FRAGMENT: ", "Hello World!");
}
public static Fragment newInstance(String title) {
SwipeyTabFragment f = new SwipeyTabFragment();
Bundle args = new Bundle();
args.putString("title", title);
f.setArguments(args);
return f;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment_swipeytab, null);
final String title = getArguments().getString("title");
((TextView) root.findViewById(R.id.text)).setText(title);
return root;
}
}
I know that the onCreateView method initialize the layout and the controlls like Button, ListView and so on.
Over to my FragmentAdapter
private class SwipeyTabsPagerAdapter extends FragmentPagerAdapter implements SwipeyTabsAdapter {
private final Context mContext;
public SwipeyTabsPagerAdapter(Context context, FragmentManager fm) {
super(fm);
this.mContext = context;
}
#Override
public Fragment getItem(int position) {
return SwipeyTabFragment.newInstance(TITLES[position]);
}
#Override
public int getCount() {
return TITLES.length;
}
public TextView getTab(final int position, SwipeyTabs root) {
TextView view = (TextView) LayoutInflater.from(mContext).inflate(R.layout.swipey_tab_indicator, root, false);
view.setText(TITLES[position]);
view.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
mViewPager.setCurrentItem(position);
}
});
return view;
}
}
This will just construct a new Tab based on a String-Array, which will set the text and the header of the Fragment.
So this is where I get confused. Say for instance that I want several fragments with different layout, and different ways of interacting when the user presses on Button, Picture or whatever. How can I do so?
Thanks.
All the 'user presses button' stuff is handled in the fragments, you can call back to the Activity of course when you need to (see here).
You have to create different fragment classes for different layouts, logic. etc. and return them to the ViewPager in getItem. You could have a FirstPageFragment and a SecondPageFragment, then return them (depending on the index) in getView. This only makes sense if those fragments have different functionalities of course.
Hope it's clear what I mean ;)
EDIT: as to your comment:
I don't know what exactly you want to do, but you have your SwipeyTabFragment already defined in it's own file. Take this then, modify it, give it another layout and other functionality, then call it OtherFragment or whatever. Let's say you want to have 2 different 'pages' in your App - the getCount() method in your adapter defines the amount of 'pages' in your ViewPager, so let's let it return two.
In the getItem() method, if position is 0, let it return your SwipeyFragment, else (position is 1) let it return your new OtherFragment. Now you have a ViewPager with 2 different Fragments that can serve totally different purposes.

Why `PagerAdapter::notifyDataSetChanged` is not updating the View?

I'm using the ViewPager from the compatibility library. I have succussfully got it displaying several views which I can page through.
However, I'm having a hard time figuring out how to update the ViewPager with a new set of Views.
I've tried all sorts of things like calling mAdapter.notifyDataSetChanged(), mViewPager.invalidate() even creating a brand new adapter each time I want to use a new List of data.
Nothing has helped, the textviews remain unchanged from the original data.
Update:
I made a little test project and I've almost been able to update the views. I'll paste the class below.
What doesn't appear to update however is the 2nd view, the 'B' remains, it should display 'Y' after pressing the update button.
public class ViewPagerBugActivity extends Activity {
private ViewPager myViewPager;
private List<String> data;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
data = new ArrayList<String>();
data.add("A");
data.add("B");
data.add("C");
myViewPager = (ViewPager) findViewById(R.id.my_view_pager);
myViewPager.setAdapter(new MyViewPagerAdapter(this, data));
Button updateButton = (Button) findViewById(R.id.update_button);
updateButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
updateViewPager();
}
});
}
private void updateViewPager() {
data.clear();
data.add("X");
data.add("Y");
data.add("Z");
myViewPager.getAdapter().notifyDataSetChanged();
}
private class MyViewPagerAdapter extends PagerAdapter {
private List<String> data;
private Context ctx;
public MyViewPagerAdapter(Context ctx, List<String> data) {
this.ctx = ctx;
this.data = data;
}
#Override
public int getCount() {
return data.size();
}
#Override
public Object instantiateItem(View collection, int position) {
TextView view = new TextView(ctx);
view.setText(data.get(position));
((ViewPager)collection).addView(view);
return view;
}
#Override
public void destroyItem(View collection, int position, Object view) {
((ViewPager) collection).removeView((View) view);
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
#Override
public Parcelable saveState() {
return null;
}
#Override
public void restoreState(Parcelable arg0, ClassLoader arg1) {
}
#Override
public void startUpdate(View arg0) {
}
#Override
public void finishUpdate(View arg0) {
}
}
}
There are several ways to achieve this.
The first option is easier, but bit more inefficient.
Override getItemPosition in your PagerAdapter like this:
public int getItemPosition(Object object) {
return POSITION_NONE;
}
This way, when you call notifyDataSetChanged(), the view pager will remove all views and reload them all. As so the reload effect is obtained.
The second option, suggested by Alvaro Luis Bustamante (previously alvarolb), is to setTag() method in instantiateItem() when instantiating a new view. Then instead of using notifyDataSetChanged(), you can use findViewWithTag() to find the view you want to update.
Conclusion
If you have a lot of views, or want to support modifying any specific item and/or view (fastly at any time), then the second approach (tagging) is very flexible and high performant, as it prevents recreating all the not modified views.
(Kudos to alvarolb for the original research.)
But if your App has only a "refresh" feature (without single item changes being even allowed), or has just few items, use the first approach, as it saves development time.
I don't think there is any kind of bug in the PagerAdapter. The problem is that understanding how it works is a little complex. Looking at the solutions explained here, there is a misunderstanding and therefore a poor usage of instantiated views from my point of view.
The last few days I have been working with PagerAdapter and ViewPager, and I found the following:
The notifyDataSetChanged() method on the PagerAdapter will only notify the ViewPager that the underlying pages have changed. For example, if you have created/deleted pages dynamically (adding or removing items from your list) the ViewPager should take care of that. In this case I think that the ViewPager determines if a new view should be deleted or instantiated using the getItemPosition() and getCount() methods.
I think that ViewPager, after a notifyDataSetChanged() call takes it's child views and checks their position with the getItemPosition(). If for a child view this method returns POSITION_NONE, the ViewPager understands that the view has been deleted, calling the destroyItem(), and removing this view.
In this way, overriding getItemPosition() to always return POSITION_NONE is completely wrong if you only want to update the content of the pages, because the previously created views will be destroyed and new ones will be created every time you call notifyDatasetChanged(). It may seem to be not so wrong just for a few TextViews, but when you have complex views, like ListViews populated from a database, this can be a real problem and a waste of resources.
So there are several approaches to efficiently change the content of a view without having to remove and instantiate the view again. It depends on the problem you want to solve. My approach is to use the setTag() method for any instantiated view in the instantiateItem() method. So when you want to change the data or invalidate the view that you need, you can call the findViewWithTag() method on the ViewPager to retrieve the previously instantiated view and modify/use it as you want without having to delete/create a new view each time you want to update some value.
Imagine for example that you have 100 pages with 100 TextViews and you only want to update one value periodically. With the approaches explained before, this means you are removing and instantiating 100 TextViews on each update. It does not make sense...
Change the FragmentPagerAdapter to FragmentStatePagerAdapter.
Override getItemPosition() method and return POSITION_NONE.
Eventually, it will listen to the notifyDataSetChanged() on view pager.
The answer given by alvarolb is definitely the best way to do it. Building upon his answer, an easy way to implement this is to simply store out the active views by position:
SparseArray<View> views = new SparseArray<View>();
#Override
public Object instantiateItem(View container, int position) {
View root = <build your view here>;
((ViewPager) container).addView(root);
views.put(position, root);
return root;
}
#Override
public void destroyItem(View collection, int position, Object o) {
View view = (View)o;
((ViewPager) collection).removeView(view);
views.remove(position);
view = null;
}
Then once by overriding the notifyDataSetChanged method you can refresh the views...
#Override
public void notifyDataSetChanged() {
int key = 0;
for(int i = 0; i < views.size(); i++) {
key = views.keyAt(i);
View view = views.get(key);
<refresh view with new data>
}
super.notifyDataSetChanged();
}
You can actually use similar code in instantiateItem and notifyDataSetChanged to refresh your view. In my code I use the exact same method.
Had the same problem. For me it worked to extend FragmentStatePagerAdapter, and override the below methods:
#Override
public Parcelable saveState() {
return null;
}
#Override
public void restoreState(Parcelable state, ClassLoader loader) {
}
After hours of frustration while trying all the above solutions to overcome this problem and also trying many solutions on other similar questions like this, this and this which all FAILED with me to solve this problem and to make the ViewPager to destroy the old Fragment and fill the pager with the new Fragments. I have solved the problem as following:
1) Make the ViewPager class to extends FragmentPagerAdapter as following:
public class myPagerAdapter extends FragmentPagerAdapter {
2) Create an Item for the ViewPager that store the title and the fragment as following:
public class PagerItem {
private String mTitle;
private Fragment mFragment;
public PagerItem(String mTitle, Fragment mFragment) {
this.mTitle = mTitle;
this.mFragment = mFragment;
}
public String getTitle() {
return mTitle;
}
public Fragment getFragment() {
return mFragment;
}
public void setTitle(String mTitle) {
this.mTitle = mTitle;
}
public void setFragment(Fragment mFragment) {
this.mFragment = mFragment;
}
}
3) Make the constructor of the ViewPager take my FragmentManager instance to store it in my class as following:
private FragmentManager mFragmentManager;
private ArrayList<PagerItem> mPagerItems;
public MyPagerAdapter(FragmentManager fragmentManager, ArrayList<PagerItem> pagerItems) {
super(fragmentManager);
mFragmentManager = fragmentManager;
mPagerItems = pagerItems;
}
4) Create a method to re-set the adapter data with the new data by deleting all the previous fragment from the fragmentManager itself directly to make the adapter to set the new fragment from the new list again as following:
public void setPagerItems(ArrayList<PagerItem> pagerItems) {
if (mPagerItems != null)
for (int i = 0; i < mPagerItems.size(); i++) {
mFragmentManager.beginTransaction().remove(mPagerItems.get(i).getFragment()).commit();
}
mPagerItems = pagerItems;
}
5) From the container Activity or Fragment do not re-initialize the adapter with the new data. Set the new data through the method setPagerItems with the new data as following:
ArrayList<PagerItem> pagerItems = new ArrayList<PagerItem>();
pagerItems.add(new PagerItem("Fragment1", new MyFragment1()));
pagerItems.add(new PagerItem("Fragment2", new MyFragment2()));
mPagerAdapter.setPagerItems(pagerItems);
mPagerAdapter.notifyDataSetChanged();
I hope it helps.
I had the same issue and my solution is using FragmentPagerAdapter with overriding of FragmentPagerAdapter#getItemId(int position):
#Override
public long getItemId(int position) {
return mPages.get(position).getId();
}
By default, this method returns item's position. I suppose that ViewPager checks if itemId was changed and recreates page only if it was. But not-overriden version returns the same position as itemId even if page is actually different, and ViewPager doesn't define that page is replaced one and needs to be recreated.
To use this, long id is needed for each page. Normally it is expected to be unique, but i suggest, for this case, that it just should be different from the previous value for the same page. So, It is possible to use continuous counter in adapter or random integers (with wide distribution) here.
I think that it is more consistent way rather using of Tags of view mentioned as a solution in this topic. But probably not for all cases.
I found very interesting decision of this problem.
Instead of using FragmentPagerAdapter, which keep in memory all fragments, we can use FragmentStatePagerAdapter (android.support.v4.app.FragmentStatePagerAdapter), that reload fragment each time, when we select it.
Realisations of both adapters are identical. So, we need just change "extend FragmentPagerAdapter" on "extend FragmentStatePagerAdapter"
ViewPager was not designed to support dynamic view change.
I had confirmation of this while looking for another bug related to this one https://issuetracker.google.com/issues/36956111 and in particular https://issuetracker.google.com/issues/36956111#comment56
This question is a bit old, but Google recently solved this problem with ViewPager2 .
It will allow to replace handmade (unmaintained and potentially buggy) solutions by a standard one. It also prevents recreating views needlessly as some answers do.
For ViewPager2 examples, you can check https://github.com/googlesamples/android-viewpager2
If you want to use ViewPager2, you will need to add the following dependency in your build.gradle file :
dependencies {
implementation 'androidx.viewpager2:viewpager2:1.0.0-beta02'
}
Then you can replace your ViewPager in your xml file with :
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
After that, you will need to replace ViewPager by ViewPager2 in your activity
ViewPager2 needs either a RecyclerView.Adapter, or a FragmentStateAdapter, in your case it can be a RecyclerView.Adapter
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private Context context;
private ArrayList<String> arrayList = new ArrayList<>();
public MyAdapter(Context context, ArrayList<String> arrayList) {
this.context = context;
this.arrayList = arrayList;
}
#NonNull
#Override
public MyViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.list_item, parent, false);
return new MyViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull MyViewHolder holder, int position) {
holder.tvName.setText(arrayList.get(position));
}
#Override
public int getItemCount() {
return arrayList.size();
}
public class MyViewHolder extends RecyclerView.ViewHolder {
TextView tvName;
public MyViewHolder(#NonNull View itemView) {
super(itemView);
tvName = itemView.findViewById(R.id.tvName);
}
}
}
In the case you were using a TabLayout, you can use a TabLayoutMediator :
TabLayoutMediator tabLayoutMediator = new TabLayoutMediator(tabLayout, viewPager, true, new TabLayoutMediator.OnConfigureTabCallback() {
#Override
public void onConfigureTab(#NotNull TabLayout.Tab tab, int position) {
// configure your tab here
tab.setText(tabs.get(position).getTitle());
}
});
tabLayoutMediator.attach();
Then you will be able to refresh your views by modifying your adapter's data and calling notifyDataSetChanged method
All these solution did not help me. thus i found a working solution:
You can setAdapter every time, but it isn't enough.
you should do these before changing adapter:
FragmentManager fragmentManager = slideShowPagerAdapter.getFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
List<Fragment> fragments = fragmentManager.getFragments();
for (Fragment f : fragments) {
transaction.remove(f);
}
transaction.commit();
and after this:
viewPager.setAdapter(adapter);
After a lot of searching for this problem, I found a really good solution that I think is the right way to go about this. Essentially, instantiateItem only gets called when the view is instantiated and never again unless the view is destroyed (this is what happens when you override the getItemPosition function to return POSITION_NONE). Instead, what you want to do is save the created views and either update them in the adapter, generate a get function so someone else can update it, or a set function which updates the adapter (my favorite).
So, in your MyViewPagerAdapter add a variable like:
private View updatableView;
an in your instantiateItem:
public Object instantiateItem(View collection, int position) {
updatableView = new TextView(ctx); //My change is here
view.setText(data.get(position));
((ViewPager)collection).addView(view);
return view;
}
so, this way, you can create a function that will update your view:
public updateText(String txt)
{
((TextView)updatableView).setText(txt);
}
Hope this helps!
Two and half years after the OP posed his question, this issue is still, well, still an issue. It's obvious Google's priority on this isn't particularly high, so rather than find a fix, I found a workaround. The big breakthrough for me was finding out what the real cause of the problem was (see the accepted answer in this post ). Once it was apparent that the issue was that any active pages are not properly refreshed, my workaround was obvious:
In my Fragment (the pages):
I took all the code which populates the form out of onCreateView and put it in a function called PopulateForm which may be called from anywhere, rather than by the framework. This function attempts to get the current View using getView, and if that is null, it just returns. It's important that PopulateForm contains only the code that displays - all the other code which creates FocusChange listeners and the like is still in OnCreate
Create a boolean which can be used as a flag indicating the form must be reloaded. Mine is mbReloadForm
Override OnResume() to call PopulateForm() if mbReloadForm is set.
In my Activity, where I do the loading of the pages:
Go to page 0 before changing anything. I'm using FragmentStatePagerAdapter, so I know that two or three pages are affected at most. Changing to page 0 ensures I only ever have the problem on pages 0, 1 and 2.
Before clearing the old list, take it's size(). This way you know how many pages are affected by the bug. If > 3, reduce it to 3 - if you're using a a different PagerAdapter, you'll have to see how many pages you have to deal with (maybe all?)
Reload the data and call pageAdapter.notifyDataSetChanged()
Now, for each of the affected pages, see if the page is active by using pager.getChildAt(i) - this tells you if you have a view. If so, call pager.PopulateView(). If not, set the ReloadForm flag.
After this, when you reload a second set of pages, the bug will still cause some to display the old data. However, they will now be refreshed and you will see the new data - your users won't know the page was ever incorrect because this refreshing will happen before they see the page.
Hope this helps someone!
A much easier way: use a FragmentPagerAdapter, and wrap your paged views onto fragments. They do get updated
Thank rui.araujo and Alvaro Luis Bustamante. At first, I try to use rui.araujo's way, because it's easy. It works but when the data change, the page will redraw obviously. It is bad so I try to use Alvaro Luis Bustamante's way. It's perfect. Here is the code:
#Override
protected void onStart() {
super.onStart();
}
private class TabPagerAdapter extends PagerAdapter {
#Override
public int getCount() {
return 4;
}
#Override
public boolean isViewFromObject(final View view, final Object object) {
return view.equals(object);
}
#Override
public void destroyItem(final View container, final int position, final Object object) {
((ViewPager) container).removeView((View) object);
}
#Override
public Object instantiateItem(final ViewGroup container, final int position) {
final View view = LayoutInflater.from(
getBaseContext()).inflate(R.layout.activity_approval, null, false);
container.addView(view);
ListView listView = (ListView) view.findViewById(R.id.list_view);
view.setTag(position);
new ShowContentListTask(listView, position).execute();
return view;
}
}
And when data change:
for (int i = 0; i < 4; i++) {
View view = contentViewPager.findViewWithTag(i);
if (view != null) {
ListView listView = (ListView) view.findViewById(R.id.list_view);
new ShowContentListTask(listView, i).execute();
}
}
I had a similar problem in which I had four pages and one of the pages updated views on the other three. I was able to updated the widgets(SeekBars, TextViews, etc.) on the page adjacent to the current page. The last two pages would have uninitialized widgets when calling mTabsAdapter.getItem(position).
To solve my issue, I used setSelectedPage(index) before calling getItem(position). This would instantiate the page, allowing me to be able to alter values and widgets on each page.
After all of the updating I would use setSelectedPage(position) followed by notifyDataSetChanged().
You can see a slight flicker in the ListView on the main updating page, but nothing noticeable. I haven't tested it throughly, but it does solve my immediate problem.
Just in case anyone are using FragmentStatePagerAdapter based adapter(which will let ViewPager create minimum pages needed for display purpose, at most 2 for my case), #rui.araujo's answer of overwriting getItemPosition in your adapter will not cause significant waste, but it still can be improved.
In pseudo code:
public int getItemPosition(Object object) {
YourFragment f = (YourFragment) object;
YourData d = f.data;
logger.info("validate item position on page index: " + d.pageNo);
int dataObjIdx = this.dataPages.indexOf(d);
if (dataObjIdx < 0 || dataObjIdx != d.pageNo) {
logger.info("data changed, discard this fragment.");
return POSITION_NONE;
}
return POSITION_UNCHANGED;
}
I am just posting this answer in case anyone else finds it useful. For doing the exact same thing, I simply took the source code of the ViewPager and PagerAdapter from the compatibility library and compiled it within my code (You need to sort out all the errors and imports yourself, but it definitely can be done).
Then, in the CustomViewPager, create a method called updateViewAt(int position). The view itself can be gotten from ArrayList mItems defined in the ViewPager class (you need to set an Id for the views at instantiate item and compare this id with position in the updateViewAt() method). Then you can update the view as necessary.
You can update dynamically all fragments, you can see in three steps.
In your adapter:
public class MyPagerAdapter extends FragmentPagerAdapter {
private static int NUM_ITEMS = 3;
private Map<Integer, String> mFragmentTags;
private FragmentManager mFragmentManager;
public MyPagerAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
mFragmentManager = fragmentManager;
mFragmentTags = new HashMap<Integer, String>();
}
// Returns total number of pages
#Override
public int getCount() {
return NUM_ITEMS;
}
// Returns the fragment to display for that page
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return FirstFragment.newInstance();
case 1:
return SecondFragment.newInstance();
case 2:
return ThirdFragment.newInstance();
default:
return null;
}
}
// Returns the page title for the top indicator
#Override
public CharSequence getPageTitle(int position) {
return "Page " + position;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Object object = super.instantiateItem(container, position);
if (object instanceof Fragment) {
Fragment fragment = (Fragment) object;
String tag = fragment.getTag();
mFragmentTags.put(position, tag);
}
return object;
}
public Fragment getFragment(int position) {
Fragment fragment = null;
String tag = mFragmentTags.get(position);
if (tag != null) {
fragment = mFragmentManager.findFragmentByTag(tag);
}
return fragment;
}}
Now in your activity:
public class MainActivity extends AppCompatActivity implements ViewPager.OnPageChangeListener{
MyPagerAdapter mAdapterViewPager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewPager viewPager = (ViewPager) findViewById(R.id.vpPager);
mAdapterViewPager = new MyPagerAdapter(getSupportFragmentManager());
viewPager.setAdapter(mAdapterViewPager);
viewPager.addOnPageChangeListener(this);
}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
Fragment fragment = mAdapterViewPager.getFragment(position);
if (fragment != null) {
fragment.onResume();
}
}
#Override
public void onPageScrollStateChanged(int state) {
}}
Finally in your fragment, something like that:
public class YourFragment extends Fragment {
// newInstance constructor for creating fragment with arguments
public static YourFragment newInstance() {
return new YourFragment();
}
// Store instance variables based on arguments passed
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
// Inflate the view for the fragment based on layout XML
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment, container, false);
}
#Override
public void onResume() {
super.onResume();
//to refresh your view
refresh();
}}
You can see complete code here.
Thanks Alvaro Luis Bustamante.
I guess, I've got the logics of ViewPager.
If I need to refresh a set of pages and display them based on new dataset, I call notifyDataSetChanged().
Then, ViewPager makes a number of calls to getItemPosition(), passing there Fragment as an Object. This Fragment can be either from an old dataset (that I want to discard) or from a new one (that I want to display). So, I override getItemPosition() and there I have to determine somehow if my Fragment is from the old dataset or from the new one.
In my case I have a 2-pane layout with a list of top items on the left pane and a swipe view (ViewPager) on the right. So, I store a link to my current top item inside my PagerAdapter and also inside of each instantiated page Fragment.
When the selected top item in the list changes, I store the new top item in PagerAdapter and call notifyDataSetChanged(). And in the overridden getItemPosition() I compare the top item from my adapter to the top item from my fragment.
And only if they are not equal, I return POSITION_NONE.
Then, PagerAdapter reinstantiates all the fragments that have returned POSITION_NONE.
NOTE. Storing the top item id instead of a reference might be a better idea.
The code snippet below is a bit schematical but I adapted it from the actually working code.
public class SomeFragment extends Fragment {
private TopItem topItem;
}
public class SomePagerAdapter extends FragmentStatePagerAdapter {
private TopItem topItem;
public void changeTopItem(TopItem newTopItem) {
topItem = newTopItem;
notifyDataSetChanged();
}
#Override
public int getItemPosition(Object object) {
if (((SomeFragment) object).getTopItemId() != topItem.getId()) {
return POSITION_NONE;
}
return super.getItemPosition(object);
}
}
Thanks for all the previous researchers!
The code below worked for me.
Create a class which extends the FragmentPagerAdapter class as below.
public class Adapter extends FragmentPagerAdapter {
private int tabCount;
private Activity mActivity;
private Map<Integer, String> mFragmentTags;
private FragmentManager mFragmentManager;
private int container_id;
private ViewGroup container;
private List<Object> object;
public Adapter(FragmentManager fm) {
super(fm);
}
public Adapter(FragmentManager fm, int numberOfTabs , Activity mA) {
super(fm);
mActivity = mA;
mFragmentManager = fm;
object = new ArrayList<>();
mFragmentTags = new HashMap<Integer, String>();
this.tabCount = numberOfTabs;
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return Fragment0.newInstance(mActivity);
case 1:
return Fragment1.newInstance(mActivity);
case 2:
return Fragment2.newInstance(mActivity);
default:
return null;
}}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Object object = super.instantiateItem(container, position);
if (object instanceof Fragment) {
Log.e("Already defined","Yes");
Fragment fragment = (Fragment) object;
String tag = fragment.getTag();
Log.e("Fragment Tag","" + position + ", " + tag);
mFragmentTags.put(position, tag);
}else{
Log.e("Already defined","No");
}
container_id = container.getId();
this.container = container;
if(position == 0){
this.object.add(0,object);
}else if(position == 1){
this.object.add(1,object);
}else if(position == 2){
this.object.add(2,object);
}
return object;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
if (object instanceof Fragment) {
Log.e("Removed" , String.valueOf(position));
}
}
#Override
public int getItemPosition (Object object)
{ int index = 0;
if(this.object.get(0) == object){
index = 0;
}else if(this.object.get(1) == object){
index = 1;
}else if(this.object.get(2) == object){
index = 2;
}else{
index = -1;
}
Log.e("Index" , "..................." + String.valueOf(index));
if (index == -1)
return POSITION_NONE;
else
return index;
}
public String getFragmentTag(int pos){
return "android:switcher:"+R.id.pager+":"+pos;
}
public void NotifyDataChange(){
this.notifyDataSetChanged();
}
public int getcontainerId(){
return container_id;
}
public ViewGroup getContainer(){
return this.container;
}
public List<Object> getObject(){
return this.object;
}
#Override
public int getCount() {
return tabCount;
}}
Then inside each Fragment you created, create an updateFragment method. In this method you change the things you need to change in the fragment. For example in my case, Fragment0 contained a GLSurfaceView which displays a 3d object based on a path to a .ply file, so inside my updateFragment method I change the path to this ply file.
then create a ViewPager instance,
viewPager = (ViewPager) findViewById(R.id.pager);
and an Adpater instance,
adapter = new Adapter(getSupportFragmentManager(), 3, this);
then do this,
viewPager.setAdapter(adapter);
viewPager.setOffscreenPageLimit(1);
Then inside the class were you initialized the Adapter class above and created a viewPager, every time you want to update one of your fragments (in our case Fragment0) use the following:
adapter.NotifyDataChange();
adapter.destroyItem(adapter.getContainer(), 0, adapter.getObject().get(0)); // destroys page 0 in the viewPager.
fragment0 = (Fragment0) getSupportFragmentManager().findFragmentByTag(adapter.getFragmentTag(0)); // Gets fragment instance used on page 0.
fragment0.updateFragment() method which include the updates on this fragment
adapter.instantiateItem(adapter.getContainer(), 0); // re-initialize page 0.
This solution was based on the technique suggested by Alvaro Luis Bustamante.
I know I'm late but still it could help someone. I'm just extending the accepted answer and I have also added the comment on it.
Well, the answer itself says it is inefficient
So in order to make it refresh only when required you can do this
private boolean refresh;
public void refreshAdapter() {
refresh = true;
notifyDataSetChanged();
}
#Override
public int getItemPosition(#NonNull Object object) {
if (refresh) {
refresh = false;
return POSITION_NONE;
} else {
return super.getItemPosition(object);
}
}
In ViewPager2 you can re-initialize the adapter again to refresh the pager list with new views. viewPager2.adapter = myPagerAdapter
1.First you have to set the getItemposition method in your Pageradapter class
2.You have to read the Exact position of your View Pager
3.then send that position as data location of your new one
4.Write update button onclick listener inside the setonPageChange listener
that program code is little bit i modified to set the particular position element only
public class MyActivity extends Activity {
private ViewPager myViewPager;
private List<String> data;
public int location=0;
public Button updateButton;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
data = new ArrayList<String>();
data.add("A");
data.add("B");
data.add("C");
data.add("D");
data.add("E");
data.add("F");
myViewPager = (ViewPager) findViewById(R.id.pager);
myViewPager.setAdapter(new MyViewPagerAdapter(this, data));
updateButton = (Button) findViewById(R.id.update);
myViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int i, float v, int i2) {
//Toast.makeText(MyActivity.this, i+" Is Selected "+data.size(), Toast.LENGTH_SHORT).show();
}
#Override
public void onPageSelected( int i) {
// here you will get the position of selected page
final int k = i;
updateViewPager(k);
}
#Override
public void onPageScrollStateChanged(int i) {
}
});
}
private void updateViewPager(final int i) {
updateButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(MyActivity.this, i+" Is Selected "+data.size(), Toast.LENGTH_SHORT).show();
data.set(i, "Replaced "+i);
myViewPager.getAdapter().notifyDataSetChanged();
}
});
}
private class MyViewPagerAdapter extends PagerAdapter {
private List<String> data;
private Context ctx;
public MyViewPagerAdapter(Context ctx, List<String> data) {
this.ctx = ctx;
this.data = data;
}
#Override
public int getCount() {
return data.size();
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
#Override
public Object instantiateItem(View collection, int position) {
TextView view = new TextView(ctx);
view.setText(data.get(position));
((ViewPager)collection).addView(view);
return view;
}
#Override
public void destroyItem(View collection, int position, Object view) {
((ViewPager) collection).removeView((View) view);
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
#Override
public Parcelable saveState() {
return null;
}
#Override
public void restoreState(Parcelable arg0, ClassLoader arg1) {
}
#Override
public void startUpdate(View arg0) {
}
#Override
public void finishUpdate(View arg0) {
}
}
}
what worked for me was going viewPager.getAdapter().notifyDataSetChanged();
and in the adapter putting your code for updating the view inside getItemPosition like so
#Override
public int getItemPosition(Object object) {
if (object instanceof YourViewInViewPagerClass) {
YourViewInViewPagerClass view = (YourViewInViewPagerClass)object;
view.setData(data);
}
return super.getItemPosition(object);
}
might not be the most correct way of going about it but it worked (the return POSITION_NONE trick caused a crash for me so wasnt an option)
Always returning POSITION_NONE is simple but a little inefficient way because that evoke instantiation of all page that have already instantiated.
I've created a library ArrayPagerAdapter to change items in PagerAdapters dynamically.
Internally, this library's adapters return POSITION_NONE on getItemPosiition() only when necessary.
You can change items dynamically like following by using this library.
#Override
protected void onCreate(Bundle savedInstanceState) {
/** ... **/
adapter = new MyStatePagerAdapter(getSupportFragmentManager()
, new String[]{"1", "2", "3"});
((ViewPager)findViewById(R.id.view_pager)).setAdapter(adapter);
adapter.add("4");
adapter.remove(0);
}
class MyPagerAdapter extends ArrayViewPagerAdapter<String> {
public MyPagerAdapter(String[] data) {
super(data);
}
#Override
public View getView(LayoutInflater inflater, ViewGroup container, String item, int position) {
View v = inflater.inflate(R.layout.item_page, container, false);
((TextView) v.findViewById(R.id.item_txt)).setText(item);
return v;
}
}
Thils library also support pages created by Fragments.
This is a horrible problem and I'm happy to present an excellent solution; simple, efficient, and effective !
See below, the code shows using a flag to indicate when to return POSITION_NONE
public class ViewPagerAdapter extends PagerAdapter
{
// Members
private boolean mForceReinstantiateItem = false;
// This is used to overcome terrible bug that Google isn't fixing
// We know that getItemPosition() is called right after notifyDataSetChanged()
// Therefore, the fix is to return POSITION_NONE right after the notifyDataSetChanged() was called - but only once
#Override
public int getItemPosition(Object object)
{
if (mForceReinstantiateItem)
{
mForceReinstantiateItem = false;
return POSITION_NONE;
}
else
{
return super.getItemPosition(object);
}
}
public void setData(ArrayList<DisplayContent> newContent)
{
mDisplayContent = newContent;
mForceReinstantiateItem = true;
notifyDataSetChanged();
}
}
This is for all those like me, which need to update the Viewpager from a service (or other background thread) and none of the proposals have worked:
After a bit of logchecking i realized, that the notifyDataSetChanged() method never returns. getItemPosition(Object object) is called an all ends there without further processing. Then i found in the docs of the parent PagerAdapter class (is not in the docs of the subclasses), "Data set changes must occur on the main thread and must end with a call to notifyDataSetChanged() ".
So, the working solution in this case was (using FragmentStatePagerAdapter
and getItemPosition(Object object) set to return POSITION_NONE) :
and then the call to notifyDataSetChanged() :
runOnUiThread(new Runnable() {
#Override
public void run() {
pager.getAdapter().notifyDataSetChanged();
}
});
In my case there is a textView in my Viewpager, on a button click in mainActivity I want to change the color of that textView and update pagerAdapter. On the button Click I saved the color in SharedPreference and update pagerAdapter, that it can update the color taken from shared prefrence. So, I update viewPager view the following way .
btn_purple.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int color = ContextCompat.getColor(mContext, R.color.colorPrimaryDark2);
editor.putInt("sahittoFontColor", color);
editor.apply();
toNotifyDatasetChanged();
}
});
now the update method :
private void toNotifyDatasetChanged (){
if(viewPager!=null&& pagerAdapter!=null) {
viewPager.setAdapter(null);
viewPager.setAdapter(pagerAdapter);
}
}
And my pagerAdapter Was :
pagerAdapter = new Sahitto_ViewPagerAdapter (mContext, filenameParameter, 30, lineList);
viewPager.setAdapter(pagerAdapter);
And in instantiateItem was (in PagerAdapter) :
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mContext);
int bnfntcolor=settings.getInt("sahittoFontColor", 0);
if (bnfntcolor!=0){
textView.setTextColor(bnfntcolor);
}
Thus, when I click the button, the color changes immediately in pagerAdapter's Textview.
Happy coding.
Instead of returning POSITION_NONE and creating all fragments again, you can do as I suggested here: Update ViewPager dynamically?
I think I've made a simple way to notify of data set changes:
First, change a bit the way the instantiateItem function works:
#Override
public Object instantiateItem(final ViewGroup container, final int position) {
final View rootView = mInflater.inflate(...,container, false);
rootView.setTag(position);
updateView(rootView, position);
container.addView(rootView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
mViewPager.setObjectForPosition(rootView, position);
return rootView;
}
for "updateView" , fill the view with all the data you wish to fill (setText,setBitmapImage,...) .
verify that destroyView works like this:
#Override
public void destroyItem(final ViewGroup container, final int position, final Object obj) {
final View viewToRemove = (View) obj;
mViewPager.removeView(viewToRemove);
}
Now, suppose you need to change the data, do it, and then call the next function on the PagerAdapter :
public void notifyDataSetChanged(final ViewPager viewPager, final NotifyLocation fromPos,
final NotifyLocation toPos) {
final int offscreenPageLimit = viewPager.getOffscreenPageLimit();
final int fromPosInt = fromPos == NotifyLocation.CENTER ? mSelectedPhotoIndex
: fromPos == NotifyLocation.MOST_LEFT ? mSelectedPhotoIndex - offscreenPageLimit
: mSelectedPhotoIndex + offscreenPageLimit;
final int toPosInt = toPos == NotifyLocation.CENTER ? mSelectedPhotoIndex
: toPos == NotifyLocation.MOST_LEFT ? mSelectedPhotoIndex - offscreenPageLimit
: mSelectedPhotoIndex + offscreenPageLimit;
if (fromPosInt <= toPosInt) {
notifyDataSetChanged();
for (int i = fromPosInt; i <= toPosInt; ++i) {
final View pageView = viewPager.findViewWithTag(i);
mPagerAdapter.updateView(pageView, i);
}
}
}
public enum NotifyLocation {
MOST_LEFT, CENTER, MOST_RIGHT
}
For example if you wish to notify all of the views that are being shown by the viewPager that something has changed, you can call:
notifyDataSetChanged(mViewPager,NotifyLocation.MOST_LEFT,NotifyLocation.MOST_RIGHT);
That's it.

Categories

Resources