Android Kotlin基礎講座 07.4:RecyclerViewのタップ処理

タスク:アイテムのクリックを処理する

このタスクでは、RecyclerView内のアイテムがクリックされたときの挙動を変更し、トーストを表示するのではなく、クリックされた睡眠データのより詳細な情報を表示するフラグメントに遷移させるようにします。

ステップ1:クリックで遷移させる

このステップでは、トーストを表示する代わりに、SleepTrackerFragmentのonCreateView()内のクリックリスナーのラムダ式を変更して、nightIdをSleepTrackerViewModelに渡し、SleepDetailFragmentへの遷移をトリガーするようにします。

クリックハンドラー関数を定義する

  1. SleepTrackerViewModel.ktを開いてください。
  2. SleepTrackerViewModel内、最後の方でonSleepNightClicked()クリックハンドラー関数を定義してください。
fun onSleepNightClicked(id: Long) {

}
  1. onSleepNightClicker()内で、_navigateToSleepDetailに引数で渡されたクリックされたアイテムのidを設定し、遷移をトリガーします。
fun onSleepNightClicked(id: Long) {
   _navigateToSleepDetail.value = id
}
  1. _navigateToSleepDetailを実装します。以前行ったように、privateのMutableLiveDataをナビゲーション状態用に定義してください。その後、publicで取得可能なvalを合わせて定義してください。
private val _navigateToSleepDetail = MutableLiveData<Long>()
val navigateToSleepDetail
   get() = _navigateToSleepDetail
  1. アプリが遷移を終えた後に呼び出すメソッドを定義します。onSleepDetailNavigated()と名付け、値をnullに設定してください。
fun onSleepDetailNavigated() {
    _navigateToSleepDetail.value = null
}

クリックハンドラーを呼び出すコードを追加する

  1. SleepTrackerFragment.ktを開き、adapterを作成しトーストを表示するためのSleepNightListenerを定義しているコードまでスクロールしてください。
val adapter = SleepNightAdapter(SleepNightListener { nightId ->
   Toast.makeText(context, "${nightId}", Toast.LENGTH_LONG).show()
})
  1. アイテムがタップされた時に、sleepTrackerViewModelのクリックハンドラー、onSleepNightClicked()を呼び出すために、以下のコードをトーストの下に追加してください。nightIdを渡し、ビューモデルがどの睡眠データを取得すべきかを知れるようにします。
    トーストはそのままでも、コメントアウト、または削除していただいても構いません。
sleepTrackerViewModel.onSleepNightClicked(nightId)

クリックを捕捉するためのコードを追加する

  1. SleepTrackerFragment.ktを開いてください。
  2. onCreateView()内、managerの宣言の直前に、新しく作成したnavigateToSleepDetail LiveDataを監視するためのコードを追加してください。
    navigateToSleepDetailが変更されると、nightを渡してSleepDetailFragmentに遷移し、その後onSleepDetailNavigated()を呼び出します。
    この作業は以前の記事で行っているので、以下に完成コードを記しておきます。
sleepTrackerViewModel.navigateToSleepDetail.observe(this, Observer { night ->
            night?.let {
              this.findNavController().navigate(
                        SleepTrackerFragmentDirections
                                .actionSleepTrackerFragmentToSleepDetailFragment(night))
               sleepTrackerViewModel.onSleepDetailNavigated()
            }
        })
  1. アプリを起動してアイテムをクリックしてください。
    …残念ながらアプリはクラッシュします。

バインディングアダプターのnull値を扱う

  1. アプリをもう一度デバッグモードで起動してください。アイテムをタップし、ログをエラーを表示するようにフィルタリングしてください。以下のようなものを含んだスタックトレースが表示されます。
Caused by: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter item

残念ながら、このスタックトレースはどこでエラーが引き起こされたかは明らかにしてくれません。データバインディングのデメリットの一つは、コードをデバッグするのが難しくなるという点です。アイテムをクリックするとアプリがクラッシュするわけですが、前後で追加したコードはクリックを扱うためのコードのみです。

しかしながら、この新しいクリック処理メカニズムによって、バインディングアダプターがitemの値がnullでも呼び出される可能性があるということが判明しました。特に、アプリスタート時、LiveDataはnullとしてスタートするので、それぞれのアダプターにnullチェックを追加する必要があります。

  1. BindingUtils.kt内、それぞれのバインディングアダプターのitem引数をnull許容に変更してください。そして、ボディ部分をitem?.let{…}で包んでください。
    例として、sleepQualityString用のアダプターは以下のようになります。他のアダプターも同様に変更してください。
@BindingAdapter("sleepQualityString")
fun TextView.setSleepQualityString(item: SleepNight?) {
   item?.let {
       text = convertNumericQualityToString(item.sleepQuality, context.resources)
   }
}
  1. アプリを起動してください。アイテムをタップすると、詳細ビューが開きます。

完成済みプロジェクト

お疲れさまでした。完成済みプロジェクトは以下からダウンロードできます。

RecyclerViewClickHandler