32
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-09-06

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

32
24
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?