Google I/O 2018アプリで使われているAdapterのクリックイベントをViewModelで受け取り、LiveDataを使ってFragment/Activityに通知する

Google I/O 2018 のセッションスケジュールなどがみれるアプリがかなり勉強になるので、ソースコードを読んでて学んだことを一つ書いてみようと思います。

アプリのダウンロードはこちら

https://play.google.com/store/apps/details?id=com.google.samples.apps.iosched

ソースコードはこちら

https://github.com/google/iosched


## AdapterのクリックイベントをViewModelで受け取り、LiveDataを使ってFragment/Activityに通知する

MVVMアーキテクチャとして基本的なロジックはViewModelに書きます。
ですが、画面遷移などViewに関わる処理はFragment or Activityでないと画面遷移できません。

ViewModelからViewへの処理はDatabinding経由でやることになっているので直接ViewModelからViewの処理を呼び出すことはできません。
なので、GoogleI/OアプリはLiveDataを使ってその辺りを解決しています。

例えばスケジュールを詳細を開く時のタップイベントはViewModelで受け取るようにして、ViewModelでLiveDataを使って変更をFragmentに検知させて画面遷移をしています。

スケジュールアイテムをタップする

<img src="https://qiita-image-store.s3.amazonaws.com/0/139360/fa52c4f8-be2d-8ccd-1891-751cfb465930.png" width="200">

↓

<img src="https://qiita-image-store.s3.amazonaws.com/0/139360/afb463ed-c6f6-2bf6-58c8-62d92ecdd9f7.png" width="200">


### ViewModelのメンバ変数にLiveDataのメンバ変数を設定

実際のソースコードに載せていきます。

ViewModelのメンバ変数にLiveDataのメンバ変数を設定しています。

- ScheduleViewModel.kt

```kotlin

private val _navigateToSessionAction = MutableLiveData<Event<String>>()
val navigateToSessionAction: LiveData<Event<String>>
   get() = _navigateToSessionAction
```

### FragmentでLiveDataの変更をObserveする

FragmentでLiveDataの変更をobserveする

- ScheduleFragment.kt

```kotlin
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()` が実行されています。

```kotlin
override fun openEventDetail(id: SessionId) {
   _navigateToSessionAction.value = Event(id)
}
```

### RecylerViewAdapterにアイテムのタップイベントを設定する

セッションのアイテムは `ScheduleDayFragment.kt` クラスにてRecyclerViewを生成する処理が実装されています。
正確にいうと `ScheduleFragment.kt` で `ScheduleDayFragment` を生成しています。

これは上タブを実装するために、ViewPagerを使っているからです。

`ScheduleDayFragment.kt` をみていきます。

`ScheduleDayAdapter` にviewModelをコンストラクタとして渡していますが、実際には `ScheduleEventListener` の実装を渡しています。

- ScheduleDayFragment.kt

```kotlin
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    adapter = ScheduleDayAdapter(
         viewModel,
         tagViewPool,
         viewModel.showReservations,
         viewModel.timeZoneId,
         this)
}
```

### ScheduleDayAdapterの中でeventListenerをbindする

- item_session.xml

```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しています。


```kotlin

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アプリ完全に理解したわー」になれる気がする。