11
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Android #2Advent Calendar 2018

Day 20

[android-architecture] MVVM & LiveDataを読んでみる

Last updated at Posted at 2018-12-18

はじめに

この記事は、以前の記事の続きです。
GoogleSamples(android-architecture)を斜め読みして筆者が印象に残った内容を書きます。

バックナンバー

1. Model-View-Presenter(MVP)+αを読んでみる
2. Clean Architectureを読んでみる
3. MVVM & DataBindingを読んでみる
4. MVVM & LiveDataを読んでみる

MVVM - DataBinding - LiveData

LiveData とは?(javaDoc翻訳)

LiveDataは、特定のライフサイクル内で観察できるデータ所有者クラスです。
これは、 "Observer"を "LifecycleOwner"とペアで追加することができることを意味します。この "Observer"は、ペアになったLifecycleOwnerがアクティブ状態の場合にのみ、ラップされたデータの変更について通知を受けます。
"Lifecycle.State#STARTED"または "Lifecycle.State#RESUMED"の場合、 "LifecycleOwner"はアクティブと見なされます。
"observeForever(Observer)"を介して追加されたオブザーバは、常にアクティブであるとみなされ、変更について常に通知されます。これらのオブザーバについては、手動で "removeObserver(Observer)"を呼び出す必要があります。
ライフサイクルが追加されたオブザーバーは、対応するライフサイクルが「Lifecycle.State#DESTROYED」状態に移行すると自動的に削除されます。
これは、LiveDataを安全に監視し、リークを心配する必要のないアクティビティやフラグメントで特に役立ちます。
彼らは破壊されるとすぐに退会?(unsubscribed)されます。
さらに、 "LiveData"には "LiveData#onActive()"と "LiveData#onInactive()"メソッドがあり、アクティブオブザーバの数が0と1の間で変化したときに通知を受けます。
これにより、「LiveData」は、活発に観察しているオブザーバを持たない場合、重いリソースを解放することができます。
このクラスは、 "ViewModel"の個々のデータフィールドを保持するように設計されていますが、アプリケーション内の異なるモジュール間でデータを分けて共有するためにも使用できます。

LiveData のメリット

「LiveDataはデータのライフサイクルの管理を引き受けるので、プログラマは監視するデータの変更通知に応じて処理ハンドリングするだけで良いよ」というのがメリットと解釈しました。
現に、GoogleSamplesのコードを読むと、ActivityはonCreateしか使っていません。
onDestoryでの破棄処理は行なっていないですね。

###LiveData をどう使うか
ViewModelのフィールドとして使っています。
しかし、UIの表示更新ではなく、UIの操作イベントをViewModelが受け取り、処理し、その結果の値を LiveData のフィールドに代入します。
そうすると、値の変更通知が発生するので、それをキャッチして処理をするという訳です。
GoogleSamplesでは、Activityが変更通知を受け取り、別のActivityを起動する、といった事をしていました。

###ViewModelの管理方法
ViewModelは、「AndroidViewModel class」をextendsします。
インスタンス生成は、「ViewModelProvider.NewInstanceFactory」をextendsしたFactoryを使います。

以下、コード要約していきます。例によって分かり易い様、独自コードです。

ViewModel
public class XxxViewModel extends AndroidViewModel {
    public final ObservableField<String> title = new ObservableField<>();
    private final SingleLiveEvent<String> event = new SingleLiveEvent<>();

    SingleLiveEvent<String> getEvent() {return event;}

    public void onClick(View view) {
        event.setValue(...); // google sampleでは、list adapter内で値をsetしてました。
    }
}
ViewModelFactory
public class ViewModelFactory extends ViewModelProvider.NewInstanceFactory {
    @SuppressLint("StaticFieldLeak")
    private static volatile ViewModelFactory INSTANCE;
    public static ViewModelFactory getInstance(...) {return INSTANCE;}

    @Override
    public <T extends ViewModel> T create(Class<T> modelClass) {
        if (modelClass.isAssignableFrom(XxxViewModel.class)) {
            return (T) new XxxViewModel(...);
        } else if (...) {
            return ...;
        }
        ...
    }
}
Activity
public class XxxActivity extends AppCompatActivity {
    private XxxViewModel vm;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        setupViewFragment();
        ...
        vm = obtainViewModel(this);
        vm.getEvent().observe(this, new Observer<String>() {
            @Override
            public void onChanged(@Nullable String xxx) {
                ...
            }
        });
    }

    public static XxxViewModel obtainViewModel(FragmentActivity activity) {
        ViewModelFactory factory = ViewModelFactory.getInstance(activity.getApplication());
        XxxViewModel vm = ViewModelProviders.of(activity, factory).get(XxxViewModel.class);
        return vm;
    }

    private void setupViewFragment() {
        XxxFragment fragment = (XxxFragment) getSupportFragmentManager().findFragmentById(...);
        if (fragment == null) {
            fragment = XxxFragment.newInstance();
            /* fragment manager で add してね*/
        }
    }
}

ViewModelProviders.of().get()を使ってViewModelのインスタンスを共有する

ViewModelProviders.ofの引数にはActivityを指定します(Fragmentも指定可能)。
実は、ViewModelのインスタンスは、ofの引数で指定したオブジェクトが保持しています。
(保存したりする処理は、SDK内に隠蔽されているので、実際の開発では意識しなくて良い。)

これはつまり、「ofの引数で指定したActivity(Fragment)にアクセスできる範囲では、ViewModelのインスタンスを共有できる」という事。素晴らしい仕組みですね。

ViewModelProviders.of().get()は、既にViewModelのインスタンスが存在する場合は、そのインスタンスを返却し、存在しない場合は、ViewModelFactory#createが呼ばれ新規に生成します。

LiveDataのjavaDocにて書いていた事は、そういう意味だったのですね。

このクラスは、 "ViewModel"の個々のデータフィールドを保持するように設計されていますが、アプリケーション内の異なるモジュール間でデータを分けて共有するためにも使用できます。

ViewModelのインスタンスを共有できる事のメリットとは

Activityに属する複数のFragmentが、同一のViewModelを参照できる、ということは
つまり、データが一元管理されるので画面表示の不整合が発生しない、という事。

実装者が頑張って整合性を取らなくても、複数のFragmentのUIがViewModelの ObservableField とDataBindingしれいれば、フィールドの更新時に同時に不整合なくUIを更新してくれたり、 LiveData でイベントドリブンで処理を開始してくれたり、実装が楽になりますね。

Fragment
public class XxxFragment extends Fragment {
    private XxxViewModel vm;
    private XxxFragBinding binding;

    @Override
    public void onResume() {
        super.onResume();
        vm.start();
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        binding = XxxFragBinding.inflate(inflater, container, false);
        vm = XxxActivity.obtainViewModel(getActivity());
        // 他のFragmentでも自分にあったイベントハンドリングを貼っておける
        vm.getOpenTaskEvent().observe(this, new Observer<String>() {
            @Override
            public void onChanged(@Nullable String xxx) {
                ...
            }
        });
        // 他のFragmentでもUIとDataBindingできる。ViewModelは一元管理なので不整合無し!
        binding.setViewmodel(vm);
        return binding.getRoot();
    }
}

いかがでしたでしょうか

一見、こんな複雑な手順面倒いなぁ・・・と思いきや、Android SDK側が色々な処理を持ってくれたり、ViewModelが一元管理できたり。
生産性が向上する可能性をとても感じます。筆者も、ぜひプロダクトに取り入れていきたいです。

11
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?