Google I/O 2018 のセッションスケジュールなどがみれるアプリがかなり勉強になるので、ソースコードを読んでて学んだことを一つ書いてみようと思います。
アプリのダウンロードはこちら
ソースコードはこちら
AdapterのクリックイベントをViewModelで受け取り、LiveDataを使ってFragment/Activityに通知する
MVVMアーキテクチャとして基本的なロジックはViewModelに書きます。
ですが、画面遷移などViewに関わる処理はFragment or Activityでないと画面遷移できません。
ViewModelからViewへの処理はDatabinding経由でやることになっているので直接ViewModelからViewの処理を呼び出すことはできません。
なので、GoogleI/OアプリはLiveDataを使ってその辺りを解決しています。
例えばスケジュールを詳細を開く時のタップイベントはViewModelで受け取るようにして、ViewModelでLiveDataを使って変更をFragmentに検知させて画面遷移をしています。
スケジュールアイテムをタップする

↓

ViewModelのメンバ変数にLiveDataのメンバ変数を設定
実際のソースコードに載せていきます。
ViewModelのメンバ変数にLiveDataのメンバ変数を設定しています。
- ScheduleViewModel.kt
private val _navigateToSessionAction = MutableLiveData<Event<String>>()
val navigateToSessionAction: LiveData<Event<String>>
get() = _navigateToSessionAction
FragmentでLiveDataの変更をObserveする
FragmentでLiveDataの変更をobserveする
- ScheduleFragment.kt
scheduleViewModel.navigateToSessionAction.observe(this, EventObserver { sessionId ->
openSessionDetail(sessionId)
})
この設定をした時点で ScheduleViewModel
の中で navigateToSessionAction
に setValue
された時点でFragmentに通知されて openSessionDetail(sessionId)
メソッドが呼び出されることになります。 ( openSessionDetail
メソッドでは中で startActivity
が実行されています。
RecyclerViewAdapterでのアイテムのタップイベントを検知してViewModelで受け取る
ViewModelの中で setValue
をどういう風に呼び出すかというとこれもDatabindingを使ってうまくアーキテクチャにのせています。
ScheduleViewModel
クラスは ScheduleEventListener
というinterfaceを実装しています。
この実装の中の openEventDetail
メソッドで setValue()
が実行されています。
override fun openEventDetail(id: SessionId) {
_navigateToSessionAction.value = Event(id)
}
RecylerViewAdapterにアイテムのタップイベントを設定する
セッションのアイテムは ScheduleDayFragment.kt
クラスにてRecyclerViewを生成する処理が実装されています。
正確にいうと ScheduleFragment.kt
で ScheduleDayFragment
を生成しています。
これは上タブを実装するために、ViewPagerを使っているからです。
ScheduleDayFragment.kt
をみていきます。
ScheduleDayAdapter
にviewModelをコンストラクタとして渡していますが、実際には ScheduleEventListener
の実装を渡しています。
- ScheduleDayFragment.kt
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
adapter = ScheduleDayAdapter(
viewModel,
tagViewPool,
viewModel.showReservations,
viewModel.timeZoneId,
this)
}
ScheduleDayAdapterの中でeventListenerをbindする
- item_session.xml
<data>
<variable
name="eventListener"
type="com.google.samples.apps.iosched.ui.sessioncommon.EventActions" />
</data>
・・・・・・・・もろもろのレイアウトの実装・・・・・・・・・・
<!--ここにタップイベントが仕込まれている↓-->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:onClick="@{() -> eventListener.openEventDetail(userSession.session.id)}"
android:paddingEnd="@dimen/spacing_normal"
android:paddingVertical="@dimen/spacing_normal"
tools:targetApi="o">
xml内の android:onClick
を使ってボタンのタップイベントを仕込んでいます。
この仕込まれたタップイベントをRecyclerViewAdapterのViewHolderで引数で渡ってきた ScheduleEventListener
をbindしています。
fun bind(userSession: UserSession) {
binding.userSession = userSession
// ↓ここで引数をバインドしている
binding.eventListener = eventListener
binding.showReservations = showReservations
binding.timeZoneId = timeZoneId
binding.setLifecycleOwner(lifecycleOwner)
binding.executePendingBindings()
}
(このコードなぜか apply
を使ってかかれていないのは謎です........)
これで全てが連動されてタップイベントがFragmentで実行されていることになります。
まとめ
- ViewModelにLiveDataのメンバ変数を設定
- FragmentでLiveDataの変数が
setValue
されるのをobserveする - ViewModelの側でボタンのタップイベントを検知するinterfaceを実装している
- ViewModelの実装をRecylerViewAdapterに引数として渡す
- RecylerViewAdapterで生成する子のViewに引数で渡ってきたリスナーをDatabindingを使ってセットしてクリックイベントを発火させる
これから
次はDaggaar2でDIしている箇所をちゃんと理解したい。
ここを理解できたら「Google I/Oアプリ完全に理解したわー」になれる気がする。