Razl-Dazl

eyecatch
Copyright © Google

FragmentStateAdapterのgetItemId()をオーバーライドするとActivity再生成時に落ちる

Posted at — 2023-05-14

FragmentStateAdapter + ViewPager2を利用して、カラムの並び替えが可能な画面を作成していたところ謎のクラッシュに遭遇


FATAL EXCEPTION: main
	Process: sns.asteroid.alpha, PID: 11739
	java.lang.IllegalStateException: Design assumption violated.
		at androidx.viewpager2.adapter.FragmentStateAdapter.placeFragmentInViewHolder(FragmentStateAdapter.java:287)
		at androidx.viewpager2.adapter.FragmentStateAdapter.onViewAttachedToWindow(FragmentStateAdapter.java:276)
		at androidx.viewpager2.adapter.FragmentStateAdapter.onViewAttachedToWindow(FragmentStateAdapter.java:67)
		at androidx.recyclerview.widget.RecyclerView.dispatchChildAttached(RecyclerView.java:8291)
		at androidx.recyclerview.widget.RecyclerView$5.addView(RecyclerView.java:924)

こういうあまり見掛けないタイプの例外が発生するとほんとにつらい

色々調べた結果、FragmentStateAdapterのgetItemId()をオーバーライドした事が原因と分かりました

カラムの並び替えの実装において、各Fragmentを判別する必要があったので、 getItemId()をオーバーライドして以下のような実装を行いました

override fun getItemId(position: Int): Long {  
    return columns[position].hashCode().toLong()  
}

安直にposition.toLong()とかを返さずに、オブジェクトのhashCode()を返すことによって各Fragmentを判別できるようにしました

・・・で、これだけでは駄目で、別でcontainsItem()もオーバーライドしてあげる必要があるっぽい

override fun containsItem(itemId: Long): Boolean {  
    return columns.find { it.hashCode().toLong() == itemId } != null  
}

こんな感じで、クラスの持ってる要素内に引数のitemIdと一致するものが存在するかどうか、確認するような処理を入れてあげることで落ちなくなりました・・・めでたしめでたし

ちなみに、このcontainsItem()を実装すると、ViewPager2内のFragmentが再生成されずに再利用されるようになります

REST API等でコンテンツを動的に取得している場合、今まではActivity再生成時に中身が破棄されてしまっていたのですがそれも同時に解消されました

getItemId()とcontainsItem()はセットで使いましょう~


参考

Google Issue Tracker - ViewPager2 Exception: java.lang.IllegalStateException: Design assumption violated

Author@zakuro

Mastodon: 396@vivaldi.net