remove fragment in viewPager2 use FragmentStateAdapter, but still display
Asked Answered
L

2

33

I have a viewPager2 and FragmentStateAdapter, and there are Fragement1, 2,3 4, I am in fragment2, and want to remove fragment3, and display fragment4 after fragment2. The problem is it always show me fragment3(data), the debug shows the fragment3 has been removed, but the displayed page still has fragment3 content.

Adpter:

class TipsAdapter(
private val items: MutableList<TripPage>,
context: FragmentActivity
) : FragmentStateAdapter(context) {

private val fragmentFactory = context.supportFragmentManager.fragmentFactory
private val classLoader = context.classLoader

override fun getItemCount(): Int = items.size

override fun createFragment(position: Int): Fragment {
    val pageInfo = items[position]
    val fragment = fragmentFactory.instantiate(classLoader, pageInfo.fragmentClass.name)
    fragment.arguments = Bundle().also { it.putParcelable(PAGE_INFO, pageInfo) }
    return fragment
}

fun getFragmentName(position: Int) = items[position].fragmentClass.simpleName

fun removeFragment(position: Int) {
    items.removeAt(position)
    notifyItemRemoved(position)
    notifyItemRangeChanged(position, items.size)
    notifyDataSetChanged()
}

}

delete fragment code:

      if ((view_pager.adapter as TipsAdapter).getFragmentName(index + 1).equals(
            TripPreFragment::class.simpleName) &&
            viewModel.shouldRemoveBulkApply()) {
            (view_pager.adapter as TipsAdapter).removeFragment(index + 1)
            view_pager.setCurrentItem(index + 1, true)
        } else {
            view_pager.setCurrentItem(index + 1, true)
        }
Lanai answered 14/9, 2019 at 20:11 Comment(1)
I have 2 fragments mainfragment and otherfragment . I'm adding otherfragment after every 5th position and it's not in arraylist so how to remove that otherfragment if its not in list ? I'm using viewpager2Sitar
L
57

Finally, it works for me. when we call notifyDataSetChanged(), android will call method getItemId() in adapter, to check if the item has been updated or not. it returns the position in source code. which means in list 0..i..n, if you remove i , it becomes to 0...i...n-1, the i won't change and the data won't update in adater.

    /**
 * Default implementation works for collections that don't add, move, remove items.
 * <p>
 * TODO(b/122670460): add lint rule
 * When overriding, also override {@link #containsItem(long)}.
 * <p>
 * If the item is not a part of the collection, return {@link RecyclerView#NO_ID}.
 *
 * @param position Adapter position
 * @return stable item id {@link RecyclerView.Adapter#hasStableIds()}
 */
@Override
public long getItemId(int position) {
    return position;
}

what you need to do is rewrite this method and containsItem(long),

for my case, I use the hashcode of each fragment:

class TipsAdapter(
     private val items: MutableList<TripPreferencesOptimizerPage>,
     context: FragmentActivity
    ) : FragmentStateAdapter(context) {

private val fragmentFactory = context.supportFragmentManager.fragmentFactory
private val classLoader = context.classLoader
private val pageIds= items.map { it.hashCode().toLong() }

override fun getItemCount(): Int = items.size

override fun createFragment(position: Int): Fragment {
    val pageInfo = items[position]
    val fragment = fragmentFactory.instantiate(classLoader, pageInfo.fragmentClass.name)
    fragment.arguments = Bundle().also { it.putParcelable(PAGE_INFO, pageInfo) }
    return fragment
}

fun getFragmentName(position: Int) = items[position].fragmentClass.simpleName

fun removeFragment(position: Int) {
    items.removeAt(position)
    notifyItemRangeChanged(position, items.size)
    notifyDataSetChanged()
}

override fun getItemId(position: Int): Long {
    return items[position].hashCode().toLong() // make sure notifyDataSetChanged() works
}

override fun containsItem(itemId: Long): Boolean {
    return pageIds.contains(itemId)
}
}
Lanai answered 15/9, 2019 at 12:40 Comment(10)
please find more useful information: #7263791Lanai
Wow, you are awesome! you saved the day :) CheersCaptive
@Lanai that is a completely different part of API and unfortunately not relevantValuer
I spent several hours trying to tackle this problem. Unfortunate thing about Android, it's not always intuitive and documentation is incomplete regarding these use cases.Anders
I have 2 fragments mainfragment and otherfragment . I'm adding otherfragment after every 5th position and it's not in arraylist so how to remove that otherfragment if its not in list ?Sitar
I'm using viewpager2Sitar
for me every time you update the number you also need to reinitialize pageIds = items.map { it.hashCode().toLong() }Thi
Thank you for this solution. I have tried it and noticed that calling notifyDataSetChanged is actually not needed and does even have an ugly side effect, i.e. the view pager jumps to the first page when the last one is deleted. In most cases, you'd want to see the previous page instead. Also, as @MihaeKheel commented, updating the pageId map is needed in the removeFragment()method to keep page ids and items in sync.Abet
what is the 'items' in the constructor of the adapter. Is it a local cache of the pager fragments ?Cookshop
For me, it's working without notifyItemRangeChanged notifyDataSetChanged; only added notifyItemRemovedChloric
A
0

The Simplest solution is bellow..

items.removeAt(position)
notifyDataSetChanged()

Then call this from your activity or fragment

view_pager.setCurrentItem(index - 1, true)

This will remove your fragment and animate like delete item in recyclerview if you are viewing deleting fragment.

Alcides answered 4/1, 2023 at 12:29 Comment(1)
No, it does not work.Mahaliamahan

© 2022 - 2024 — McMap. All rights reserved.