ViewBinding 使っていますか? とても便利ですが、ActivityとFragmentでインスタンスの取り扱いに違いがあり少々クセがあります。特にFragmentの場合が面倒です。
ViewBinding をActivity で使う場合
Activity で使う場合は lazy { } を使うと簡単です。インスタンスを破棄することは考えなくても大丈夫です。1行で書くことができます。
private val binding by lazy { MyActivityBinding.inflate(layoutInflator, container, false)
ViewBinding を Fragment で使う場合
問題は ViewBinding を Fragment で使う場合で、インスタンスを破棄することを考える必要があるのでコードがやや冗長になります。ボイラープレートコードを減らしてもっと短く方法はないでしょうか。
private var _binding: MyFragmentBinding? = null
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
_binding = MyFragmentBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
Fragment でも 1行で ViewBinding を使う方法
Simple one-liner ViewBinding in Fragments and Activities with Kotlin で紹介されている方法を使います。 Delegated Property を通じて ViewBinding のインスタンスを取得するのがミソです。
導入手順
- jitpack でライブラリを入れる
implementation("com.github.Zhuinden:fragmentviewbindingdelegate-kt:1.0.0")
-
by viewBinding { }を使って書き換える
class MyFragment : Fragment(R.layout.my_fragmet) { // ← layoutファイルを指定する
private val binding by viewBinding(MyFragmentBinding::bind) // ← ::bind を使う。(::inflateではない)
}
たったこれだけです!素直に書く場合と比べて 10行 くらい短くなるし、インスタンス破棄を忘れることもなくなって一石二鳥です ✨
補足: Delegated Property の実装の解説
/*
* Copyright 2021 Gabor Varadi
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zhuinden.fragmentviewbindingdelegatekt
import android.view.View
import androidx.fragment.app.Fragment
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.Observer
import androidx.viewbinding.ViewBinding
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
class FragmentViewBindingDelegate<T : ViewBinding>(
val fragment: Fragment,
val viewBindingFactory: (View) -> T
) : ReadOnlyProperty<Fragment, T> {
private var binding: T? = null
init {
fragment.lifecycle.addObserver(object : DefaultLifecycleObserver {
val viewLifecycleOwnerLiveDataObserver =
Observer<LifecycleOwner?> {
val viewLifecycleOwner = it ?: return@Observer
viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onDestroy(owner: LifecycleOwner) {
binding = null // FragmentのonDestroyが呼ばれるタイミングで自動的に ViewBinding のインスタンスを破棄してくれる
}
})
}
override fun onCreate(owner: LifecycleOwner) {
fragment.viewLifecycleOwnerLiveData.observeForever(viewLifecycleOwnerLiveDataObserver)
}
override fun onDestroy(owner: LifecycleOwner) {
fragment.viewLifecycleOwnerLiveData.removeObserver(viewLifecycleOwnerLiveDataObserver)
}
})
}
override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
val binding = binding // シャドーイング
if (binding != null) {
return binding
}
val lifecycle = fragment.viewLifecycleOwner.lifecycle
if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) {
throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed.")
}
// View と bind することで ViewBinding のインスタンスを作る
return viewBindingFactory(thisRef.requireView()).also {
this.binding = it // ViewBinding のインスタンスを変数に保持する
}
}
}
fun <T : ViewBinding> Fragment.viewBinding(viewBindingFactory: (View) -> T) =
FragmentViewBindingDelegate(this, viewBindingFactory)