たった1行で ViewBinding を使う方法 👏

ViewBinding 使っていますか? とても便利ですが、ActivityとFragmentでインスタンスの取り扱いに違いがあり少々クセがあります。特にFragmentの場合が面倒です。

## ViewBinding をActivity で使う場合

Activity で使う場合は `lazy { }` を使うと簡単です。インスタンスを破棄することは考えなくても大丈夫です。1行で書くことができます。

```kotlin
private val binding by lazy { MyActivityBinding.inflate(layoutInflator, container, false)
```

## ViewBinding を Fragment で使う場合

問題は ViewBinding を Fragment で使う場合で、インスタンスを破棄することを考える必要があるのでコードがやや冗長になります。ボイラープレートコードを減らしてもっと短く方法はないでしょうか。

```kotlin
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](https://zhuinden.medium.com/simple-one-liner-viewbinding-in-fragments-and-activities-with-kotlin-961430c6c07c) で紹介されている方法を使います。 Delegated Property を通じて ViewBinding のインスタンスを取得するのがミソです。

## 導入手順

1. jitpack でライブラリを入れる

```kotlin
implementation("com.github.Zhuinden:fragmentviewbindingdelegate-kt:1.0.0")
```

2.  `by viewBinding { }` を使って書き換える

```kotlin
class MyFragment : Fragment(R.layout.my_fragmet) {  // ← layoutファイルを指定する
   private val binding by viewBinding(MyFragmentBinding::bind) // ← ::bind を使う。(::inflateではない)
}
```

たったこれだけです！素直に書く場合と比べて 10行 くらい短くなるし、インスタンス破棄を忘れることもなくなって一石二鳥です ✨

## 補足: Delegated Property の実装の解説

```kotlin
/*
 * 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)
```

## 参考リンク

- [https://zhuinden.medium.com/simple-one-liner-viewbinding-in-fragments-and-activities-with-kotlin-961430c6c07c](https://zhuinden.medium.com/simple-one-liner-viewbinding-in-fragments-and-activities-with-kotlin-961430c6c07c)
- [https://github.com/Zhuinden/fragmentviewbindingdelegate-kt](https://github.com/Zhuinden/fragmentviewbindingdelegate-kt)