Получение «java .lang.IllegalArgumentException: для идентификатора не найдено представление» во фрагменте ViewPager, который находится в RecyclerView - PullRequest
0 голосов
/ 07 августа 2020

Я получаю исключение:

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}

Моя иерархия выглядит так:

Иерархия экранов

Мой адаптер для 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)
    }

}

А я применяю его таким образом:

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

Далее мой SnapBannersCarouselViewHolder для обработки элементы с ViewPager внутри:

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
        }
    }

}

Мой адаптер RecyclerView 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()
                )
            }
        }
    }

}

И моя реализация fragmentManagerRetriever в ForYouFragment выглядит так:

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

Мой BannerItemFragment код:

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
    }

    // ...

}

В каждом месте, где мне нужно создавать фрагменты внутри других фрагментов, я использую childFragmentManager.

И когда я открываю ForYouFragment первый раз, нормально работает. Мой товар с ViewPager работает нормально. Но когда я заменяю фрагмент в контейнере Activity (добавляя в задний стек), находящийся на ForYouFragment, а затем возвращаюсь обратно (обратно на HomeFragment, потому что ForYouFragment внутри HomeFragment), я получаю ошибку.

Для замены фрагментов я использую этот метод внутри ForYouFragment:

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

и ActivityUtils код:

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

}

Пожалуйста, помогите мне понять, почему я получаю это исключение?

UPD

Адаптер для ViewPager внутри 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
    }

}

1 Ответ

0 голосов
/ 07 августа 2020

Решено

Проблема заключалась в том, что фрагменты нужно было прикрепить к ViewPager до того, как ViewPager будет прикреплен к его родительскому элементу. Этот вопрос обрисован в общих чертах здесь .

Итак, чтобы решить эту проблему, я создал пользовательский 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
    }

}

А затем вместо использования setAdapter() I используйте setAdapterLazy().

Кроме того, важно сбросить адаптер до нуля в onDetachedFromWindow().

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...