こうだ
※Developer options
の Don't keep activities
を有効にしています
Androidとメモリー上のデータ
Androidはすぐメモリー上のデータが飛びます。
画面を回転したら飛ぶ、システムがアクティビティを破棄したら飛ぶ、システムがプロセスをキルったら飛ぶ。
これらはAndroidのライフサイクルのせいで、上記のタイミングでActivityやFragmentのインスタンスが都度作り直しになるためです。
そのために開発者はActivityやFragmentの onSaveInstanceState
関数で飛ばしたくないデータを保存し、 onCreate
関数などで savedInstanceState
からデータを復元する処理を書かなくてはいけませんでした。
ViewModelの登場
AndroidでViewModelクラスが登場して以降、開発者は様々な恩恵を受けられるようになりました。
その一つにメモリー上のデータが飛びにくくなったことがあります。
ViewModelは画面を回転させてもデータは飛びません! 自動で復元されます!
やったね!……?
ViewModelの問題
しかしViewModelといえど、システムがアクティビティを破棄した時、システムがプロセスをキルした時はやはり飛びます。No, やったね!。やってない。
結局、ViewModle内の状態も savedInstanceState
に都度保存・から都度復元しなければならないのでしょうか?
SavedStateHandle
いいえ、そんなことはありません。ViewModelには SavedStateHandle
という便利なものが用意されています。
SavedStateHandle
はViewModel内のプロパティの保存/復元を行うためのヘルパークラスです。
SavedStateHandle
の内部でプロパティの実体を保持しており、ライフサイクルを勝手に観測していい感じに保存・復元をしてくれます。
ここで例を見てみましょう。
保存/復元を行いたいのは EditText
とデータバインディングされた val userTextInput: MutableLiveData<String>
プロパティであるとします。
保存/復元を考えない場合、 userTextInput
は val userTextInput = MutableLiveData<String>("")
のように宣言されていたことでしょう。
これをSavedStateHandleを使って保存/復元するには以下のように書きます。
class MainFragment : Fragment() {
...
// SavedStateHandleを使っても使わなくてもViewModelの初期化方法は同じ。
private val viewModel: MainViewModel by viewModels()
...
}
class MainViewModel(
// ViewModelの第1引数にSavedStateHandleを指定します
handle: SavedStateHandle
) : ViewModel() {
// SavedStateHandleからキー"USER_TEXT_INPUT"にひも付くMutableLiveDataを探して引っ張ってくる。
// 存在しない場合は新しく作る(そしてSavedStateHandle内でキー"USER_TEXT_INPUT"に対してひも付け、抱えておく)。
// 初期値は空文字。
val userTextInput: MutableLiveData<String> = handle.getLiveData<String>("USER_TEXT_INPUT", "")
普通にMutableLiveDataのコンストラクタから宣言していたのを handle
の関数から取得するようにしただけです。
これだけでシステムがアクティビティを破棄する時、システムがプロセスをキルする時はその時のuserTextInputの値を勝手に保存してくれますし、復帰する時は勝手に復元してくれます。
val userTextInput = MutableLiveData<String>("")
(Simple LiveData)方式と val userTextInput: MutableLiveData<String> = handle.getLiveData<String>("USER_TEXT_INPUT", "")
(SavedStateHandle#getLiveData)方式の振る舞いを比較したのが冒頭の動画になります。
EditText
に文字を入力したあとホームボタンを押してランチャー画面を表示させ、再度アプリアイコンをタップしてアプリを起動させたところ、Simple LiveDataの方は入力したデータが飛んでしまっていますが、SavedStateHandle#getLiveDataの方はきちんとデータが残っています。これは保存/復元が行われたということです。今度こそやったね!
なお、handle.getLiveData<T>(...)
の T
になれるのは、
- 基本型
- Boolean
- Short
- Int
- Long
- Float
- Double
- String
- Byte
- Char
- CharSequence
- Parcelable
- Array<基本型 または Parcelable>
- Serializable
- Binder
- Bundle
- Size
- SizeF
- ArrayList<`T`になれるどれか>
- SparseArray<`T`になれるどれか>
のみです。
T
がエンティティクラスの場合はParcelableを継承し実装するのがよいでしょう。
ちなみに
SavedStateHandle
は get
という関数も持っています。
これは MutableLiveData<T>
ではなく生の値(T
)を返す関数です。
使う機会は多くないかもしれませんが覚えておくといいでしょう。
おわりに
Androidのライフサイクルに泣きたくなるのをこらえて頑張りましょう!
追記
続きのような立ち位置の記事を書きました。合わせてどうぞ。