この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
いきなりですが、
Viewとの通信はデータバインディング機構のような仕組みを通じて行うため、ViewModelの変更は開発者から見て自動的にViewに反映される。
引用元:https://ja.wikipedia.org/wiki/Model_View_ViewModel
さて、MVVMではViewへの表示をDataBindingになるべく任せたいですね。
ですが、Androidには様々なViewが存在します。
DataBindingを使うことで、Viewへの表示がとても簡単になりましたが、一部のViewはそのまま値を渡すことが難しいです。
ex) RecyclerView, NavigationView, TabLayoutなど
今回はNavigationViewへの反映についてご紹介します。
ユーザーのログイン状態でメニューの表示を切り替える
今回のサンプルでは、TwitterKitを利用してログイン中ならユーザー情報を表示させます。
イメージ画像です。
では、コードを見ていきましょう。まずはViewModelです。
ViewModel
class TopViewModel {
val mRepository: UserRepository = UserRepository()
var user: ObservableField<User> = ObservableField()
fun getUser() {
launch(UI) {
val session = Twitter.getSessionManager().activeSession
if (session != null) {
val (status, user) = async(context + CommonPool) { mRepository.getUser(session) }.await()
when (status) {
Status.SUCCESS -> {
this@TopViewModel.user.set(user)
}
}
}
}
}
}
getUserでRepositoryからユーザー情報を取得します。
ログインされていない状態では、sessionがnullなので、userも空の状態となります。
また、userをObservableFieldにすることで、値が変化した際にViewに反映されるようにします。
ヘッダーレイアウト
NavigationViewのヘッダー部分のレイアウトです。こちらもDataBindingに表示を任せます。
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
>
<data>
<variable
name="viewModel"
type="com.helmos.app.features.top.TopViewModel"
/>
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<ImageView
android:id="@+id/user_background_image"
android:layout_width="match_parent"
android:layout_height="200dp"
android:scaleType="centerCrop"
app:imageUrl="@{viewModel.user != null ? viewModel.user.bannerUrl : ``}"
/>
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/baseBlack_alpha2"
/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginBottom="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
>
<ImageView
android:id="@+id/user_profile_image"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginEnd="8dp"
android:scaleType="centerCrop"
app:imageUrl="@{viewModel.user != null ? viewModel.user.imageUrlHttps : ``}"
/>
<TextView
android:id="@+id/user_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toEndOf="@id/user_profile_image"
android:text="@{viewModel.user != null ? viewModel.user.name : ``}"
android:textColor="@color/baseWhite"
android:textStyle="bold"
/>
<TextView
android:id="@+id/user_screen_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/user_name"
android:layout_toEndOf="@id/user_profile_image"
android:text="@{viewModel.user != null ? `@` + viewModel.user.screenName : ``}"
android:textColor="@color/baseWhite"
/>
</RelativeLayout>
</FrameLayout>
</layout>
メニュー
NavigationViewのメニュー部分です。いつも通りな感じで。
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
<group android:checkableBehavior="single">
<item
android:id="@+id/menu_image_search"
android:icon="@android:drawable/ic_menu_gallery"
android:title="@string/image_search"
/>
</group>
<item android:title="@string/other">
<menu android:checkableBehavior="single">
<item
android:id="@+id/menu_license"
android:title="@string/license"
/>
<item
android:id="@+id/menu_login"
android:title="@string/login"
/>
<item
android:id="@+id/menu_logout"
android:title="@string/logout"
android:visible="false"
/>
</menu>
</item>
</menu>
Activityのレイアウト
NavigationViewの部分のみ記載します。その他はご自由に。
<android.support.design.widget.NavigationView
android:id="@+id/main_navigation"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="@layout/navigation_header"
app:menu="@menu/drawer_menu"
app:topNavigation="@{viewModel.user}"
/>
BindingAdapterを作る
NavigationViewの表示を切り替えるためにBindingAdapterを作ります。
デフォルトのDataBinding機構だと足りない箇所を補ってあげるイメージです。
object NavigationViewBindingAdapter {
@JvmStatic
@BindingAdapter("bind:topNavigation")
fun setupTopNavigation(view: NavigationView, user: User?) {
view.menu.findItem(R.id.menu_login).isVisible = user == null
view.menu.findItem(R.id.menu_logout).isVisible = user != null
}
}
BindingAdapterを利用して、メニューのログイン・ログアウトの表示を切り替えます。
まとめ
公式のDataBindingによって、AndroidでもMVVMを採用しやすくなったと感じます。
しかし、HTMLなどと違い、Viewによってはクラスを別に作成する必要があったり(Recyclerなど)と、そのままではバインドできないことが多いです。
そういった箇所で、BindingAdapterを利用して、ViewModelからViewへの反映を自動でさせられるようにしましょう。
ViewModelにViewを持たせて、Viewのメソッドを呼んでしまうと、MVPチックになってしまうので注意しましょう。(体験談)