[Android Architecture Components] Lifecycle, LiveData and ViewModel 詳解
Jumpei Matsuda
こんにちは。daruma です。
Google I/O 2017 で Android Architecture Components をテーマとした以下の3セッションが行われました。
そのセッションでは Google が 公式に Androidアプリにおけるアーキテクチャの指針と支援ライブラリを提供する ことを発表しました。すでにガイド及びライブラリは公開されており、こちらから試すことが可能です。ただし まだ正式版ではなく、1.0.0-alpha1 となっています。プロダクション導入については完全に自己責任です。
Android Architecture Components とは
『頑強で、テストが可能かつ容易で、保守のしやすいアプリの設計』を実現するための思想とそのコンポーネントの定義、さらにそれを支援するライブラリ群 を指します。Javadoc はこちら。
そのモチベーションとして、既存の問題点が挙げられています。
- Activity/Fragment の生存・有効期間の管理が容易でない
- 画面回転時に Activity が再生成されるため、Activity の参照を保持してしまうとメモリリークする
- 画面回転後に Activity が再生成されてもデータを引き継ぎたいが難しい
- SQLite、ContentProvider、Realm など、現状永続化の選択肢が多い
- etc...
これらの目的のために Architecture Components は以下の考えと方針を設計の選択肢として提供します。
- Activity/Fragment の Lifecycle を観測可能に
- Activity/Fragment の生存・有効期間にのみアクティブになる観測者
- 画面回転時のActivityの再生成を跨ぐデータ保持
- 堅牢な SQLite の操作系
- テストを容易にする設計思想
ここで注意したいことは 1つの問題を解決するライブラリや単一の設計を強制するものではなく、各アプローチは互いに独立しているため、それぞれを独立して利用することが可能な点 です。したがって、それぞれ独立したアプローチを分けて理解し、一部のアプローチを適用することで既存アプリでも十分に恩恵を受けることができます1)まだ正式版でないことなどから、ただちに適用すべきかどうかはまた別の問題です。。各アプローチを実装含め、複数回に分けて追っていきたいと思います。今回は以下の3点について、掘っていきます。
- Activity/Fragment の Lifecycle を観測可能に
- Activity/Fragment の生存・有効期間にのみアクティブになる観測者
- 画面回転時のActivityの再生成を跨ぐデータ保持
Architecture Components は Reactive Programming の考え方に則っている部分がいくつか存在しています。RxJava の知識を求められることはないものの、基本的な Observer パターン及び Pub/Sub を理解する必要があります。ただしその多くは Lifecycle の観測系周りに集中しています。
参考: Solving the Lifecycle Problem より 19:24 地点
準備
Maven レポジトリの追加
repositories {
maven { url 'https://maven.google.com' }
}
Lifecycle and LifecycleObserver - Lifecycle を観測する
概要
Lifecycle
を観測可能に- APT を用いることで Lifecycle-aware な記述が容易に
{
compiles: [ "android.arch.lifecycle:runtime" ] ,
annotationProcessors: [ "android.arch.lifecycle:compiler" ],
dependsOn: []
}
Lifecycle を持つもの(LifecycleOwner
)とそれを観測するもの(LifecycleObserver
)という観測系を作ります。それにより Lifecycle を簡単に扱えるようにします。また Activity/Fragment に限らず、Application2)正確には
ProcessLifecycleOwner は Application そのものではなく、最初と最後に起動した Activity を以って Lifecycle とします。つまり Application process そのものの Lifecycle ではありません。 や Service3)基底クラスはLifecycleService、また独自の Lifecycle にも対応しています。
Architecture Components における Lifecycle の定義は以下になります。
public abstract class Lifecycle {
public abstract State getCurrentState();
public abstract void addObserver(LifecycleObserver observer);
public abstract void removeObserver(LifecycleObserver observer);
public enum State {
/* INITIALIZED はまだ CREATED が呼ばれていない段階です */
DESTROYED, INITIALIZED, CREATED, STARTED, RESUMED;
boolean isAtLeast(State state) {
return this.compareTo(state) >= 0;
}
}
public enum Event {
/* ON_ANY は全てのイベントとマッチします */
ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY, ON_ANY;
}
}
非常にシンプルに観測可能なオブジェクトとして定義されています。実際に LifecycleObserver や Lifecycle#Event を受け取ったときの振る舞いは LifecycleRegistry
にあります。開発者側が実際に使うときは LifecycleRegistryOwner を Activity/Fragment に実装し、LifecycleRegistry を返すようにします4)LifecycleRegistryOwner はサポートライブラリ統合後に削除される予定です。 またすでに実装された基底クラスも提供されています。
class MainActivity extends LifecycleActivity {}
// or
class MainActivity extends FragmentActivity implements LifecycleRegistryOwner {
private final LifecycleRegistry mRegistry = new LifecycleRegistry(this);
@Override public LifecycleRegistry getLifecycle() {
return mRegistry;
}
}
LifecycleObserver 自体は何もメソッドを持っていません。開発者は LifecycleObserver を実装したクラスで @LifecycleEvent
と Lifecycle#Event
を用いてその振る舞いを定義します。APT は開発者が独自定義した LifecycleObserver と Lifecycle#Event に対する定義を読み取り、GenericLifecycleObserver
または ReflectiveGenericLifecycleObserver (内部クラス用)
にその振る舞いを落とし込みます。GenericLifecycleObserver/ReflectiveGenericLifecycleObserver は各 Lifecycle#State
の変化などを、独自定義した LifecycleObserver へ dispatch します。これにより、開発者は簡単な記述で Lifecycle を扱うことが可能です。
class MyActivity extends LifecycleActivity {
// private でも可。
static class MyLifecycleObserver implements LifecycleObserver {
private static final String TAG = MyLifecycleObserver.class.getSimpleName();
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
void onResume() {
Log.d(TAG, "onResume");
}
}
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate();
// MyLifecycleObserver#onResume() は MyActivity が resume 状態になったときのみ呼ばれる
getLifecycle().addObserver(new MyLifecycleObserver());
}
}
おまけ : APT により ${Activity_name}_${LifecycleObserver_name}_LifecycleAdapter
というクラスが作成されます。
LiveData - Activity/Fragment の生存・有効期間にのみアクティブになる観測者
概要
- Lifecycle-aware なデータ管理
- エラー状態はそのままでは扱えない
{
compiles: [ "android.arch.lifecycle:extensions" ] ,
annotationProcessors: [],
dependsOn: [{
compiles: [ "android.arch.lifecycle:runtime" ] ,
annotationProcessors: [ "android.arch.lifecycle:compiler" ]
}]
}
Lifecycle を観測して振る舞うクラスの1つとして LiveData
が提供されます5)LifecycleObserver を直接継承しているわけではありません。。これは Lifecycle
に応じてデータの変更通知管理を自身が行ない、Lifecycle
的に安全な場合のみデータを提供するような責務を持ちます。また提供されたデータを観測するクラスは Observer
です。LiveDate は最終的に自動で Observer をリリースしまう。したがって、LiveData を用いてデータを管理することにより、開発者は Lifecycle を過度に意識せずに UI のアップデートを行うことが可能 になります。
LiveData
の変更通知が行える状態を Active
、行えない状態を Inactive
と呼びます。Active の定義は Lifecycle が State#STARTED または State#RESUMED であること です。また Active/Inactive に関わらず最新の値はキャッシュされます。Observer への通知は次の状況下で行われます。
前提 : 通知したい Observer に対して、まだ最新の値を通知していない
- Active な状態で LiveData の値が更新された場合
- Inactive から Active になった場合
要するに重複通知は行われず、安全な状態でしか通知は来ず、Observer を新たに追加した場合はその時点での最新の値を受け取れることを意味します。値の変更には LiveData#setValue(..)
と LiveData#postValue(..)
を用います。Observer への変更通知の際だけメインスレッドに命令を発行する仕組みではないため、非同期時の値更新インターフェースとして LiveData#postValue(..) が存在しています6)公式のコメントによると // TODO: Thread checks are too strict right now, we may consider automatically moving them to main thread. とあるので、変わるかもしれません。。
最新の値をキャッシュしているという面では EventBus の sticky event、Rx の Behavior Subject の機構に似ていますが、1度送信した値は再度通知しないという制御が異なります。EventBus や RxJava だと自分で最新値の通知管理を行なう必要があり、複雑になりやすい点をうまく吸収してくれています。
LiveData からデータを得る場合、LiveData#observe(LifecycleOwner, Observer)
を呼び出します。またObserver#onChanged()
はメインスレッドで呼び出されます。開発者は LiveData から送られてくるデータを加工する記述に注力すればよく、 Lifecycle を意識せずに UI をアップデートすることができます。しかし 受け取ったデータをいつ処理するかは LiveData の責務ではない ため、 ON_RESUME まで Observer を動かしたくない場合などには工夫が必要です。
この場合には onResume()
で observe し、onStop()
で removeObserver を行なうか、独自の Lifecycle を定義する必要があります。例えば onResume() のときのみ Active な LifecycleOwner を作ることも可能です。以下が実装例です。
class MyLifecycleOwner implements LifecycleOwner {
private final LifecycleRegistry registry;
MyLifecycleOwner(@NonNull LifecycleOwner owner) {
registry = new LifecycleRegistry(owner) {
@Override public void handleLifecycleEvent(Event event) {
if (event != Event.ON_START) {
if (event == Event.ON_RESUME) {
super.handleLifecycleEvent(Event.ON_START);
}
super.handleLifecycleEvent(event);
} else {
// ここで Event.ON_DESTROYED を渡してはいけません。
// LiveData#observe(...) は Destroy 状態の場合に Observer の追加を無視します。
}
}
};
}
@Override public Lifecycle getLifecycle() {
return registry;
}
}
class MyActivity extends LifecycleActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyLifecycleOwner owner = new MyLifecycleOwner(MyActivity.this);
liveData.observe(owner, (x) -> /* ON_RESUME のときのみ呼ばれます。 */);
}
}
さて、実際に利用する際の LiveData の具象型は MutableLiveData
が一番多くなるかと思います7)状態変化が発生することを明示的に表すためのクラスとして分離し、値更新メソッドの可視性を上げているだけであり、LiveData が immutable であるという意味ではありません。。値を更新するための LiveData#setValue(..) と LiveData#postValue(..) に外からアクセスできます。
もう1つの具象型として MediatorLiveData
が存在します。これは複数の LiveData を観測する Observer のように振る舞い、他の LiveData の変更を観測して自身を変更するような LiveData として用いられることを想定しています。要は LiveData の transformation を実現するために用いられるクラス です。実際に利用する際は Utility や Helper を経由することが多くなると思います。公式からは Transformations
というヘルパークラスが提供されています。とはいえ現在は map
と switchMap
しかありません。
Transformations#map
Transformations#map
はシンプルに引数の transformer を適用します。
UserRepository userRepository = ...;
LiveData<User> userLiveData = userRepository.findById(id);
LiveData<Integer> myCourseCountLiveData =
Transformations.map(userLiveData, (user) -> user.getMyCourseCount());
userLiveData
が変化すれば常に
が呼ばれ、myCourseCountLiveData
も変化します。
Transformations#switchMap
Transformations#switchMap
の transformer は LiveData を返します。
// 1. myCourseCountLiveData が変化すると switchMap へ
// 2. count が1より大きい場合は courseRepository からマイコースを取得
// 3. count が0未満の場合は recommendedCoursesLiveData を返す。
// 3-1. 前回 recommendedCoursesLiveData が通知されている場合、switchMap 内の処理により Observer の重複は発生しない。
class MyViewModel {
private final LiveData<List<Course>> coursesLiveData;
MyViewModel(UserRepository userRepository, CourseRepository courseRepository) {
LiveData<List<Course>> recommendedCoursesLiveData = courseRepository.findAllByRecommanded();
LiveData<Integer> myCourseCountLiveData = ...;
this.coursesLiveData = Transformations.switchMap(myCourseCountLiveData, (count) -> {
if (count > 0) {
return courseRepository.findAllByMine();
} else {
return recommendedCoursesLiveData;
}
});
}
LiveData<List<Course>> getCoursesLiveData() {
coursesLiveData;
}
}
エラー状態の扱い
LiveData はそのままではエラー状態を扱えません。つまり成功時の値しか取ってくることができません。それを回避するには成功・失敗双方の情報を保持できるオブジェクトを用意することが必要になります。ネットワーク処理については特に考慮が必要になる部分で、公式もその方法について触れています。 参考: Addendum: exposing network status, 公式サンプル
RxJava(もとい Rx)ではエラーも Stream に流れてくるため、subscribe 時にエラー発生時のアクションも扱えます。これが『そのままではエラー状態を扱えない』という表現の意図です。何にせよ、 null でエラー状態を表現することだけはやめましょう。
ViewModel - 画面回転時の Activity の再生成を跨ぐデータ保持
概要
- 画面回転時にデータを引き継げる
- 同じ親Activity に属する複数 Fragment のデータシェアが可能
{
compiles: [ "android.arch.lifecycle:runtime" ] ,
annotationProcessors: [],
dependsOn: []
}
ViewModel は画面回転時の Activity再生成 を跨いでデータを保持し、通常の終了であればデータを破棄するような生存期間をもったUIデータホルダーであり、ビジネスロジックも扱います。利用方法は非常にシンプルで、Activity#onSaveInstance(Bundle) / onResume / onStart / onPause / onDestroy などに処理を追加する必要はありません。
class MainActivity extends LifecycleActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MainActivityViewModel viewModel = ViewModelProviders.of(this).get(MainActivityViewModel.class);
}
}
class MainFragment extends LifecycleFragment {
@Override
protected void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
MainFragmentViewModel viewModel = ViewModelProviders.of(this).get(MainFragmentViewModel.class);
}
}
この ViewModel は LiveData を持ち、Activity/Fragment に対して LiveData を返すことでデータを提供します。もちろん
LiveData を利用せず、データ保持のヘルパーとして利用することも可能です。またこのとき ViewModel は以下の制約を持ちます。
- View に直接アクセスしてはいけない。
- Drawable などの Resource を持ってはいけない。
- Activity または Fragment を持ってはいけない。
- 終了時の処理は
onCleared()
に記述する。
責務が異なるためとも見れますが、メモリリークを避ける狙いが大きいと感じました。ViewModel は LiveData などのデータを取り出すだけのゲートウェイとして利用し、それ以外の用途では極力触らせないような印象を受けます。
異なる Fragment 間で共有可能な ViewModel
Activity を ViewModelProviders に渡せば ViewModel を取り出せることはすでに説明しました。つまり 異なる Fragment でも親Activity が同じであれば、Activity を利用して共通の ViewModel を取り出せるということです。この方法は公式も一つの応用法として紹介しています。
データ保持の実現方法
HolderFragment
が実際のデータホルダーです。ViewModelProviders から ViewModel を取得する際、FragmentManager に自動で差し込まれます。HolderFragment は setRetainInstance(true) な Fragment となっており、これによって画面回転による Activity 再生成の影響を受けません。このデータ保持の方法については既知のプラクティスですが、使い手から見ると上手く隠蔽されていますし、一番面倒な部分であるメモリリーク対策が丁寧に行われています。また Activity の再生成を跨いでデータを保持をしたくない場合は ViewModelProviders を使わずに ViewModel を生成してください。任意のタイミングで ViewModel を破棄したいと思ってしまった好奇心旺盛な方は HolderFragment#HOLDER_TAG
で明示的に FragmentManager から HolderFragment を removeしてください。
ViewModel/LiveData で Context を使う
ViewModel の中やその ViewModel が保持する LiveData の中で Context
を使いたい場合、ViewModel ではなく AndroidViewModel
というクラスを利用します。このクラスは Application を保持し、Activity の参照を掴むことがありません。つまり Activity 参照保持からのメモリリークという有名なバッドパターンを踏み抜かずに済みます。AndroidViewModel を継承したクラスの生成は ViewModelProviders.DefaultFactory
内にあります。後述する DI コンテナを利用するケースを除き、独自の ViewModelProvider.Factory を使うときは ViewModelProviders.DefaultFactory を忘れずに継承しましょう。
ViewModel と Dependency Injection
現在 ViewModelProviders から ViewModelProvider.Factory を指定しない ViewModel の取得を行なう際、Reflection を使ってViewModelの作成が行われています。しかしながら Dagger といった DI コンテナを使った場合、Repository の依存をConstructor Injection
で実現したいと思う方も多いでしょう。その場合、Reflection 以外の方法を用いた ViewModelProvider.Factory を定義する必要があります。公式サンプルでは Subcomponent.Builder
を利用して実現しています。 ViewModelSubComponent / ViewModelFactory
さらに Scope
の考慮も必要です。今までは Activity/Fragment Scope を使っていた方も多いと思いますが、データ関連は ViewModel Scope に置き換わる と考えた方が良いでしょう。この時、間違っても Activity/Fragment Scope で作成した Component を ViewModel に渡してはいけません。
同じ ViewModel クラスを複数使う
ViewModelProvider#get(String key, Class)
を使えば任意の key に対して ViewModel を保存できます。デフォルトでは ViewModel クラスの canonical name を key に使っているので、ViewModel は Activity/Fragment の生存期間から見ると singleton です。したがって key を分けない限り、2つにできません。正直そういったケースはあまり思い浮かびませんでした。 ちなみに、異なる ViewModel でキャッシュキーが被ったときの動作は対応外となっています。ログを出す TODO が書かれているだけでした。
MyViewModel viewModel1 = ViewModelProviders.of(activity).get("key", MyViewModel.class);
NotMyViewModel viewModel2 = ViewModelProviders.of(activity).get("key", NotMyViewModel.class);
// この時点で viewModel1#onCleared() は呼ばれません。
// またどのクラスも viewModel1 を管理していない状態になるため、viewModel1#onCleared() を呼ぶクラスはこの先もありません。
Architecture Components の現在での注意点
正式バージョンではないということ
まだバージョンが正式版ではなく、alpha 版です。移行タイミングに注意してください。
We’re not recommending that everyone migrate today. We’ll have a migration guide ready for the 1.0 release of architecture components.
Lifecycle Fragment and ActivityCompat in the Support Library do not yet implement LifecycleOwner interface. They will when Architecture Components reaches 1.0.0 version.
From: Release Notes
公式が言及している通り、1.0.0 正式版が出てから移行した方が良いでしょう。ただ Architecture Components の考え方自体は非常に明瞭ですし、フォーカスしている問題に対する1つの解だと思います。サイドプロジェクトで触るなどし、思想の理解を先に行っておくと良いかと思います。
Architecture Components では提供していない/定義していない点
Architecture Components はアプリに必要なすべての要素を支援するわけではありません。例えば以下の要素は開発者の発想と判断に委ねられています。
- ViewModel から得たパラメータと View の binding
ViewModel から LiveData を取り出し、View を更新するように。程度の記述に留まっています。その際、直接 View を更新してもよいでしょうし、DataBinding を用いて記述しても良いでしょう。
- Dependency Injection Containers
DIコンテナは提供されません。発表では一例として Dagger の名前が出てきました。
RxJava を使っているプロジェクト
すでにうまく Lifecycle と友達になっている場合には導入しなくても良いと発表内でも発言しています。RxLifecycle もすでにありますし、少なくともLifecycle 周りで導入する必要性はないでしょう。ただViewModel などはうまく利用できると思います。また Architecture Components を導入した後、Reactive Programming をより適用させたいなら RxJava を入れるといいという話が発表でもありました。また以下の依存で LiveDataReactiveStreams
という RxJava2 と LiveData の変換ヘルパーが提供されています。
異なる Activity を経由する ViewModel の受け渡し
現状の実装はまだ考慮していない/不安定で実装していないように見えます。一応技術的には可能ですが、設計をガン無視してしまいますし、根幹から否定することになります。この設計の上に載せようとした場合は ViewModelStore (ViewModel を直接管理しているクラス) を拡張する方法が素直に思えます。実際その拡張用 interface として ViewModelStoreOwner が切られています。しかし ViewModelStore が現状でも継承可能ながら全てのメンバーが final で、新しい管理方法を差し込む余地がありません。この部分は正式版リリース、あるいはその後に変更されるように思えます。
とりあえず ViewModel#onCleared() は呼ばれてしまいますが、ゴリ押しすれば・・・ jmatsu/gist
脚注
↑1 | まだ正式版でないことなどから、ただちに適用すべきかどうかはまた別の問題です。 |
---|---|
↑2 | 正確には ProcessLifecycleOwner は Application そのものではなく、最初と最後に起動した Activity を以って Lifecycle とします。つまり Application process そのものの Lifecycle ではありません。 |
↑3 | 基底クラスはLifecycleService |
↑4 | LifecycleRegistryOwner はサポートライブラリ統合後に削除される予定です |
↑5 | LifecycleObserver を直接継承しているわけではありません。 |
↑6 | 公式のコメントによると // TODO: Thread checks are too strict right now, we may consider automatically moving them to main thread. とあるので、変わるかもしれません。 |
↑7 | 状態変化が発生することを明示的に表すためのクラスとして分離し、値更新メソッドの可視性を上げているだけであり、LiveData が immutable であるという意味ではありません。 |