公式Webサイトでは、ViewModelにアクティビティコンテキストを導入できないなど、ViewModelについていくつかの説明を行っていますが、ViewModelをより適切に使用するための予防策や慣用句(一般的な構文)はまだたくさんあります。
この記事は、Googleの公式Webサイトで推奨されているブログ投稿を参照しています:ViewModelsおよびLiveData:Patterns + AntiPatterns
公式ウェブサイトで提供されている建築コンポーネントの使用の全体的な説明を見てみましょう:
理想的には、ViewModelsはAndroidに何も導入しないでください。これにより、テスト容易性、リークの安全性、モジュール性が向上します。一般的な経験則は、Androidがないことを確認することです。*インポート(android.archを除く。*)ViewModelに。同じことがプレゼンター層にも当てはまります。
❌注1:ViewModel(またはプレゼンターレイヤー)にAndroidフレームワーククラスを導入しないでください。
条件付きステートメント、ループ、および一般的な決定は、アクティビティやフラグメントではなく、ViewModelまたはアプリケーションの他のレイヤーで行う必要があります。Viewは通常(Robolectricを使用しない限り)単体テストされていないため、コードの行数が少ないほど優れています。VIewビューは、データを表示し、ユーザーイベントをViewModel(またはPresenter)に送信する方法のみを知っている必要があります。これは、パッシブ ビューパッシブビューモードと呼ばれます。
つまり、最小化されたロジックコードは、アクティビティとフラグメントに保持する必要があります。
❌注2:「ViewModel公式ウェブサイトの学習の概要」の記事では、ViewModelのライフサイクルがActivityよりも大きいため、ViewModelレイヤーにActivityまたはフラグメントのコンテキストを導入してメモリリークやクラッシュを回避することはできないと説明されています。
注意: ViewModelsとViewの相互作用、推奨される方法は、LiveDataや他のライブラリのオブザーバーを使用するなどのオブザーバーモードです。つまり、データを直接UIレイヤーに渡さないでください。ただし、UIレイヤーにデータの変更を監視させます。つまり、データがUIを駆動します。
肥大化したViewModelを回避する
参考:アーキテクチャコンポーネントを使用したライフサイクル対応のデータロード
注意:これは、ViewModelsデザインの目的がLiveDataを作成および整理することであるため、データ処理ロジックを直接ViewModelに配置することを避け、LiveData(または他のオブザーバブル)に配置することです。一部のロジックは、プレゼンター層に転送できます。
メモ:ロジックをLiveDataにカプセル化します。同じLiveDataを多くのViewModelで再利用したり、MediatorLiveDataを介して複数のLiveDataソースを組み合わせたり、サービスで使用したりすることも有益です。
次のコードを参照してください。
public class JsonViewModel extends AndroidViewModel {
// You probably have something more complicated
// than just a String. Roll with me
private final MutableLiveData<List<String>> data =
new MutableLiveData<List<String>>();
public JsonViewModel(Application application) {
super(application);
loadData();
}
public LiveData<List<String>> getData() {
return data;
}
private void loadData() {
new AsyncTask<Void,Void,List<String>>() {
@Override
protected List<String> doInBackground(Void... voids) {
File jsonFile = new File(getApplication().getFilesDir(),
"downloaded.json");
List<String> data = new ArrayList<>();
// Parse the JSON using the library of your choice
return data;
}
@Override
protected void onPostExecute(List<String> data) {
this.data.setValue(data);
}
}.execute();
}
}
上記のコードは、多くのデータ取得または処理ロジックをViewModelレイヤーに配置します。これは不合理であり、次のメソッドに変更する必要があります。
public class JsonViewModel extends AndroidViewModel {
private final JsonLiveData data;
public JsonViewModel(Application application) {
super(application);
data = new JsonLiveData(application);
}
public LiveData<List<String>> getData() {
return data;
}
}
public class JsonLiveData extends LiveData<List<String>> {
private final Context context;
public JsonLiveData(Context context) {
this.context = context;
loadData();
}
private void loadData() {
new AsyncTask<Void,Void,List<String>>() {
@Override
protected List<String> doInBackground(Void… voids) {
File jsonFile = new File(getApplication().getFilesDir(),
"downloaded.json");
List<String> data = new ArrayList<>();
// Parse the JSON using the library of your choice
return data;
}
@Override
protected void onPostExecute(List<String> data) {
setValue(data);
}
}.execute();
}
}
これでViewModelは非常にシンプルになりました。これで、LiveDataはロードプロセスを完全にカプセル化し、データを1回だけロードします。
データリポジトリレイヤーを使用する
参照ブログの説明によると、実際、公式のWebサイトのドキュメントでは、データがネットワーク、ローカルメモリ、キャッシュなどから取得された可能性がある場合は、リポジトリレイヤーを追加してこれらのデータ処理操作をカプセル化し、次にプレゼンテーションレイヤー(プレゼンターまたはViewModels)を追加する必要があるとしています)データの出所を気にする必要はありません。リポジトリレイヤーは、データへの統一された入り口です。
✅:つまり、外部データへの唯一の入り口としてデータリポジトリレイヤーを追加します。
データのステータスを処理しています
次のシナリオを考えてみます。表示する一連のリストデータを含むViewModelによって公開されているLiveDataを監視しています。ビューでは、読み込まれたデータ、ネットワークエラー、空のリストをどのように区別しますか?
ViewModelからLiveData <MyDataState>を公開できます。たとえば、MyDataStateには、データが現在ロードされているかどうか、正常にロードされたか失敗したか、またはその他の生データ情報に関する情報を含めることができます。
注意:つまり、ラッパークラスを使用してデータのステータス情報を公開するか、別のLiveDataを使用します。
アクティビティの状態を保存する
アクティビティステータスは、アクティビティが消えた後に画面を再作成するために必要な情報であり、アクティビティが破棄されたか、プロセスが終了したことを示します。回転は最も明らかな状況であり、ViewModelsでカバーしました。状態がViewModelに保存されている場合、状態は安全です。
ただし、ViewModelも表示されなくなった他のシナリオでは、状態を復元する必要がある場合があります。たとえば、オペレーティングシステムのリソースが不十分で、プロセスが終了した場合などです。
UI状態を効果的に保存および復元するには、永続性、onSaveInstanceState()、およびViewModelsの組み合わせを使用します。
例については、ViewModels:Persistence、onSaveInstanceState()、UI状態とローダーの復元を参照してください。
イベント
ここで参照されるイベントは1回だけ発生します。ViewModelsはデータを公開しますが、イベントについてはどうですか?たとえば、ナビゲーションイベント、許可アプリケーション、またはSnackbarメッセージの表示は、一度だけ実行する必要があるアクションです。
イベントの概念は、LiveDataがデータを保存および復元する方法と完全には一致しません。以下のフィールドを持つViewModelを考えてみましょう:
LiveData<String> snackbarMessage = new MutableLiveData<>();
アクティビティがこの操作の監視を開始し、ViewModelが操作を完了するため、メッセージを更新する必要があります。
snackbarMessage.setValue("Item saved!");
このとき、Activityは値の変更を取得してSnackbarを表示します。問題はないようですが、画面を回転させると新しいアクティビティが作成され、古い値が受信されるため、Snackbarに再度メッセージが表示されます。
この問題を解決するには、サードパーティのライブラリを使用したり、アーキテクチャコンポーネントを拡張したりせず、イベントを状態の一部として扱う必要があります。
つまり、イベントを状態の一部として設計します。詳細については、SnackBarを使用したLiveData、ナビゲーション、その他のイベント(SingleLiveEventの場合)を参照してください。
ViewModelのリークに注意してください
次のシナリオを検討してください。
プレゼンテーション層はオブザーバーモードを使用し、データ層はインターフェイスコールバックを使用します。
ユーザーがアプリを終了すると、ビューが消え、ViewModelが観察されなくなります。リポジトリレイヤーがシングルトンまたはアプリケーションのライフサイクルと同じ場合、プロセスが強制終了されるまでリポジトリは破棄されません。プロセスは、システムリソースが不足しているか、ユーザーが手動で強制終了した場合にのみ終了します。リポジトリレイヤーにViewModelコールバックがある場合、ViewModelはメモリを一時的にリークします。
ViewModelが比較的軽量であるか、操作がすぐに終了する場合は、大きな影響はありません。しかし、これは常にそうだとは限りません。理想的には、ViewModelを監視するViewがない限り、ViewModelを破棄する必要があります。
これには多くの方法があります。
- ViewModelのonCleared()メソッドを使用して、リポジトリレイヤーにコールバックをViewModelにドロップするように指示できます。
- リポジトリでは、弱参照を使用するか、EventBusを使用できます(どちらも簡単に誤用されたり、有害と見なされたりすることもあります)
- LiveDataを使用してビューとViewModelの間でやり取りするように、LiveDataを使用してViewModelとリポジトリレイヤーの間でやり取りする
重要:アーキテクチャーのインスタンスオブジェクトに対する境界条件、リーク、および時間のかかる操作の影響を考慮する必要があります。
リポジトリレイヤーでLiveDataを使用する
ViewModelとコールバック地獄のリークを回避するために、次のようにリポジトリレイヤーを確認できます。
ViewModelがクリアされるか、Viewのライフサイクルが終了すると、ViewModelとリポジトリの間のサブスクリプション関係がクリアされます。
このメソッドを試すと問題があります。LifecycleOwnerにアクセスできない場合、ViewModelからリポジトリをサブスクライブするにはどうすればよいですか?変換の使用は、この問題を解決する非常に便利な方法です。Transformations.switchMapを使用すると、他のLiveDataインスタンスの変更に応じて新しいLiveDataを作成できます。また、チェーン全体でオブザーバーのライフサイクル情報を運ぶことができます。
LiveData<Repo> repo = Transformations.switchMap(repoIdLiveData, repoId -> {
if (repoId.isEmpty()) {
return AbsentLiveData.create();
}
return repository.loadRepo(repoId);
}
);
この例では、トリガーが更新を取得すると、関数が適用され、結果が下流でスケジュールされます。アクティビティはリポジトリを監視し、同じLifecycleOwnerがrepository.loadRepo(id)呼び出しに使用されます。
✅ つまり、ViewModel でLifecycleオブジェクトを取得する必要がある場合は、現時点で変換を使用できます。
LiveDataを展開する
LiveDataを使用する通常の方法は、ViewModelでMutableLiveDataを使用し、getメソッドを公開することです。
さらに機能が必要な場合は、アクティブなオブザーバーがいるときにExtended LiveDataから通知されます。たとえば、これは、位置情報サービスやセンサーサービスのリスニングを開始する場合に便利です。
public class MyLiveData extends LiveData<MyData> {
public MyLiveData(Context context) {
// Initialize service
}
@Override
protected void onActive() {
// Start listening
}
@Override
protected void onInactive() {
// Stop listening
}
}
LiveDataを拡張すべきでない場合
onActive()を使用してデータをロードするいくつかのサービスを開始することもできますが、正当な理由がない限り、LiveDataが監視されるまで待つ必要はありません。いくつかの一般的なパターン:
- start()メソッドをViewModelに追加し、できるだけ早く呼び出します[ Google公式の例 Blueprintsの例を参照 ]
- プロパティを設定して読み込みを開始します[ GithubBrowserExampleを参照 ]。
usually 通常、 LiveData は拡張しません。アクティビティまたはフラグメントにデータの読み込みを開始するタイミングをViewModelに通知させます
最後に:
ViewModelの使用に関して、公式Webサイトはブログを推奨しています:ViewModels:A Simple Example
Android codelabでのViewModelの使用例:https ://codelabs.developers.google.com/codelabs/android-room-with-a-view/#0
YouTubeでのViewModelの使用に関する公式の紹介:https : //www.youtube.com/watch?v=c9-057jC1ZA
Android Jetpack:ViewModel |中国語教育ビデオ:https : //mp.weixin.qq.com/s/uGWH1os8Kq3Pp6_x5hXI8Q
アーキテクチャコンポーネントの使用については 、公式Webサイトにいくつかの簡単な例があります。
https://github.com/googlesamples/android-architecture-components/blob/master/README.md
そして公式サイトで推奨されているJetpackの使用例: Sunflower
ライフサイクルコンポーネントのMVVMバージョンを使用したAndroidアーキテクチャコンポーネントを含むGitHubクライアント Githubブラウザサンプル
これは、Dagger 2でAndroidアーキテクチャコンポーネントを使用するサンプルアプリです。
注 これは比較的複雑で完全な例であるため、アーキテクチャコンポーネントに慣れていない 場合は、まずこのリポジトリの他の例を確認することを強くお勧めします。
公式ウェブサイトでは、これは比較的複雑で完全な例であることを紹介しています。依存性注入フレームワークDagger2をある程度理解している必要があります。この例を学ぶことは非常に非常に推奨されます。
その後、さらにコードを記述し、いくつかのアーキテクチャコンポーネントの学習経験を要約する時間があります。