学ぶこと
LiveDat
でTransformations
を使用する方法
すること
- ゲームを終了するタイマーを追加します。
Transformations.map()
を使用して、あるLiveData
を別のLiveData
に変換します。
アプリの概要
今回は、前回のコードラボに続き、スコアの上に表示される一分間のカウントダウンタイマーを追加して、GuessTheWord アプリを改善します。カウントダウンが 0
に到達すると、タイマーはゲームを終了します。
また、transformation を使用して、経過時間 LiveData
オブジェクトをタイマー文字列 LiveData
オブジェクトに format します。変換された LiveData
は、timer's text view のデータバインディングソースです。
Timer を追加
このタスクでは、アプリに CountDownTimer
を追加します。word list が空の時にゲームが終了するのではなく、タイマーが終了するとゲームが終了します。Android には、タイマーの実装に使用する CountDownTimer
という urility クラスが用意されています。
GameViewModel
にタイマーのロジックを追加して、configuration の変更中にタイマーが破棄されないようにします。fragment には、タイマーが作動した時に timer text view を更新するコードが含まれています。
GameViewModel
クラスに次の手順を実装します:
- タイマー定数を保持する
companion
オブジェクトを作成します。
companion object { // Time when the game is over private const val DONE = 0L // Countdown time interval private const val ONE_SECOND = 1000L // Total time for the game private const val COUNTDOWN_TIME = 60000L }
- timer のカウントダウン時間を保存するには、
_currentTime
というMutableLiveData
メンバー変数と backing propertycurrentTime
を追加します。
// Countdown time private val _currentTime = MutableLiveData<Long>() val currentTime: LiveData<Long> get() = _currentTime
CountDownTimer
のtimer
と呼ばれるprivate
メンバー変数を追加します。次の手順で初期化エラーを解決します。
private val timer: CountDownTimer
init
ブロック内で、タイマーを初期化して開始します。合計時間COUNTDOWN_TIME
を渡します。time interval には、ONE_SECOND
を使用します。コールバックメソッドonTick()
およびonFinish()
をオーバーライドして、タイマーを開始します。
// Creates a timer which triggers the end of the game when it finishes timer = object : CountDownTimer(COUNTDOWN_TIME, ONE_SECOND) { override fun onTick(millisUntilFinished: Long) { } override fun onFinish() { } } timer.start()
onTick()
コールバックメソッドを実装します。これは interval または tick ごとに呼び出されます。渡されたパラメーターmilisUntilFinished
を使用して、_currentTime
を更新します。milisUntilFinished
は、タイマーが終了するまでの時間(ミリ秒単位)です。milisUntilFinished
をseconds
に変換し、それを_currentTime
に割り当てます。
override fun onTick(millisUntilFinished: Long) { _currentTime.value = millisUntilFinished/ONE_SECOND }
onFinish()
コールバックメソッドは、タイマーが終了した時に呼び出されます。onFinish()
を実装して、_currentTime
を更新し、ゲーム終了イベントをトリガーします。
override fun onFinish() { _currentTime.value = DONE onGameFinish() }
nextWord()
メソッドを更新して、ゲームを終了する代わりに、リストが空の時に単語リストをリセットします。
private fun nextWord() { // Shuffle the word list, if the list is empty if (wordList.isEmpty()) { resetList() } else { // Remove a word from the list _word.value = wordList.removeAt(0) } }
onCleared()
メソッド内で、メモリリークを回避するためにタイマーをキャンセルします。log ステートメントは不要になったため、削除できます。onCleared()
メソッドは、ViewModel
が破棄される前に呼び出されます。
override fun onCleared() { super.onCleared() // Cancel the timer timer.cancel() }
- アプリを実行してゲームをプレイします。60秒待つと、ゲームは自動的に終了します。ただし、タイマーテキストは画面に表示されません。次にそれを修正します。
LiveData の Transformation を追加する
Transformation.map()
) メソッドは、ソース LiveData
でデータを操作を実行し、結果の LiveData
オブジェクトを返す方法を提供します。これらの変換は、オブザーバーが返された LiveData
オブジェクトを監視していない限り計算されません。
このメソッドは、ソース LiveData
と関数をパラメーターとして受け取ります。この関数は、ソース LiveData
を操作します。
Note:
Transformation.map()
に渡されるラムダ関数はメインスレッドで実行されるため、長時間実行されるタスクは含めないでください。
このタスクでは、経過時間 LiveData
オブジェクトを MM:SS
形式の新しい文字列 LiveData
オブジェクトにフォーマットします。また、フォーマットされた経過時間を画面に表示します。
game_fragment.xml
レイアウトファイルには、すでに timer text view が含まれています。これまでのところ、text view には表示するテキストがないため、タイマーテキストは表示されていません。
GameViewModel
クラスで、currentTime
をインスタンス化した後、currentTimeString
という名前の新しいLiveData
オブジェクトを作成します。このオブジェクトは、currentTime
のフォーマットされた文字列バージョン用です。Transformation.map()
を使用してcurrentTimeString
を定義します。currentTime
とラムダ関数を渡して時間をフォーマットします。DataUtils.formatElapsedTime()
) ユーティリティーメソッドを使用してラムダ関数を実装できます。このメソッドは、long
ミリ秒を要し、MM:SS
文字列形式にフォーマットします。
// The String version of the current time val currentTimeString = Transformations.map(currentTime) { time -> DateUtils.formatElapsedTime(time) }
game_fragment.xml
ファイルの timer text view で、text
attribute をgameViewModel
のcurrentTimeString
にバインドします。
<TextView android:id="@+id/timer_text" ... android:text="@{gameViewModel.currentTimeString}" ... />
- アプリを実行してゲームをプレイします。タイマーテキストは1秒に一回更新されます。全ての word を循環しても、ゲームは終了しないことに注目してください。タイマーが切れるとゲームが終了します。
まとめ
Transforming LiveData
LiveData
の結果を変換したい場合があります。例えば、Date
string を "hours:mins:seconds" としてフォーマットしたり、リスト自体を返すのではなく、リスト内のアイテムの数を返したりすることができます。LiveData
でTransformations()
クラスのヘルパーメソッドを使用します。Transformations.map()
) メソッドは、LiveData
でデータ操作を実行し、別のLiveData
オブジェクトを返す簡単な方法を提供します。推奨される方法は、Transformations
クラスを使用するデータフォーマットロジックを UI データと共にViewModel
に配置することです。
変換の結果を TextView
に表示する
- ソースデータが
ViewModel
でLiveData
として定義されていることを確認してください。 newResult
などの変数を定義します。Transformation.map()
を使用して transformation を実行し、結果を変数に返します。
val newResult = Transformations.map(someLiveData) { input -> // Do some transformation on the input live data // and return the new value }
TextView
を含むレイアウトファイルがViewModel
の<data>
変数を宣言していることを確認してください。
<data> <variable name="MyViewModel" type="com.example.android.something.MyViewModel" /> </data>
- レイアウトファイルで、
TextView
のtext
attribute をViewModel
のnewResult
のバインディングに設定します。例えば:
android:text="@{SomeViewModel.newResult}"
Formatting dates
DateUtils.formatElapsedTime()
) ユーティリティメソッドは、long
ミリ秒数を要し、MM:SS
string format を使用するように数値をフォーマットします。