PagerAdapter.notifyDataSetChanged does not refresh fragments
Asked Answered
W

7

18

My Activity contains a ViewPager and it's custom Adapter which extends FragmentStatePagerAdapter. ViewPager contains 3 fragments

Code to remove Fragments from the ViewPager

MainActivity

public void deleteElement(){
    final int currentItem = mViewPager.getCurrentItem();
    mPagerAdapter.removeItem(currentItem);
    mPagerAdapter.notifyDataSetChanged();
}

CustomPagerAdapter

private ArrayList<Item> data;
public void removeItem(int index){
        data.remove(index);
}

when removing the middle Fragment (index 1):

  • from data i'm removing the correct item.
  • Problem is that i (i guess) Fragment 3 is removed after notifyDataSetChanged and the current Fragment is still the fragment that the user saw and the data that is being loaded is from the SavedInstance bundle

So the end result that the user still see the same Fragment that he tried to remove which is kinda suck for him.

Ideas?

***** EDIT ******

seems like ViewPager2 Features might solve this issue and many other issues as well

Wallsend answered 23/1, 2015 at 18:48 Comment(4)
I would suggest switching to my ArrayPagerAdapter, which handles this. I am not terribly surprised that FragmentStatePagerAdapter has problems in this area, which is why I rolled something separate.Gamaliel
Try thisCense
Good simple solution: https://mcmap.net/q/57703/-onresume-not-called-in-fragment-using-tablayout-and-viewpagerMaund
@gnB this is not a solution to this problemLordinwaiting
M
4

I know this question is a bit old, but it might be interesting to know that Google recently solved this problem with ViewPager2. See examples here

First of all, a the following dependency in your build.gradle file :

  dependencies {
     implementation 'androidx.viewpager2:viewpager2:1.0.0-beta02'
  }

Now 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" />

Then you will need to replace ViewPager by ViewPager2 in your activity

ViewPager2 needs either a RecycleView.Adapter, or a FragmentStateAdapter :

    public class TabAdapter extends FragmentStateAdapter {
        private final List<Tab> tabs;

        public TabAdapter(@NonNull FragmentManager fm, List<Tab> tabs, Lifecycle lifecycle) {
            super(fm, lifecycle);

            this.tabs = tabs;
        }

        @NonNull
        @Override
        public Fragment createFragment(int position) {
            //create your fragment here
        }

        @Override
        public int getItemCount() {
            return tabs.size();
        }

        @Override
        public long getItemId(int position) {
            // use a distinct id for each item to allow the adapter to see the changes
        }
    }

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

In order to remove data, after removing the item from your collection you can either call the notifyDataSetChanged() method or more specifically the notifyItemRemoved() with the position of removed item

Mainz answered 7/8, 2019 at 10:23 Comment(3)
Please add an example of how to remove data and I will approve your answer. Thanks!Wallsend
I just edited my answer to show how to remove the item. I also add the getItemId method in the adapter, it might be needed by the adapter to monitor the changesMainz
According to the JavaDoc of getItemId(int) you should also override containsItem(long) When overriding, also override {@link #containsItem(long)}.Braeunig
R
30

Try adding this:

private class MyPagerAdapter extends FragmentStatePagerAdapter {

//... your existing code

    @Override
    public int getItemPosition(Object object){
        return PagerAdapter.POSITION_NONE;
    }

}
Reliance answered 23/1, 2015 at 19:12 Comment(4)
but then it will cause android to restart all my fragments which is kind of pointless no?Wallsend
Yes, here others optionsReliance
i know about those 2 options and i saw those posts before asking the question. the problem is that i'm not updating a view but removing one. i can't be sure which view will be removed thus calling fragment by tag is not really safe (maybe it's already destroyed? should i update all other fragments?)Wallsend
Thanks its work for me: extends FragmentStatePagerAdapterHowl
C
5

You have no choice but to implement PagerAdapter#getItemPosition. Looking at the source that's exactly how ViewPager determines how to update its position, add new fragments or drop dead ones after notifyDataSetChanged. It shouldn't be too hard, you just need to access the state of the passed fragment, and you also need to access the master state in your activity which holds your ordering.

It's also required you implement getItemId to return a consistent ID for each fragment, because they can be reordered.

This is how I handled it in a ViewPager of photos.

    private inner class PhotoPagerAdapter(fm: FragmentManager) : FragmentPagerAdapter(fm) {
    override fun getItem(position: Int): PhotoViewFragment {
        // getItem is called to instantiate the fragment for the given page.
        return PhotoViewFragment.newInstance(position, [email protected][position])
    }

    override fun getItemId(position: Int): Long {
        return [email protected][position].ID!!.hashCode().toLong()
    }

    override fun getItemPosition(`object`: Any): Int {
        val frag = `object` as PhotoViewFragment
        val idx = [email protected] { ph -> ph.ID == frag.dataBinding.photo.ID }
        return if (idx >= 0) idx else PagerAdapter.POSITION_NONE
    }

    override fun getCount(): Int {
        return [email protected]
    }
}
Cavalcade answered 7/9, 2017 at 22:54 Comment(0)
M
4

I know this question is a bit old, but it might be interesting to know that Google recently solved this problem with ViewPager2. See examples here

First of all, a the following dependency in your build.gradle file :

  dependencies {
     implementation 'androidx.viewpager2:viewpager2:1.0.0-beta02'
  }

Now 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" />

Then you will need to replace ViewPager by ViewPager2 in your activity

ViewPager2 needs either a RecycleView.Adapter, or a FragmentStateAdapter :

    public class TabAdapter extends FragmentStateAdapter {
        private final List<Tab> tabs;

        public TabAdapter(@NonNull FragmentManager fm, List<Tab> tabs, Lifecycle lifecycle) {
            super(fm, lifecycle);

            this.tabs = tabs;
        }

        @NonNull
        @Override
        public Fragment createFragment(int position) {
            //create your fragment here
        }

        @Override
        public int getItemCount() {
            return tabs.size();
        }

        @Override
        public long getItemId(int position) {
            // use a distinct id for each item to allow the adapter to see the changes
        }
    }

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

In order to remove data, after removing the item from your collection you can either call the notifyDataSetChanged() method or more specifically the notifyItemRemoved() with the position of removed item

Mainz answered 7/8, 2019 at 10:23 Comment(3)
Please add an example of how to remove data and I will approve your answer. Thanks!Wallsend
I just edited my answer to show how to remove the item. I also add the getItemId method in the adapter, it might be needed by the adapter to monitor the changesMainz
According to the JavaDoc of getItemId(int) you should also override containsItem(long) When overriding, also override {@link #containsItem(long)}.Braeunig
C
1

You can try to use FragmentPagerAdapter instead of FragmentStatePagerAdapter. The thing is, i have also run into this issue, and it helped me.

Carolecarolee answered 24/2, 2018 at 5:49 Comment(1)
single-line solution should be posted in comment.Salvatoresalvay
D
1

You have to first set the adapter to null and then set it back:

int currentPosition = mViewPager.getCurrentItem();
mPagerAdapter.notifyDataSetChanged();
mViewPager.setAdapter(null);
mViewPager.setAdapter(mPagerAdapter);
mViewPager.setCurrentItem(currentPosition);
Decanter answered 14/9, 2018 at 7:34 Comment(1)
int getItemPosition(Object object) { return POSITION_NONE; }Howl
S
0

Differentiate the Fragment then android system will take care of recycle the fragments.

For examaple - override getItemId provide unique id.

FragmentStateAdapter (ViewPager2) - Kotlin Version.

class HomeViewPagerAdapter(
fa: FragmentActivity
) : FragmentStateAdapter(fa) {

lateinit var fragments: ArrayList<Fragments>

override fun getItemCount(): Int {
    return fragments.size
}

override fun getItemId(position: Int): Long {
    return fragments[position].id.ordinal.toLong()
}
override fun createFragment(position: Int): Fragment {
    return fragments[position].fragment
}
}
data class Fragments (val id: FragmentId, val fragment: Fragment)
enum class FragmentId {
    PageOne, 
    PageTwo,
    PageThree
}

Update Adapter

    private fun setupAdapter(fragments: ArrayList<Fragments>) {
    if (adapter != null) {
        adapter?.fragments = fragments
        adapter?.notifyDataSetChanged()
    } else {
        adapter = HomeViewPagerAdapter(requireActivity())
        adapter?.fragments = fragments
        binding.homeViewPager.adapter = adapter
        binding.locationDisabledLl.setOnClickListener {
            findNavController().navigate(HomeFragmentDirections.actionHomeToPermission())
        }
    }
}
Skirl answered 30/6, 2021 at 15:16 Comment(0)
H
-1

You can try to use FragmentStatePagerAdapter instead of FragmentPagerAdapter
public


Its work for me..!

Howl answered 1/7, 2019 at 8:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.