FragmentとViewのライフサイクルは違うよ。
ViewよりFragmentのほうが長生きだから、
FragmentにViewを保持してしまうと、
Androidのライフサイクル的に、Fragmentはまだ使うけどViewはもういらないって時に
いらなくなったViewを破棄できなくてメモリリークになっちゃうよ。
いろんなやり方で対策できるんだけど、
もしFragmentにViewを保持しなくてもいいなら保持しないようにしたほうがいいよ。
でもFragmentに保持しないと難しいってこともあると思うからいくつか対策を紹介するよ。
Nullable
Nullableで宣言してonDestroyView
でnullを代入する方法だよ。
結構よく見る方法で、シンプルで分かりやすいね。
class ExampleFragment: Fragment() {
private var _binding: FragmentExampleBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentExampleBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
この方法はAndroidデベロッパーの
フラグメントでビュー バインディングを使用するにも書いてあるよ。
例ではbindingをNonNullにしてるけど、Nullableのまま使ってもよさそうだね
この方法の注意点としては、
onDestroyView
でbindingにnullを入れるの忘れちゃいがちなとこだよ
AutoClearedValue
Google公式のサンプルで使用されているAutoClearedValueというのもあるよ。
これはonCreateView
で値を代入するとonDestroyView
で自動的にnullを代入してくれるよ。
だからonDestroyView
でnullを入れ忘れることがないよ。
この方法の注意点は、値がNullの時に取得するとエラーするところだよ
だからonDestroyView
のあとにbindingを使っちゃうとエラーになっちゃうよ
そんなことあんまりないと思うけど非同期処理とかでたまにやっちゃうね
bind関数
bindingを使うたびにviewをbindingにする方法だよ
これも結構シンプルかも
class ExampleFragment: Fragment(R.layout.fragment_example) {
private val binding get() = view?.let(FragmentExampleBinding::bind)
}
この方法の注意点はbindingを使うたびにbind関数が使われるから、
もしかしたらパフォーマンスに影響しちゃうところだよ
Delegateで保持する
AutoClearedValueを参考にして、FragmentでViewBindingを保持する物を作るよ
fun <T: ViewBinding>Fragment.viewBinding(
viewBinder: (View) -> T
): ReadOnlyProperty<Fragment, T?> = FragmentViewBindingDelegate { view?.let(viewBinder) }
private class FragmentViewBindingDelegate<T: ViewBinding>(
private val bindingProvider: () -> T?
): ReadOnlyProperty<Fragment, T?>, DefaultLifecycleObserver {
private var binding: T? = null
override fun getValue(thisRef: Fragment, property: KProperty<*>) =
binding ?: thisRef.viewLifecycleOwnerLiveData.value?.lifecycle?.let{
if(!it.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) return null
it.addObserver(this)
binding = bindingProvider()
binding
}
override fun onDestroy(owner: LifecycleOwner) {
super.onDestroy(owner)
binding = null
}
}
書いてあることを簡単に説明すると
最初にbindingを取得した時にonCreateView
以降だったらbindingがnullableで取得できるよ。
それでonDestroyView
になるとbindingにnullが代入されるよ。
Fragmentで使うにはこんな感じ
class ExampleFragment: Fragment(R.layout.fragment_example) {
private val binding by viewBinding(FragmentExampleBinding::bind)
}
僕はこれよく使ってるんだけど、
もしかしたら僕の知らないエラーとかあるかもしれないよ…
その他
そもそもxmlのレイアウトを使わずにJetPackComposeにしちゃえば全部解決だね!
といってもJetPackComposeも最近の技術だから
xmlでできたことが全部できるわけじゃないらしいよ
あとはこの問題を解決するライブラリとかもあるみたいだから調べてみてね!
自分のプロジェクトにあった解決方法を選択するのが重要だよ