'How to fix 'Design assumption violated' error in ViewPager2?
I'm using ViewPager2, Kotlin and AndroidX. When the adapter is not at index 0 and I change the adapter list and set current item to index 0 the exception is thrown.
java.lang.IllegalStateException: Design assumption violated.
at androidx.viewpager2.widget.ViewPager2.updateCurrentItem(ViewPager2.java:538)
at androidx.viewpager2.widget.ViewPager2$4.onAnimationsFinished(ViewPager2.java:518)
at androidx.recyclerview.widget.RecyclerView$ItemAnimator.isRunning(RecyclerView.java:13244)
at androidx.viewpager2.widget.ViewPager2.onLayout(ViewPager2.java:515)
at android.view.View.layout(View.java:15596)
In line 537 of ViewPager2 there is an if which causes the exception:
// Extra checks verifying assumptions
// TODO: remove after testing
View snapView1 = mPagerSnapHelper.findSnapView(mLayoutManager);
View snapView2 = mLayoutManager.findViewByPosition(snapPosition);
if (snapView1 != snapView2) {
throw new IllegalStateException("Design assumption violated.");
}
This is how I'm updating the adapter list:
adapter.list = newList
adapter.notifyDataSetChanged()
viewpager.setCurrentItem(0, true)
It happens only when smoothScroll is set to true
What am I missing?
Edit :
I'm using androidx.viewpager2.widget.ViewPager2 available in com.google.android.material:material:1.1.0-alpha08
Solution 1:[1]
I'm using androidx.viewpager2:viewpager2:1.0.0
For whoever still stuck at this, and need to implement getItemId() containsItem(). Please chek your getItemId() and containsItem() implementation. My problem is when I serve 10 item with possibility several item have same value, this error occurs.
Media have several data with same value.
private var mediaId = media.map { it.hashCode().toLong() }
override fun getItemId(position: Int): Long = mediaId[position]
override fun containsItem(itemId: Long): Boolean = mediaId.contains(itemId)
while you updating the data or even simply swiping, the viewpager is confuse because you implement getItemId() with non unique id. The solution is just make sure your data is unique or have their own id. *My media datas dont have id.
If you open the implementation of
getItemId()notes this part :
- @return stable item id {@link RecyclerView.Adapter#hasStableIds()}
you need to have unique id for each of item.
Solution 2:[2]
I had the same problem but the bug seems to be fixed (at least for me) in the current version androidx.viewpager2:viewpager2:1.0.0-beta01.
See more in ViewPager2 1.0.0-beta01 Release Notes
Solution 3:[3]
Here is the solution for a straightforward use case
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
class ViewPager2Adapter(
val dataList: List<Item> = listOf(),
fragmentActivity: FragmentActivity
) :
FragmentStateAdapter(fragmentActivity) {
val pageIds= dataList.map { it.hashCode().toLong() }.toMutableList()
override fun createFragment(position: Int): Fragment {
return MyFragment.newInstance(dataList[position])
}
override fun getItemCount(): Int {
return dataList.size
}
override fun getItemId(position: Int): Long {
return pageIds[position]
}
override fun containsItem(itemId: Long): Boolean {
return pageIds.contains(itemId)
}
}
Then I encountered a specific use case when I was replacing my object at the position with a new one then I got the exception.
Solution: Now in case you are making data changes to the adapter i.e. with replacing an object in dataList with a new one, then make sure to replace id in pageIds of the old object with the new one.
(viewPager2.adapter as? ViewPager2Adapter)?.apply {
pageIds[oldIndex] = (NewItemObject).hashCode().toLong()
notifyItemChanged(oldIndex)
}
Solution 4:[4]
My issue was that I was customizing containsItem() to have some conditions under which I remove Fragments from the ViewPager, and then return false;
I encounter Design assumption violated after rotating the phone (and then swipe the ViewPager2).
I solved that by just returning the super class method: return super.containsItem(itemId);
@Override
public boolean containsItem(long itemId) {
// Your code
return super.containsItem(itemId);
}
UPDATE:
The containsItem method does not have the "CallSuper" annotation and is therefore not required to be called
This is right, no need to call the super; and I didn't call it explicitly before returning a boolean; just returning it solving this issue
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 | |
| Solution 2 | xsveda |
| Solution 3 | Muhammad Maqsood |
| Solution 4 |
