Android JetPack Fragment-ktx 1.1.0 でlateinitを駆逐できる


Android JetPackにActivity-ktx1.0.0とFragment-ktx1.1.0がStableリリースされました。
面白い追加機能の1つとして、コンストラクタにLayoutIDを渡すことができるようになりました。

```kotlin
class MainActivity : AppCompatActivity(R.layout.activity_main) {
}
```

こうすることでsetContentViewが不要になります。

# lateinitがやめられない
ところでDataBindingやViewModelを初期化する時はActivityやFragmentの初期化と同時にはできないのでlateinitで後で初期化しているケースが多いと思います。

```kotlin
class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    private lateinit var viewModel: MainViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        viewModel = ViewModelProviders.of(this).get()
    }
}
```

全ActivityやFragmentでこの儀式を行われければならなかったのですが、今回のアップデートをうまく使うとlateinitを使わずに宣言できるようになります！

# by dataBinding()
AppCompatActivityにLayoutIDを渡した時はAppCompatActivity側でViewがセットされます。
これとDataBindingを組み合わせる時は `DataBindingUtil.bind()` でインスタンスを取得できます。

```kotlin
val binding: ActivityMainBinding = DataBindingUtil.bind(findViewById<ViewGroup>(android.R.id.content)[0])!!)
```

これをさらにlazyと組み合わせることでDataBindingインスタンスを遅延初期化できます。

```kotlin
private val binding: ActivityMainBinding by lazy { findViewById<ViewGroup>(android.R.id.content)[0])!! }
```

このlazyをさらにラップした拡張関数を作ります。

```DataBindingExtension.kt

/**
 * DataBindingを遅延初期化します。
 * このDelegateを使うにはAppCompatActivityのコンストラクタにレイアウトIDを指定ください。
 *
 * ex:
 * ```
 * class MainActivity : AppCompatActivity(R.layout.activity_main) {
 *   private val binding: ActivityMainBinding by dataBinding()
 * }
 * ```
 */
@MainThread
fun <V : ViewDataBinding> AppCompatActivity.dataBinding(): Lazy<V> {
    return object : Lazy<V> {
        private var holder: V? = null

        override val value: V
            get() = holder ?: run {
                val binding = DataBindingUtil.bind<V>(findViewById<ViewGroup>(android.R.id.content)[0])!!
                holder = binding
                binding
            }

        override fun isInitialized(): Boolean = holder != null
    }
}

/**
 * DataBindingを遅延初期化します。
 * このDelegateを使うにはFragmentのコンストラクタにレイアウトIDを指定ください。
 *
 * ex:
 * ```
 * class MainFragment : Fragment(R.layout.fragment_main) {
 *   private val binding: FragmentMainBinding by dataBinding()
 * }
 */
fun <V : ViewDataBinding> Fragment.dataBinding(): ReadOnlyProperty<Fragment, V> {
    return object : ReadOnlyProperty<Fragment, V> {
        private var holder: V? = null

        override fun getValue(thisRef: Fragment, property: KProperty<*>): V {
            return holder ?: DataBindingUtil.bind<V>(view!!)!!.also {
                holder = it
                // ライフサイクルの終了時にViewを破棄
                viewLifecycleOwner.lifecycle.addObserver(object : LifecycleObserver {
                    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
                    fun onDestroyView() {
                        viewLifecycleOwner.lifecycle.removeObserver(this)
                        holder = null
                    }
                })
            }
        }
    }
}
```

結果👇

```kotlin
private val binding: ActivityMainBinding by dataBinding()
```

👏👏👏

上のコードでFragment用のdataBinding()も作りましたので、Fragmentの場合はonCreateView()が不要になります。

# by viewModels()
この勢いでViewModelの初期化も遅延初期化していきましょう。と思ったらこのバージョンから公式で by viewModels()が提供されました。


```kotlin
private val viewModel: MainViewModel by viewModels()
```

これまでFragmentでViewModelProviders.of()で指定していたスコープはそれぞれ

viewModels()
activityViewModels()

の2つで指定するようになりました。

ViewModelFactoryを使っている場合は、

```kotlin
// MainFactoryがViewModelProvider.Factoryを継承している
private val viewModel: MainViewModel by viewModels { MainFactory() }
```

で初期化ができます。


# 結果
```kotlin
class MainActivity : AppCompatActivity(R.layout.activity_main) {
    private val binding: ActivityMainBinding by dataBinding()
    private val viewModel: MainViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }
}
```

bindingとviewModelからlateinitが消えてvalで宣言できるようになりました！