androidkotlinandroid-viewpagerandroid-pageradapter

ViewPager list refresh is not working properly


I have custom ViewPager and PagerAdapter which is loading items inside instantiateItem. It is working fine if I initialize it for the first time with set list of items.

But as I call refresh on the list and I want to populate Adapter with new (totally different) list, after calling viewPager.adapter?.notifyDataSetChanged(), PagerAdapter stops working properly and those items are blank pages without content and I can swipe through them in UI.

PageScreen is not Fragment. It is just ViewGroup container which is inflating layout and setting values out of specific item. It is similar to ViewHolder + Binder in RecyclerView.

Also instatiateItem() is called only once as i add new list and call notifyDataSetChanged(). At start it is called 3 items, which is amount of PageScreen items in first list.

//init
val pages = mutableListOf<PageScreen>()
pages.add(PageScreen(activity, app, itemJs1, onClick = {onItemClicked(itemJs1.id)}))
pages.add(PageScreen(activity, app, itemJs2, onClick = {onItemClicked(itemJs2.id)}))
pages.add(PageScreen(activity, app, itemJs3, onClick = {onItemClicked(itemJs3.id)}))

swipePager.adapter = CustomPagerAdapter(pages).also { it.notifyDataSetChanged() }

...

//on refresh after API call
pages.clear()

contentList.forEach{item-> pages.add(PageScreen(activity, app, item, onClick = {onItemClicked(item.id)}))}

(swipePager.adapter as? CustomPagerAdapter)?.notifyDataSetChanged()

Also tried this (same result):

//on refresh after API call

val newPages = mutableListOf<PageScreen>()

contentList.forEach{item-> newPages.add(PageScreen(activity, app, item, onClick = {onItemClicked(item.id)}))}

swipePager.adapter = CustomPagerAdapter(newPages).also { it.notifyDataSetChanged() }

Adapter:

class CustomPagerAdapter(private var pageList: MutableList<PageScreen>) : PagerAdapter() {
    override fun isViewFromObject(view: View, `object`: Any): Boolean {
        return view == `object`
    }

    override fun getCount(): Int {
        return pageList.size
    }

    override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
        container.removeView(`object` as View)
    }

    override fun instantiateItem(container: ViewGroup, position: Int): View {
        val layout = pageList[position].getScreen(container)
        container.addView(layout)
        return layout
    }
}

Also I tried to properly refresh items (I expect that this is done internally by PagerAdapter and ViewPager when I call notifyDataSetChanged()) by removing them from ViewPager contentView and calling instantiateItem() for each item. But same result as above. Now every single page was blank. Function below is added to CustomPagerAdapter.

fun refreshItems(vp: ViewPager, data: MutableList<PageScreen>){
        pageList.apply {
            forEachIndexed{pos, item->
                item.screenView?.let { sv->
                    destroyItem(vp, pos, sv)
                }
            }
            clear()
            addAll(data)
            forEachIndexed { pos, _ -> instantiateItem(vp, pos) }
        }
        notifyDataSetChanged()
    }

UPDATE: I managed to "fix" this by setting ViewPager height to fixed value instead WRAP_CONTENT but its not a solution. I want ViewPager with dynamic height, because some of its children can have different height + setting something to static is not good approach in Android. Some phones with square displays could have cropped page then.

What happened is as I replaced all items, those "blank" pages were items with 0px height and 0px width for some unknown reason.

If I replaced ViewPager height to dp value, it "worked". But as I replaced those Views, first item was always blank. but as I scrolled to third one and back to first, item was there for some reason.

Also I don't get that height problem. I have function inside ViewPager which is setting its height based on tallest child in list. It works if list is static, but it is not working now as I refresh that.


Solution

  • Recreating whole ViewPagerAdapter is a solution for this problem.

    I called API inside onPageSelected() and remembered position returned to listener function.

    Then I recreated whole adapter like this:

    swipePager.apply {
       adapter = null
       adapter = CustomPagerAdapter(newPages)
       adapter?.notifyDataSetChanged()
       invalidate()
    }
    

    and after refresh I scrolled to remembered position like this:

    setCurrentItem(position, true)
    

    This solution will not left blank pages, but I had to set static height for my ViewPager, which can be a problem on mdpi screens which sometimes cannot redraw particular dp value from XML layout correctly. Phone with this problem is for example Sony Xperia E5 which has xhdpi screen and part of my ViewPager is cropped from the bottom.

    This have to be tuned manually with separate dimens.xml for both mdpi and xhdpi.