SavedStateを手軽に扱う機能を追加した Lich ViewModel 2.0 をリリースしました

Android向けライブラリコレクション「Lich」について

こんにちは。コミュニケーションアプリ「LINE」の Android クライアントチームの大石です。
今回は、私達のチームがOSSとして公開しているAndroid向けライブラリコレクション「Lich」について、新機能とともに紹介しようと思います。

「Lich」は、Androidアプリの開発に役立つ軽量な汎用ライブラリを集めたもので、以下のGitHubリポジトリにて公開されています。

https://github.com/line/lich

このうち Lich Component と Lich Lifecycle については、先日の LINE DEVELOPER DAY 2019 の講演内で紹介いたしました。ログミーTechさんのほうに書き起こし記事がありますので、ぜひこちらも参照してみてください。
前編: Kotlin Coroutinesの導入にLich Lifecycleを利用する話
後編: マルチモジュール化にLich Componentを利用する話

今回は、先の講演では触れなかった Lich ViewModel ライブラリについて紹介します。

Lich ViewModel 2.0 で SavedState に簡単アクセス

Lich ViewModel は MVVM パターンの ViewModel の利用やユニットテストを容易にするためのライブラリです。今回リリースされたバージョン 2.0 では SavedState を簡単に扱うための機能を新たに追加しました。

まず、Lich ViewModel の基本的な使い方から説明します。

class FooViewModel(private val context: Context, savedState: SavedState) : AbstractViewModel() {
 
    // snip...
 
    companion object : ViewModelFactory<FooViewModel>() {
        override fun createViewModel(context: Context, savedState: SavedState): FooViewModel =
            FooViewModel(context, savedState)
    }
}

Lich ViewModel のクラスは AbstractViewModel クラスを継承して実装します。そして、ViewModelFactory クラスを継承した companion object も同時に実装します。このように実装された ViewModel は、Fragment や Activity の delegated properties として以下のように使用することができます。

class FooFragment : Fragment() {
 
    private val fooViewModel by viewModel(FooViewModel)
 
    // snip...
}

このように Lich ViewModel では、インスタンスの生成処理を ViewModelFactory 内に隠蔽しつつ、Fragment/Activityのプロパティとして ViewModel をバインドさせることができます。これにより、ViewModel インスタンス生成をカスタマイズしたり、ユニットテストで ViewModel をモックしたりすることが容易になります。

続いて、新機能である SavedState の利用方法について説明します。

@GenerateArgs
class FooViewModel(private val context: Context, savedState: SavedState) : AbstractViewModel() {
 
    @Argument
    private val userName: String by savedState.required()
 
    @Argument(isOptional = true)
    private var amount: Int by savedState.initial(0)
 
    @Argument(isOptional = true)
    private val message: MutableLiveData<String> by savedState.liveData()
 
    // snip...
}

上のコードのように “by savedState.required()” などとするだけで SavedState へ簡単にアクセスすることができます。この例ですと、 userName, amount, message のそれぞれのプロパティに対してセットされた値が saved instance state として自動的に保持されるわけです

また、上のコードには @GenerateArgs, @Argument というアノテーションが付いていますが、このアノテーションにより以下のようなコードが自動生成されます。

class FooViewModelArgs(
    val userName: String,
    val amount: Int? = null,
    val message: String? = null
) : ViewModelArgs {
    override fun toBundle(): Bundle = Bundle().apply {
        putString("userName", userName)
        if (amount != null) putInt("amount", amount)
        if (message != null) putString("message", message)
    }
}

この自動生成されたクラスを使って SavedState の初期値を与えることができます。この初期値を指定する方法は2種類あります。1つめは、以下のように “by viewModel(…)” の引数として直接的に指定する方法です。

class FooFragment : Fragment() {
 
    private val fooViewModel by viewModel(FooViewModel) {
        FooViewModelArgs(userName = "foo", amount = 100, message = "Hello.").toBundle()
    }
 
    // snip...
}

もう1つは、Fragment の arguments や Intent の extras として指定する方法です。

class FooFragment : Fragment() {
 
    private val fooViewModel by viewModel(FooViewModel)
 
    // snip...
}
 
val fooFragment = FooFragment().also {
    // This `FooViewModelArgs` is used to initialize the `SavedState` of `FooFragment.fooViewModel`.
    it.setViewModelArgs(FooViewModelArgs(userName = "foo", amount = 100, message = "Hello."))
}

後者の方法は Fragment や Activity に渡された引数をそのまま ViewModel のプロパティとして扱うことができるので、boilerplate的なコードを大幅に削減することができます。

以上のように、Lich ViewModel 2.0 では SavedState に対する簡単で型安全なアクセスができるようになりました。

Lich ViewModel を Dagger と組み合わせる

Androidアプリの開発において既に Dagger を導入している場合、Jetpack の ViewModel ライブラリが Dagger と連携しづらくて不満に思っている人は多いのではないでしょうか。

Lich ライブラリでは、ViewModel インスタンスの生成処理を Dagger に移譲することも容易に行なえます。具体的なやり方については、以下のサンプルプロジェクトを参照してみてください。https://github.com/line/lich/tree/master/dagger_sample_app
Lich ライブラリやこれらのサンプルコードが皆さんのAndroidアプリ開発に役立てば幸いです。

Related Post