Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

android MapView + ViewPager2 + DataBinding issue

I've recently came upon a really strange issue. I had a mapview in my layout which was inflated using databinding. After I added ViewPager2 to my layout I got this issue:

java.lang.IllegalArgumentException: Wrong state class, expecting View State but received class androidx.recyclerview.widget.RecyclerView$SavedState instead. This usually happens when two views of different type have the same id in the same hierarchy. This view's id is id/0x1. Make sure other views do not use the same id.

But it happens only when you first open first fragment and go to next one, then press back.

  • If I remove mapview from my layout everything works.
  • If I remove viewpager2 from my layout everything works.
  • If I remove databinding everything works.

It seems it has something to do with view state, but is there anything I could do to address this issue?

Here's a sample fragment code:

class TestFragment(override val layoutRes: Int = R.layout.fragment_test) : BaseFragment() {

lateinit var binding: FragmentTestBinding

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

override fun onResume() {
    super.onResume()
    binding.mapView.onResume()
}

override fun onPause() {
    super.onPause()
    binding.mapView.onPause()
}

private fun setupMap(savedInstanceState: Bundle?) {
    binding.mapView.onCreate(savedInstanceState)
    binding.mapView.onResume()
    binding.mapView.getMapAsync { }
}
}

Sample layout:

<?xml version="1.0" encoding="utf-8"?>
<layout>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.gms.maps.MapView
        android:id="@+id/mapView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/viewPager"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</FrameLayout>
</layout>

To reproduce I have to open another fragment and then press back

**EDIT: ** Seems like moving viewpager in front of mapview also helps, so databinding can still be used.

like image 677
SMGhost Avatar asked Aug 14 '19 12:08

SMGhost


Video Answer


2 Answers

The problem seems to be that MapView and ViewPager2 both inflate views and assign dynamically-generated ids to them.

ViewPager2 (version 1.0.0 atm) does that by calling ViewCompat.generateViewId(), I have no idea what MapView is doing, it's not using LayoutInflater to inflate its children views and I suspect it is not calling ViewCompat.generateViewId() either.

The cause of the issue is that the view hierarchy ends up containing 2 View with the same id (with value 1 for me), one is the RecyclerView inside the ViewPager2 (TIL that ViewPager2 is a wrapper of a RecyclerView, amazing), the other is a LinearLayout containing 2 ImageView inside the MapView.

I'm currently hacking this unfortunate situation by calling:

for(i in 0 until 1000) ViewCompat.generateViewId()

before setContentView(Int) in the Activity.

like image 54
lelloman Avatar answered Oct 09 '22 08:10

lelloman


The issue seems to be that the viewpager wraps a recyclerview to which it assigns an ID of 1, and the mapview assigns the same ID of 1. I resolved this with:

viewPager = binding.viewPager
    for (view in viewPager.children) {
        if (view is RecyclerView) {
            view.id = 99999999
            break
        }
    }
like image 1
LMBond Avatar answered Oct 09 '22 09:10

LMBond