はじめに
この記事は、以前の記事の続きです。
GoogleSamples(android-architecture)を斜め読みして筆者が印象に残った内容を書きます。
バックナンバー
1. Model-View-Presenter(MVP)+αを読んでみる
2. Clean Architectureを読んでみる
3. MVVM & DataBindingを読んでみる
4. MVVM & LiveDataを読んでみる
Model‑View‑ViewModel (MVVM) - DataBinding
##ViewModel
ObservableFieldを使ってUIとViewModelをバインドしています。
(ちなみに、BaseObservable#notifyPropertyChanged を使ってもできます)
ViewModelはRepositoryも持っていて、必要な情報は自分で用意します。
public abstract class XxxViewModel extends BaseObservable {
ObservableField<Xxx> xxx = new ObservableField<>();
Repository repository;
}
##BindingAdapter
xmlの要素とViewModelの変数を変換します。
また、イベントハンドラとしても使えます。
便利な反面、乱用すると追いにくい不具合の温床にもなりかねない諸刃の剣の側面も。
// xml の記載
<ListView app:items="@{viewmodel.items}"/>
// ソース側
public class ListBindings {
@BindingAdapter("app:items")
public static void setItems(ListView listView, List<xxx> items) {
XxxAdapter adapter = (XxxAdapter) listView.getAdapter();
if (adapter != null) adapter.method(items);
}
}
// xml の記載
<SwipeRefreshLayout android:onRefresh="@{viewmodel}" />
// ソース側
public class XxxBinding {
@BindingAdapter("android:onRefresh")
public static void onSwipeRefresh(SwipeRefreshLayout v, XxxViewModel vm) {
v.setOnRefreshListener(() -> {vm.xxxx()});
}
}
##ViewModelの管理方法
このサンプルコードでは、UI無しFragmentを定義し、FragmentがViewModelの参照を管理しています。
メリットは、例えば画面回転をした際に毎度ViewModelをインスタンス化すると効率が悪いですが
Fragmentのフィールドとして保持しグローバル変数的に参照すれば、再度インスタンス化せずに済みます。
(実はこの辺り、AACで解決されるので自作不要です。AACのViewModel管理はこちら)
ソースがちょっと長いので、意味が通じる程度に要約(圧縮、改変した)コードを記載します。
public class ViewModelHolder<VM> extends Fragment {
private VM vm; // setter, getterも定義してね
public ViewModelHolder() {...}
public static <M> ViewModelHolder create(M vm) {
ViewModelHolder<M> holder = new ViewModelHolder<>();
holder.vm = vm;
return holder;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
}
public class XxxActivity extends AppCompatActivity {
public static final String TAG = "TAG";
private TasksViewModel vm;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
XxxFragment fragment = findOrCreateViewFragment();
...
vm = findOrCreateViewModel();
...
fragment.setViewModel(vm);
}
private XxxViewModel findOrCreateViewModel() {
ViewModelHolder<XxxViewModel> holder = (ViewModelHolder<XxxViewModel>) getSupportFragmentManager().findFragmentByTag(TAG);
if (holder == null || holder.getViewmodel() == null) {
hodler = ViewModelHolder.create(new XxxViewModel(xxx));
/* add ViewHodler(fragment) */
/* fragment manager で add してね*/
}
return holder.getViewmodel();
}
private XxxFragment findOrCreateViewFragment() {
XxxFragment fragment = (XxxFragment) getSupportFragmentManager().findFragmentById(xxx);
if (fragment == null) {
fragment = XxxFragment.newInstance();
/* fragment manager で add してね*/
}
return fragment;
}
}
public class XxxFragment extends Fragment {
private XxxViewModel vm;
private XxxFragBinding binding;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = XxxFragBinding.inflate(inflater, container, false);
binding.setView(this);
binding.setViewmodel(vm);
return binding.getRoot();
}
public void setViewModel(TasksViewModel vm) {this.vm = vm;}
}
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View" />
<variable name="view" type="XxxFragment"/>
<variable name="vm" type="XxxViewModel" />
</data>
...
<LinearLayout
android:visibility="@{vm.xxx ? View.GONE : View.VISIBLE}" />
...
</layout>
いかがでしたでしょうか
xml側の記載量が増えますが、ソースコード側はいちいちViewを検索してイベントハンドラを登録して〜といった、面倒なView操作がググッと減りそうですね。ビジネスロジックの変更に集中できる様になれば、生産性も上がるのかもしれません。