Updating ViewPager With New Data Dynamically

24 / Apr / 2015 by Noor Alam 3 comments

ViewPager is a layout manager that allows users to flip and view pages left and right. It is used in conjunction with PagerAdapter,  FragmentPagerAdapter or FragmentStatePagerAdapter. We attach adapter consisting of either Fragment objects or simple View objects.

Note: You can download source code of a sample application from here to run the android application.

ViewPagerRefresh

Difference Between PagerAdapter, FragmentPagerAdapter and FragmentStatePagerAdapter

 PagerAdapter:

  1. PagerAdapter is base class for both FragmentPagerAdapter and FragmentStatePagerAdapter.
  2. If you have to show custom views (not Fragments) then subclass it and override its

In instantiateItem method we inflate our view from xml file and add it to ViewGroup parameter which is the reference of the ViewPager.

MyPagerAdapter.java

@Override
    public Object instantiateItem(ViewGroup container, int position) {
        // Inflate a new layout from our resources
        View view = mLayoutInflater.inflate(R.layout.photo_layout, container, false);
        // Retrieve a TextView from the inflated View, and update it's text
        TextView titleTextView = (TextView) view.findViewById(R.id.title);
        Utils.DummyItem dummyItem = mDummyItems.get(position);
        titleTextView.setText(dummyItem.getImageTitle());
        ImageView imageView = (ImageView) view.findViewById(R.id.image);
        ImageLoaderUtil.downloadImage(dummyItem.getImageUrl(), imageView);
        view.setTag(dummyItem);
        // Add the newly created View to the ViewPager
        container.addView(view);
        Log.i(TAG, "instantiateItem() [position: " + position + "]" + " childCount:" + container.getChildCount());
        // Return the View
        return view;
    }

3. It keeps maximum three views in memory, one which is currently visible, one which is left and one is right of the visible item. While scrolling, the pages which goes out of the screen will be destroyed in destroyItem(ViewGroup, int, Object) method.

MyPagerAdapter.java

    /**
     * Destroy the item from the {@link android.support.v4.view.ViewPager}. In our case this is simply removing the
     * {@link View}.
     */
    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        container.removeView((View) object);
        Log.i(TAG, "destroyItem() [position: " + position + "]" + " childCount:" + container.getChildCount());
    }

4. getCount() returns the number of items which will be shown in the ViewPager.                   5. isViewFromObject(View, Object)  method checks whether the Object returned from instantiateItem(ViewGroup, int) method is linked to the View supplied here.

Note: In PagerAdapter, PagerFragmentAdapter and PagerFragmentStateAdapter, the Views or Fragments are recognised by a key Object not by their index or position in the adapter.

In simplest implementation, we return View or Fragment created in instantiateItem(ViewGroup, int) itself and in isViewFromObject(View, Object) method we simply compare View and Object to check the association between them.

MyPagerAdapter.java

    /**
     * @return true if the value returned from {@link #instantiateItem(ViewGroup, int)} is the
     * same object as the {@link View} added to the {@link android.support.v4.view.ViewPager}.
     */
    @Override
    public boolean isViewFromObject(View view, Object object) {
        return object == view;
    }

FragmentPagerAdapter

  1. In FragmentPagerAdapter we implement only getItem() and getCount() methods in order to get a working adapter.
  2. getItem() is called by the instantiateItem() internally in order to create a new fragment.
  3. In FragmentPagerAdapter, once a fragment has been created, it will never be destroyed throughout the life of the adapter.The fragments will be held by the FragmentManager and will be reused again whenever we will require a new fragment to show.Fragments needed again will be fetched from the FragmentManager and its view hierarchy will be created again by going through  onCreateView(), onViewCreated(), onActivityCreated(), onViewStateRestored(), onStart(), and onResume()  methods.
  4. If the fragment is being created first time its onAttach(), onCreate(), onCreateView(), onViewCreated(), onActivityCreated(), onViewStateRestored(), onStart(), and onResume() methods will be called sequentially.
  5. If the fragment is going off the screen its onPause(), onStop(), onDestroyView() methods will be called in order. Note that the fragments onDestroy() and onDetach() methods will never be called when the fragments are going off the screen, means fragments are not being destroyed once created and will be held by the FragmentManager.

MyFragmentPagerAdapter.java

@Override
    public Fragment getItem(int position) {
        //We are doing this only for checking the total number of fragments in the fragment manager.
        List<Fragment> fragmentsList = mFragmentManager.getFragments();
        int size = 0;
        if (fragmentsList != null) {
            size = fragmentsList.size();
        }
        Utils.DummyItem dummyItem = mDummyItems.get(position);
        Log.i(TAG, "********getItem position:" + position + " size:" + size + " title:" + dummyItem.getImageTitle() + " url:" + dummyItem.getImageUrl());

        //Create a new instance of the fragment and return it.
        SampleFragment sampleFragment = (SampleFragment) SampleFragment.getInstance(/*dummyItem.getImageUrl(), dummyItem.getImageTitle()*/);
        //We will not pass the data through bundle because it will not gets updated by calling notifyDataSetChanged()  method. We will do it through getter and setter.
        sampleFragment.setDummyItem(dummyItem);
        return sampleFragment;
    }

If the number of fragments are large, it will take a lot of memory if we use FragmentPagerAdapter because it never destroys the Fragments once created. It only destroys fragments’s view hierarchy and keeps its state internally. To overcome this shortcoming we have FragmentStatePagerAdapter which we are going to discuss here.

FragmentStatePagerAdapter

FragmentStatePagerAdapter differs from the FragmentPagerAdapter only in one way that it destroys the fragments which are going off the screen. So Fragments are created, attached, destroyed and detached keeping the memory uses low. It holds maximum three fragments and keep destroying the off screen fragments. It is suitable in the situation where the number of fragments are large. In every other aspect it is just like the FragmentPagerAdapter.

Updating ViewPager With New Data Dynamically:

If we populate or update our data collection with new data and call notifyDataSetChanged() on adapter(PagerAdapter or FragmentPagerAdapter or FragmentStatePagerAdapter instance), adapter’s getItemPosition(Object) is called. It is called for all the active pages of the adapter(three maximum). If the data in the fragment present in the collection we simply return its position and if new data not found in the collection we return POSITION_NONE which forces fragments to recreate its view hierarchy again with new data.

MyFragmentPagerAdapter.java

    /**
     * This method is only gets called when we invoke {@link #notifyDataSetChanged()} on this adapter.
     * Returns the index of the currently active fragments.
     * There could be minimum two and maximum three active fragments(suppose we have 3 or more  fragments to show).
     * If there is only one fragment to show that will be only active fragment.
     * If there are only two fragments to show, both will be in active state.
     * PagerAdapter keeps left and right fragments of the currently visible fragment in ready/active state so that it could be shown immediate on swiping.
     * Currently Active Fragments means one which is currently visible one is before it and one is after it.
     *
     * @param object Active Fragment reference
     * @return Returns the index of the currently active fragments.
     */
    @Override
    public int getItemPosition(Object object) {
        Utils.DummyItem dummyItem = (Utils.DummyItem) ((View) object).getTag();
        int position = mDummyItems.indexOf(dummyItem);
        if (position >= 0) {
            // The current data matches the data in this active fragment, so let it be as it is.
            return position;
        } else {
            // Returning POSITION_NONE means the current data does not matches the data this fragment is showing right now.  Returning POSITION_NONE constant will force the fragment to redraw its view layout all over again and show new data.
            return POSITION_NONE;
        }
    }

This method tells the adapter to force out of sync fragments to recreate its view hierarchy again(in case of FragmentPagerAdapter) or destroy fragments and recreate it(in case of FragmentStatePagerAdapter and PagerAdapter) or tells that the position and state of the active page(s) are appropriate and it doesn’t need to be updated.

If we pass the data to the fragment using setArgument(Bundle), event if we force fragment to recreate its view hierarchy, it will show the old data which we have passed earlier using setArgument() method. To overcome this problem we will supply data to the fragments using a setter while creating the fragment first time and override instantiateItem()  method to check whether the old data and new data are different or equal. If new data is not equal to old data, we replace the old data with new one using the same setter. Don’t forget to call super.instantiateItem(container, position) at end of the method. In this way we can update the fragment with new data.

MyFragmentPagerAdapter.java


 @Override
    public Object instantiateItem(ViewGroup container, int position) {
        List<Fragment> fragmentsList = mFragmentManager.getFragments();
        if (fragmentsList != null && position <= (fragmentsList.size() - 1)) {
            SampleFragment sampleFragment = (SampleFragment) fragmentsList.get(position);
            Utils.DummyItem dummyItem = mDummyItems.get(position);
            //If the current data of the fragment changed, set the new data
            if (!dummyItem.equals(sampleFragment.getDummyItem())) {
                sampleFragment.setDummyItem(dummyItem);
                Log.i(TAG, "********instantiateItem position:" + position + " FragmentDataChanged");
            }
        } else {
            //No fragment instance available for this index, create a new fragment by calling getItem() and show the data.
            Log.i(TAG, "********instantiateItem position:" + position + " NewFragmentCreated");
        }

        return super.instantiateItem(container, position);
    }

For equality checking we must have to override equal method of our model data class.

Inside the Utils.java, DummyItem class

public static class DummyItem{
        private String imageUrl;
        private String imageTitle;

        public DummyItem(String imageUrl, String imageTitle) {
            this.imageUrl = imageUrl;
            this.imageTitle = imageTitle;
        }

        public String getImageUrl() {
            return imageUrl;
        }

        public String getImageTitle() {
            return imageTitle;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if(getClass() != obj.getClass()){
                return false;
            }

            final DummyItem other = (DummyItem) obj;
            if (!this.imageUrl.equals(other.imageUrl)) {
                return false;
            }
            if (!this.imageTitle.equals(other.imageTitle)) {
                return false;
            }

            /*if(obj == this){
                return true;
            }*/
            return true;
        }

        //The hashCode() method of objects is used when you insert them into a HashTable, HashMap or HashSet.
        //Since we are using these objects to store in List, we are not going to override it.
        /*@Override
        public int hashCode() {
            return super.hashCode();
        }*/
    }

In case of FragmentStatePagerAdapter we will only override getItemPosition() method in the same way as we just did in the FragmentPagerAdapter. We will not override onInstantiateItem() method in FragmentStatePagerAdapter since FragmentStatePagerAdapter destroys the fragments which go off the screen and creates a new fragment every time it needs one all over again. FragmentManager does not hold the fragments which are going off the screen and simply destroys it. In this way new fragments gets new data from the object collection directly.

Same thing is true for PagerAdapter as well and we only override getItemPosition() in order to update the view with new data because FragmentPager also destroys the views going off the screen.

For better understanding of the process, please download the sample source code from here , run the android application and go through the code.

FOUND THIS USEFUL? SHARE IT

comments (3)

  1. samad

    hi, i want to create string tabs dynamically using FragmentPagerAdapter. in getItem() i have return single fragment it contain different views. how to do this?

    Reply
  2. Bharat Ghimire

    nice Article it help me Lot to understand ViewPager in Depth please keep writing this kind Of Articles

    Reply

Leave a comment -