Pasha Oleynik
Pasha Oleynik

Reputation: 515

Getting “java.lang.IllegalArgumentException: No view found for id” in ViewPager's Fragment that in RecyclerView

I am getting exception:

E/MessageQueue-JNI: java.lang.IllegalArgumentException: No view found for id 0x7f0a0554 (ua.com.company.app:id/vpBanners) for fragment BannerItemFragment{30698be} (4c80b228-4303-4c80-b99d-a55b8359b8c2) id=0x7f0a0554}

My hierarchy looks like so:

Screen hierarchy

My adapter for vpHome:

class HomeViewPagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {

    private val mFragmentList: MutableList<Fragment> = ArrayList()
    private val mFragmentTitleList: MutableList<String> = ArrayList()

    override fun getItem(position: Int): Fragment {
        return mFragmentList[position]
    }

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

    override fun getPageTitle(position: Int): CharSequence? {
        return mFragmentTitleList[position]
    }

    fun addFragment(fragment: Fragment, title: String) {
        mFragmentList.add(fragment)
        mFragmentTitleList.add(title)
    }

}

And I apply it in this way:

private fun setupViewPager(viewPager: DisableSwipeViewPager) {
    vpAdapter = HomeViewPagerAdapter(childFragmentManager).apply {
        addFragment(ForYouFragment(), "for you")
        addFragment(AnotherFragment1(), "a1")
        addFragment(AnotherFragment2(), "a2")
    }
    viewPager.adapter = vpAdapter
}

Next, my SnapBannersCarouselViewHolder to handle items with ViewPager inside:

class SnapBannersCarouselViewHolder(
    private val mBinding: HomeFragmentItemSnapBannersCarouselBinding
) : RecyclerView.ViewHolder(mBinding.root) {

    companion object {
        // ...

        fun newInstance(
            inflater: LayoutInflater,
            parent: ViewGroup,
            onMoreInteractionListener: ((infoBlockId: String) -> Unit)?
        ): SnapBannersCarouselViewHolder {
            val binding =
                HomeFragmentItemSnapBannersCarouselBinding.inflate(inflater, parent, false)
            return SnapBannersCarouselViewHolder(
                binding,
                onMoreInteractionListener
            )
        }
    }

    fun bind(item: SnapBannersItem, fragmentManager: FragmentManager) {
        // ...
        val adapter = BannerPagesAdapter(fragmentManager, item.banners)
      
        with(mBinding.vpBanners) {
            // ...
            this.adapter = adapter
            offscreenPageLimit = 3
        }
    }

}

My RecyclerView adapter ForYouContentAdapter:

class ForYouContentAdapter(
    var data: List<HomeBaseItem> = emptyList(),
    var fragmentManagerRetriever: () -> FragmentManager
) : BaseRecyclerViewAdapter<HomeBaseItem>(data) {

    enum class ViewType(val value: Int) {
        // ...
        SNAP_BANNERS(6)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return when (viewType) {
            // ...
            ViewType.SNAP_BANNERS.value -> SnapBannersCarouselViewHolder.newInstance(
                mInflater!!,
                parent,
                onBannersMoreInteractionListener // todo possibly change it
            )
            else -> throw RuntimeException("Can not create view holder for undefined view type")
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (getItemViewType(position)) {
            // ...
            ViewType.SNAP_BANNERS.value -> {
                val vHolder = holder as SnapBannersCarouselViewHolder
                vHolder.bind(
                    getItem(position) as SnapBannersItem,
                    fragmentManagerRetriever.invoke()
                )
            }
        }
    }

}

And my fragmentManagerRetriever implementation in ForYouFragment looks like so:

private val fragmentManagerRetriever: () -> FragmentManager = {
    childFragmentManager
}

My BannerItemFragment code:

class BannerItemFragment : Fragment() {

    private lateinit var mBinding: HomeFragmentSnapBannerItemBinding

    companion object {
        // ...

        fun newInstance(
            item: RectWebViewItem
        ): BannerItemFragment {
            return BannerItemFragment()
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        mBinding = HomeFragmentSnapBannerItemBinding.inflate(inflater, container, false)
        return mBinding.root
    }

    // ...

}

In every place where I need to create fragments inside other fragments I am using childFragmentManager.

And when I open ForYouFragment first time, it works normally. My item with ViewPager works normally. But when I replace fragment in Activity's container (adding to back stack) being on ForYouFragment and then return back (back on HomeFragment because ForYouFragment inside HomeFragment), I am getting error.

To replace fragments I am using this method inside ForYouFragment:

private fun showAnotherFragment() {
    ActivityUtils.replaceFragmentToActivity(
        requireActivity(),
        SomeFragment.newInstance(),
        true
    )
}

And ActivityUtils code:

object ActivityUtils {

    fun replaceFragmentToActivity(
            activity: FragmentActivity,
            fragment: Fragment,
            addToBackStack: Boolean
    ) {
        replaceFragmentToActivity(activity, fragment, addToBackStack, containerId = R.id.fragmentContainer)
    }

    fun replaceFragmentToActivity(
            activity: FragmentActivity,
            fragment: Fragment,
            addToBackStack: Boolean,
            containerId: Int
    ) {
        val fragmentManager = activity.supportFragmentManager
        val transaction = fragmentManager.beginTransaction()
        transaction.replace(containerId, fragment)
        if (addToBackStack) {
            transaction.addToBackStack(null)
        }
        transaction.commitAllowingStateLoss()
    }

}

Please, help me understand why I am getting this exception?

UPD

Adapter for ViewPager inside RecyclerView:

class BannerPagesAdapter(
    fragmentManager: FragmentManager,
    var data: List<RectWebViewItem>
) : FragmentStatePagerAdapter(
    fragmentManager,
    BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
) {

    private var fragments: MutableList<BannerItemFragment> = mutableListOf()

    init {
        // initial empty fragments
        for (i in data.indices) {
            fragments.add(BannerItemFragment())
        }
    }

    override fun getItem(position: Int): Fragment {
        return BannerItemFragment.newInstance(data[position])
    }

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

    override fun instantiateItem(container: ViewGroup, position: Int): Any {
        val f = super.instantiateItem(container, position)
        fragments[position] = f as BannerItemFragment
        return f
    }

}

Upvotes: 0

Views: 1257

Answers (1)

Pasha Oleynik
Pasha Oleynik

Reputation: 515

Solved

The problem was that fragments want to be attached to the ViewPager before the ViewPager is attached to its parent. This question outlined here.

So, to solve this problem, I created custom ViewPager:

/**
 * Use this ViewPager when you need to place ViewPager inside RecyclerView.
 * [LazyViewPager] allows you to set [PagerAdapter] in lazy way. This prevents IllegalStateException
 * in case when the fragments want to be attached to the viewpager before the viewpager is
 * attached to its parent
 */
class LazyViewPager
@JvmOverloads
constructor(
    context: Context,
    attrs: AttributeSet? = null
) : ViewPager(context, attrs) {

    private var mPagerAdapter: PagerAdapter? = null

    override fun onAttachedToWindow() {
        super.onAttachedToWindow()
        if (mPagerAdapter != null) {
            super.setAdapter(mPagerAdapter)
        }
    }

    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
        super.setAdapter(null)
    }

    @Deprecated("Do not use this method to set adapter. Use setAdapterLazy() instead.")
    override fun setAdapter(adapter: PagerAdapter?) {}

    fun setAdapterLazy(adapter: PagerAdapter?) {
        mPagerAdapter = adapter
    }

}

And then, instead of using setAdapter() I use setAdapterLazy().

Also, it is important to reset adapter to null in onDetachedFromWindow().

Upvotes: 1

Related Questions