Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

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をさらにラップした拡張関数を作ります。

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
                    }
                })
            }
        }
    }
}

結果👇

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で宣言できるようになりました!

kuluna
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away