Android JetPackにActivity-ktx1.0.0とFragment-ktx1.1.0がStableリリースされました。
面白い追加機能の1つとして、コンストラクタにLayoutIDを渡すことができるようになりました。
class MainActivity : AppCompatActivity(R.layout.activity_main) {
}
こうすることでsetContentViewが不要になります。
lateinitがやめられない
ところでDataBindingやViewModelを初期化する時はActivityやFragmentの初期化と同時にはできないのでlateinitで後で初期化しているケースが多いと思います。
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()
でインスタンスを取得できます。
val binding: ActivityMainBinding = DataBindingUtil.bind(findViewById<ViewGroup>(android.R.id.content)[0])!!)
これをさらにlazyと組み合わせることでDataBindingインスタンスを遅延初期化できます。
private val binding: ActivityMainBinding by lazy { findViewById<ViewGroup>(android.R.id.content)[0])!! }
このlazyをさらにラップした拡張関数を作ります。
/**
* 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
}
})
}
}
}
}
結果👇
private val binding: ActivityMainBinding by dataBinding()
👏👏👏
上のコードでFragment用のdataBinding()も作りましたので、Fragmentの場合はonCreateView()が不要になります。
by viewModels()
この勢いでViewModelの初期化も遅延初期化していきましょう。と思ったらこのバージョンから公式で by viewModels()が提供されました。
private val viewModel: MainViewModel by viewModels()
これまでFragmentでViewModelProviders.of()で指定していたスコープはそれぞれ
viewModels()
activityViewModels()
の2つで指定するようになりました。
ViewModelFactoryを使っている場合は、
// MainFactoryがViewModelProvider.Factoryを継承している
private val viewModel: MainViewModel by viewModels { MainFactory() }
で初期化ができます。
結果
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で宣言できるようになりました!