'ViewPager2 crash

I am using Advance Navigation Component with BottomNavigationView.

In one tab I have ViewPager2. When I clicked on the tab for the first time, it worked fine.

Although the second time, come on that tab application keep crashing. Below is the crash log. How can I fix this?

java.lang.IllegalArgumentException
at androidx.core.util.Preconditions.checkArgument(Preconditions.java:36)
at androidx.viewpager2.adapter.FragmentStateAdapter.onAttachedToRecyclerView(FragmentStateAdapter.java:140)
at androidx.recyclerview.widget.RecyclerView.setAdapterInternal(RecyclerView.java:1206)
at androidx.recyclerview.widget.RecyclerView.setAdapter(RecyclerView.java:1158)
at androidx.viewpager2.widget.ViewPager2.setAdapter(ViewPager2.java:460)
at com..ui.home.history.HistoryFragment.setupAdapter(HistoryFragment.kt:25)
at com.
.ui.home.history.HistoryFragment.viewSetup(HistoryFragment.kt:21)
at com.****.base.BaseFragment.onViewCreated(BaseFragment.kt:37)
at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:332)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1187)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1356)
at androidx.fragment.app.FragmentManager.moveFragmentToExpectedState(FragmentManager.java:1434)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1497)
at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2625)
at androidx.fragment.app.FragmentManager.dispatchActivityCreated(FragmentManager.java:2577)
at androidx.fragment.app.Fragment.performActivityCreated(Fragment.java:2722)
at androidx.fragment.app.FragmentStateManager.activityCreated(FragmentStateManager.java:346)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1188)
at androidx.fragment.app.FragmentManager.addAddedFragments(FragmentManager.java:2224)
at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:1997)
at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1953)
at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1849)
at androidx.fragment.app.FragmentManager$4.run(FragmentManager.java:413)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6940)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:537)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

Here is my code for the fragment:

private val adapter by lazy {
    HistoryPagerAdapter(this)
}

override fun viewSetup() {
    binding.vpBuySell.adapter = adapter
    TabLayoutMediator(
        binding.tabBuySell,
        binding.vpBuySell,
        TabLayoutMediator.TabConfigurationStrategy { tab: TabLayout.Tab, i: Int ->
           tab.text = when (i) {
                0 -> getString(R.string.buy)
                1 -> getString(R.string.sell)
                else -> getString(R.string.buy)
            }
        })
}

Here is the UI code:

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/blue_122e47">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?actionBarSize"
        android:background="@color/blue_06233e"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:titleTextColor="@color/white">

        <androidx.appcompat.widget.AppCompatTextView
            android:id="@+id/tvTitle"
            style="@style/ToolbarTitleTextStyle"
            android:text="@string/history" />

        <TextView
            android:id="@+id/btnExport"
            android:layout_width="wrap_content"
            android:layout_height="@dimen/_24sdp"
            android:layout_gravity="end"
            android:layout_marginEnd="@dimen/_8sdp"
            android:fontFamily="@font/helvetica_neue_medium"
            android:insetLeft="0dp"
            android:gravity="center"
            android:background="@drawable/shape_export_button"
            android:insetTop="0dp"
            android:insetRight="0dp"
            android:insetBottom="0dp"
            android:foreground="?selectableItemBackground"
            android:paddingBottom="@dimen/_2sdp"
            android:paddingStart="@dimen/_8sdp"
            android:paddingEnd="@dimen/_8sdp"
            android:text="@string/export"
            android:textAllCaps="false"
            android:textColor="@color/white"
            android:textSize="@dimen/_12ssp" />

    </androidx.appcompat.widget.Toolbar>


    <com.google.android.material.tabs.TabLayout
        android:id="@+id/tabBuySell"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/blue_122e47"
        app:tabIndicatorFullWidth="false"
        app:tabIndicatorGravity="bottom"
        app:tabTextAppearance="@style/HistoryTabTextStyle"
        app:tabTextColor="@color/gray_697b8b"
        app:tabSelectedTextColor="@color/white"
        app:tabIndicatorHeight="@dimen/_2sdp"
        app:tabIndicatorColor="@color/blue_47cfff"
        app:layout_constraintTop_toBottomOf="@id/toolbar"
        app:tabGravity="start"
        app:tabMode="scrollable" />

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/vpBuySell"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toBottomOf="@id/tabBuySell" />

</androidx.constraintlayout.widget.ConstraintLayout>

Here is my adapter code:

class HistoryPagerAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {

    override fun getItemCount(): Int {
        return 2
    }

    override fun createFragment(position: Int): Fragment {
        return when (position) {
            0 -> HistoryBuyFragment()
            1 -> HistorySellFragment()
            else -> HistoryBuyFragment()
        }
    }

}


Solution 1:[1]

The actual error is of the lazy initialisation of the adapter. I also don't know why that happened.

Solution 2:[2]

I had the same crash with my ViewPager2 implementation. In my case I was creating an adapter in onCreate and setting it to ViewPager in onViewCreated. Like this:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    adapter = FragmentAdapter(this)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    binding?.viewPager?.adapter = adapter
}

I've fixed the crash, combining adapter creation and setting into one method - onViewCreated. Like this:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    adapter = FragmentAdapter(this, month)
    binding?.viewPager?.adapter = adapter
}

Solution 3:[3]

I ran into this issue with BottomNavigationView when I switched between fragments back and forward. The problem was that I used Kotlin's by lazy {} definition of a value and I was setting the adapter in onViewCreated. For some reason, the fragment state could not be properly restored and it was not possible to set the adapter even after I used some "hacks" from this post, like modifying some lifecycle methods, etc.

That pager adapter is automatically restored during state restoration. This is why you might not want to persist your adapter, but always set a new in onViewCreated. Then just wait for automatic restoration to trigger and have your previous state.

Fix:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    // Anything you set here will be overriden during restoration
    viewBinding.pager.adapter = DemoCollectionPagerAdapter(this)

    var mediator = TabLayoutMediator(viewBinding.tabLayout, viewBinding.pager) { tab, position ->
        when (position){
            0 -> tab.text = getString(R.string.global_rooms).uppercase()
            1 -> tab.text = getString(R.string.global_devices).uppercase()
            2 -> tab.text = getString(R.string.global_presets).uppercase()
        }
    }.attach()
}

Solution 4:[4]

I had the same error when I got back to the fragment, and I solved the problem after I read Oleh's comment that said:

I assume that on a retained fragment lazy property cause memory leak because of holding a reference to the old view which leads to crash

So I set the adapter to null in onPause like this code, and then the problem is disappeared:

override fun onPause() {
    super.onPause()
    binding?.run {
        viewPager.adapter = null
    }
}

Solution 5:[5]

Try out this solution:

import androidx.viewpager2.adapter.FragmentStateAdapter

class HistoryPagerAdapter(activity: AppCompatActivity, private var itemCount: Int): FragmentStateAdapter(activity) {

    override fun getItemCount(): Int {
        return 2
    }

    override fun createFragment(position: Int): Fragment {
        return when (position) {
            0 -> HistoryBuyFragment()
            1 -> HistorySellFragment()
            else -> HistoryBuyFragment()
        }
    }

Code for fragment:

private val TAB_ITEMS_COUNT = 2

adapter = HistoryPagerAdapter((activity as AppCompatActivity), TAB_ITEMS_COUNT)
binding.vpBuySell.adapter = adapter

TabLayoutMediator(binding.tabBuySell, binding.vpBuySell) { tab, position ->
    when(position)
    {
        0 -> tab.text = getString(R.string.buy)
        1 -> tab.text = getString(R.string.sell)
    }
}.attach()

binding.tabBuySell.addOnTabSelectedListener(object: TabLayout.OnTabSelectedListener
{
    override fun onTabReselected(tabItem: TabLayout.Tab?) {}
    override fun onTabUnselected(tabItem: TabLayout.Tab?) {}
    override fun onTabSelected(tabItem: TabLayout.Tab) {
        binding.vpBuySell.currentItem = tabItem.position
    }
})

Solution 6:[6]

If you are using Navigation Component, the fragment's view gets recreated when you navigate back. For some reason in my case, the adapter wasn't detaching automatically when the fragment's view was destroyed. The error was: I kept an already attached adapter reference and was trying to add it to a new pager's view. Setting adapter = null inside onDestroyView() worked in my case.

override fun onDestroyView() {
    pager.adapter = null
    super.onDestroyView()
}

Solution 7:[7]

When you set the viewpager2 adapter, first check viewpager already adapter, else set the adapter.

For example,

if(viewPager.adapter == null) {
     viewPager.adapter = adapter
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Peter Mortensen
Solution 2 Peter Mortensen
Solution 3 Peter Mortensen
Solution 4 Peter Mortensen
Solution 5 Peter Mortensen
Solution 6 G_comp
Solution 7 Peter Mortensen