Android
Kotlin

evernote/android-state と Kotlin Android ExtensionsのParcelize を組み合わせて自作オブジェクトのインスタンスをラクに保持する

はじめに

Kotlinの時代が到来し、ViewModel使っていこうな!という機運高い状況のなかでもParcelableを使っていく必要のある皆さん、こんばんは!

以下の記事の続き、というか今こうしてますよ、という話です。

下準備

evernote/android-stateKotlin Android ExtensionsのParcelize(以下Parcelizeとします) を自分のプロジェクトで使えるようにします。
(rootのbuild.gradleにはKotlin Gradle Pluginがセットされている状態で説明します)

app/build.gradle
apply plugin: 'com.android.application'

apply plugin: 'kotlin-android-extensions' // ← Parcelizeの為
apply plugin: 'kotlin-kapt' // ← android-stateのkaptの為

...

dependencies {
    implementation 'com.evernote:android-state:1.3.1' // ← android-state 本体
    kapt 'com.evernote:android-state-processor:1.3.1' // ← android-state 本体

...
}

androidExtensions {
    experimental = true // ← Parcelizeは現時点でexperimentalのため
    features = ["parcelize"] // ← View Binding(ノットDataBinding)の機能を使わない場合はこうする
}

モデル側の記述(Parcelize)

Parcelizeで扱いたいモデルには、@Parcelizeアノテーションを付与します。
Parcelizeで扱うことの出来る型はここにまとめられています

@Parcelize
data class Album(
    var title: String = "",
    var artistName: String = "",
    var songs: List<Song> = emptyList(),
    var date: Date = Date()
) : Parcelable

@Parcelize
data class Song(
    var title: String = "",
    var genre: String = ""
) : Parcelable

AlbumクラスはSongクラスをListで持っていますが、Songクラスにも@Parcelizeを付与することでList<Song>をParcelizeで管理できます。
(理屈としては、SongがParcelableであること、かつ、Supprted Typesより"Arrays of all supported types"であるためです。)

うっかりParcelableにし忘れても、Syntax checkがきっちり機能する為、忘れることはほぼないはずです。
(エラー内容にType is not directly supported by 'Parcelize'. Annotate the parameter type with '@RawValue' if you want it to be serialized using 'writeValue()' と出ます。「@RawValueとは?」という気持ちになりましたが、sakuna63さんのQiita記事に書いてありました。)

Applicationクラス(あるいはActivity/Fragment)への記述(android-state)

通常、InstanceStateを保持するためには、Activity#onSaveInstanceState(Bundle?), Activity#onRestoreInstanceState(Bundle?)を継承して実装するのが定石なのですが、android-stateでは以下の記述をオススメしています。

public class App extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        StateSaver.setEnabledForAllActivitiesAndSupportFragments(this, true);
    }
}

これは内部的にApplication#registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks)を使っています。Activityのライフサイクルをコールバックとして受け取ることが出来るのですが、うまく使っているなと思いました。

もし、何らかの事情でActivity/Fragmentに直接実装する方法がお望みなら、以下のように書きます。

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        StateSaver.restoreInstanceState(this, savedInstanceState);
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        StateSaver.saveInstanceState(this, outState);
    }
}

保持したいメンバ変数への記述

非常にお手軽です。@Stateをつけるだけです(nullも可)。

class MainActivity: AppCompatActivity() {
    @State
    var album = Album()
}

おわりに(過去記事と見比べて)

以前書いた記事と見比べると、わざわざ書かなければ「ならない」部分が劇的に減っており、より馴染んだ印象です。
一方で、AndroidではLiveDataやViewModelという新しい概念が出てきており、わざわざInstanceStateに保持しなくてもよいケースが増えているように思います。
適材適所で、うまく使っていくことが求められているのかな、と思います。