Android Kotlin Fundamentals: Use LiveData to control button states
学ぶこと
- database 内の既存の睡眠品質レコードを変更する方法
LiveData
を使用してボタンの状態を追跡する方法- イベントに応じて snackbar を表示する方法
すること
- TrackMySleepQuality アプリを拡張して、品質評価を収集し、データベースに評価を追加して結果を表示します。
LiveData
を使用して、snackbar の表示をトリガーします。LiveData
を使用して、ボタンを有効または無効にします。
Navigation を追加
コードを調べる
- Design Editor で、navigation.xml を開きます。
SleepTrackerFragment
からSleepQualityFragment
への navigation path があり、SleepQualityFragment
からSleepTrackerFragment
へ戻る navigation path があることが分かります。
- navigation.xml のコードを調べます。特に
sleepNightKey
という名前の<argument>
を探します。
ユーザが SleepTrackerFragment
から SleepQualityFragment
に移動すると、アプリは更新が必要な夜の sleepNightKey
を SleepQualityFragment
に渡します。
Sleep-quality を追跡するための navigation を追加する
navigation graph には、SleepTrackerFragment
から SleepQualityFragment
へのパスとその逆のパスがすでに含まれています。ただし、ある Fragment から次の Fragment への Navigation を実装するクリックハンドラーはまだコーディングされていません。ここで、そのコードを ViewModel
に追加します。
クリックハンドラーで、アプリを別の宛先に移動する時に変更される LiveData
を設定します。fragment はこの LiveData
を observe します。データが変更されると、fragment は宛先に移動し、完了したことを viewModel に通知します。これにより、状態変数がリセットーされます。
SleepTrackerViewModel
を開きます。ユーザーが Stop ボタンをタップした時にアプリがSleepQualityFragment
に移動して品質評価を収集できるように、navigation を追加する必要があります。SleepTrackerViewModel
で、アプリがSleepQualityFragment
に移動する時に変更されるLiveData
を作成します。カプセル化を使用して、Gettable バージョンのLiveData
のみをViewModel
に公開します。
このコードは、クラス本体の最上位のどこにでも配置できます。
private val _navigateToSleepQuality = MutableLiveData<SleepNight>() val navigateToSleepQuality: LiveData<SleepNight> get() = _navigateToSleepQuality
- navigation をトリガーする変数をリセットするために
doneNavigating()
関数を追加します。
fun doneNavigating() { _navigateToSleepQuality.value = null }
- Stop ボタンのクリックハンドラー
onStopTracking()
で、SleepQualityFragment
への navigation をトリガーします。関数の最後に_navigateToSleepQuality
変数をlaunch{}
ブロック内の最後のものとして設定します。この変数はnight
に設定されていることに注意してください。この変数に値がある場合、アプリはSleepQualityFragment
に移動し、night を渡します。
_navigateToSleepQuality.value = oldNight
SleepTrackerFragment
は、_navigateToSleepQuality
を observe して、アプリがいつ Naivigate するかを認識できるようにする必要があります。SleepTrackerFragment
のonCreateView()
で、navigateToSleepQuality()
のオブザーバーを追加します。androidx.lifecycle.Observer
をインポートする必要があることに注意してください。
sleepTrackerViewModel.navigateToSleepQuality.observe(this, Observer {
})
- observer ブロック内で、current night の ID を navigate して渡し、
doneNavigating()
を呼び出します。androidx.navigation.fragment.findNavController
をインポートする必要があります。
night -> night?.let { this.findNavController().navigate( SleepTrackerFragmentDirections .actionSleepTrackerFragmentToSleepQualityFragment(night.nightId)) sleepTrackerViewModel.doneNavigating() }
- アプリをビルドして実行します。Start ボタンをタップしてから Stop ボタンをタップすると、
SleepQualityFragment
画面が表示されます。戻るにはシステムの Back Button を使用します。
睡眠の質を記録する
このタスクでは、睡眠の質を記録し、sleep tracker fragment に戻ります。表示は自動的に更新され、更新された値がユーザに表示されます。ViewModel
と ViewModelFactory
を作成し、SleepQualityFragment
を更新する必要があります。
ViewModel と ViewModelFactory を作成する
sleepquality
パッケージで、SleepQualityViewModel.kt
を作成または開きます。sleepNightKey
とデータベースを引数として取るSleepQualityViewModel
クラスを作成します。SleepTrackerViewModel
の場合と同じように、factory からdatabase
を渡す必要があります。また、navigation からsleepNightKey
を渡す必要があります。
class SleepQualityViewModel( private val sleepNightKey: Long = 0L, val database: SleepDatabaseDao) : ViewModel() { }
- 上記と同じパターンを使用して、
SleepTrackerFragment
に戻るには、_navigateTpSleepTracker
を宣言します。navigateToSleepTracker
とdoneNavigating()
を実装します。
private val _navigateToSleepTracker = MutableLiveData<Boolean?>() val navigateToSleepTracker: LiveData<Boolean?> get() = _navigateToSleepTracker fun doneNavigating() { _navigateToSleepTracker.value = null }
- 使用する全ての睡眠品質の画像に対して、ワンクリックハンドラー
onSetSleepQuality()
を作成します。
前のコードラボと同じ Coroutine パターンを使用します:
viewModelScope
で coroutine を起動するsleepNightKey
を使用してtonight
を取得する- sleep quality をセットする
- databse を更新する
- navigation をトリガーする
以下のコードサンプルは、異なるコンテキストでのデータベース操作を除外するのではなく、クリックハンドラーで全ての作業を実行することに注意してください。
fun onSetSleepQuality(quality: Int) { viewModelScope.launch { val tonight = database.get(sleepNightKey) ?: return@launch tonight.sleepQuality = quality database.update(tonight) // Setting this state variable to true will alert the observer and trigger navigation. _navigateToSleepTracker.value = true } }
- 以下に示すように、
sleepquality
パッケージをで、SleepQualityViewModelFactory.kt
を作成または開き、SleepQualityViewModelFactory
クラスを追加します。このクラスは、以前に見たのと同じ定型コードのバージョンを使用します。
class SleepQualityViewModelFactory( private val sleepNightKey: Long, private val dataSource: SleepDatabaseDao) : ViewModelProvider.Factory { @Suppress("unchecked_cast") override fun <T : ViewModel?> create(modelClass: Class<T>): T { if (modelClass.isAssignableFrom(SleepQualityViewModel::class.java)) { return SleepQualityViewModel(sleepNightKey, dataSource) as T } throw IllegalArgumentException("Unknown ViewModel class") } }
SleepQualityFragment を更新する
SleepQualityFragment.kt
を開きます。onCreateView()
で、アプリケーションを取得した後、navigation に付属のarguments
を取得する必要があります。これらの引数はSleepQualityFragmentArgs
にあります。bundle からそれらを抽出する必要があります。
val arguments = SleepQualityFragmentArgs.fromBundle(arguments!!)
- 次に
dataSource
を取得します。
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
dataSource
とsleepNightKey
を渡して、Factory を作成します。
val viewModelFactory = SleepQualityViewModelFactory(arguments.sleepNightKey, dataSource)
viewModel
参照を取得します。
val sleepQualityViewModel = ViewModelProvider( this, viewModelFactory).get(SleepQualityViewModel::class.java)
- binding object に
ViewModel
を追加します。(バインディングオブジェクトにエラーが表示された場合は、今のところ無視してください)
binding.sleepQualityViewModel = sleepQualityViewModel
- observer を追加します。プロンプトが表示されたら
androidx.lifecycle.Observer
sleepQualityViewModel.navigateToSleepTracker.observe(this, Observer { if (it == true) { // Observed state is true. this.findNavController().navigate( SleepQualityFragmentDirections.actionSleepQualityFragmentToSleepTrackerFragment()) sleepQualityViewModel.doneNavigating() } })
レイアウトファイルを更新してアプリを実行します
fragment_sleep_quality.xml
レイアウトファイルを開きます。<data>
ブロックに、SleepQualityViewModel
の変数を追加します。
<data> <variable name="sleepQualityViewModel" type="com.example.android.trackmysleepquality.sleepquality.SleepQualityViewModel" /> </data>
- 6つの睡眠品質の画像のそれぞれに、次のようなクリックハンドラーを追加します。品質評価を画像に一致させます。
android:onClick="@{() -> sleepQualityViewModel.onSetSleepQuality(5)}"
- プロジェクトをクリーンアップして再構築します。これにより、binding object のエラーが解決されます。それ以外の場合は、キャッシュをクリアし(File > Invalidate Caches / Restart)、アプリを再構築します。
コントロールボタンの可視性とスナックバーの追加
このタスクでは、transformation maps を使用してボタンの可視性を管理し、ユーザが正しい選択のみを行えるようにする方法を学習します。同様の方法を使用して、すべてのデータがクリアされた後に分かりやすいメッセージを表示できます。
Button States を更新する
アイデアは、最初はスタートボタンのみが有効になるようにボタンの状態を設定することです。つまり、clickable です。
ユーザが Start ボタンをタップすると、Stop ボタンが有効になり、Start ボタンは無効になります。Clear ボタンは、データベースにデータがある場合にのみ有効になります。
fragment_sleep_tracker.xml
レイアウトファイルを開きます。- 各ボタンに
android:enabled
プロパティを追加します。android:enabled
プロパティは、ボタンが有効かどうかを示す bool 値です。
start_button
:
android:enabled="@{sleepTrackerViewModel.startButtonVisible}"
stop_button
:
android:enabled="@{sleepTrackerViewModel.stopButtonVisible}"
clear_button
:
android:enabled="@{sleepTrackerViewModel.clearButtonVisible}"
SleepTrackerViewModel
を開き、対応する3つの変数を作成します。各変数に、それをテストする Transformation を割り当てます。Start button は
tonight
がnull
の場合有効になります。- Stop button は、
tonight
がnull
ではない場合に有効になります。 - Clear button は、
nights
、データベースにデータが含まれている場合にのみ有効になります。
val startButtonVisible = Transformations.map(tonight) { it == null } val stopButtonVisible = Transformations.map(tonight) { it != null } val clearButtonVisible = Transformations.map(nights) { it?.isNotEmpty() }
- アプリを実行して、ボタンの挙動を確認してみてください。
Tip: 無効な View の外観を設定する enabled 属性は、visibility 属性と同じではありません。enabled 属性は、View が表示されているかどうかではなく、View が有効かどうかを決定するだけです。
無効になっている View にはデフォルトのスタイルが適用され、View がアクティブでないことを視覚的に表します。
ただし、View に background 属性または textColor 属性がある場合、View が無効になっている場合でも、View が表示される時にこれらの属性の値が使用されます。
有効状態と無効状態に使用する色を定義するには、テキストの色に ColorStateList を使用し、background color に StateListDrawable を使用します。
snackbar を使用してユーザに通知する
ユーザがデータベースをクリアした後、Snackbar
widget を使用してユーザに確認を表示します。snackbar は、画面の下部にあるメッセージを通じて、操作に関する簡単なフィードバックを提供します。snackbar は、タイムアウト後、画面上の他の場所でユーザーが操作した後、またはユーザが snackbar を画面からスワイプした後に消えます。
Snackbar の表示は UI タスクであり、Fragment で行われる必要があります。snackbar を表示することを決定するのは ViewModel
で行われます。データがクリアされた時に snackbar を設定してトリガーするには、navigation のトリガーと同じ手法を使用できます。
SleepTrackerViewModel
で、カプセル化されたイベントを作成します。
private var _showSnackbarEvent = MutableLiveData<Boolean>() val showSnackBarEvent: LiveData<Boolean> get() = _showSnackbarEvent
- 次に、
doneShowingSnackbar()
を実装します。
fun doneShowingSnackbar() { _showSnackbarEvent.value = false }
SleepTrackerFragment
のonCreateView()
で、observer を追加します。
sleepTrackerViewModel.showSnackBarEvent.observe(this, Observer { })
- observer ブロック内に、snackbar を表示し、すぐにイベントをリセットします。
if (it == true) { // Observed state is true. Snackbar.make( activity!!.findViewById(android.R.id.content), getString(R.string.cleared_message), Snackbar.LENGTH_SHORT // How long to display the message. ).show() sleepTrackerViewModel.doneShowingSnackbar() }
SleepTrackerViewModel
で、onClear()
メソッドでイベントをトリガーします。これを行うには、launch
ブロック内でイベント値をtrue
に設定します。
_showSnackbarEvent.value = true
- アプリをビルドして実行します。
まとめ
これらのパターンに注目すると、既存のアプリのコードを再利用できるため、コーディングスピードが向上します。
ViewModel
とViewModelFactory
を作成し、datasource を設定します。- navigation をトリガーします。関心の分離を行うには、クリックハンドラーを ViewModel に配置し、navigation を Fragment に配置します。
LiveData
でカプセル化を使用して、状態の変化を追跡し、応答します。LiveData
で transformations を使用します。- Singleton database を作成します。
- database 操作用の Coroutine を設定します。
Navigation のトリガー
navigation file 内の fragment 間の可能な navigation path を定義します。ある Fragment から次の Fragment への navigation をトリガーする方法はいくつかあります。これらには以下が含まれます:
onClick
ハンドラーを定義して、destination fragment への navigation をトリガーします。- ある Fragment から次の Fragment への navigation を有効にするには:
- navigation が必要かどうかを記録する
LiveData
値を定義します。 - その
LiveData
値にオブザーバーをアタッチします。 - 次に、navigation をトリガーする必要がある時、または navigation が完了するたびに、コードはその値を変更します。
android:enabled attribute の設定
android:enabled
属性はTextView
で定義され、Button
を含むすべてのサブクラスに継承されます。android:enabled
属性は、View
を有効にするかどうかを決定します。"enabled" の意味はサブクラスによって異なります。例えば、有効になっていないEditText
は、ユーザがテキストを編集できないようにし、有効になっていないButton
は、ユーザがボタンをタップできないようにします。enabled
属性は、visibility
属性と同じではありません。- transformation maps を使用して、別のオブジェクトまたは変数の状態に基づいてボタンの
enabled
属性の値を設定できます。
このコードラボでカバーされているその他のポイント:
- ユーザへの通知をトリガーするには、navigation のトリガーに使用するのと同じ手法を使用できます。
- Snackbar
を使用してユーザに通知できます。