<이전글>
<코드랩 과정5-4 : LiveData Transformations>
<참고글>
# 시작하며
일단 지금껏 해오던 코드랩에서 한가지의 기능을 추가할 것이다. 단어가 더이상 없으면 EndGame이 되는 것이 아닌 제한시간이 지나면 게임이 종료되도록 기능을 수정한다. 또한 단어가 사라진다면 단어를 다시 섞어서(reset) 해서 보여준다.
위 기능을 추가하기 위해서는 타이머를 구현해야한다. 또한 그냥 LONG의 자료형을 가지는 타이머를 00:00와 같은 형식으로 변환시켜야한다. 이 기능을 Transformations를 사용하면 손쉽게 구현할 수 있다.
# 타이머 만들기 ( ViewModel에 구현)
안드로이드에서 제공하는 유틸중에 CountDownTimer가 존재하기 때문에 어렵지 않게 구현 할 수 있다.
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
}
타이머에서 사용되는 1Tick과 전체 주어진 시간을 설정하기 위한 static한 변수를 만들어준다.
남은시간또한 LiveData로 활용한다면 시간이 변화할때마다 UI를 새로 업데이트 시켜 줄 수 있을 것이다.
// Countdown time
private val _currentTime = MutableLiveData<Long>()
val currentTime: LiveData<Long>
get() = _currentTime
이제 timer를 만들어줘야한다. timer는 게임이 시작됐을때 작동해야하기 때문에, init블럭 안에다가 만들어줘야 한다.
private val timer: CountDownTimer
init {
timer = object : CountDownTimer(COUNTDOWN_TIME, ONE_SECOND){
override fun onTick(millisUntilFinished: Long) {
Log.d(TAG,_currentTime.value.toString())
_currentTime.value = millisUntilFinished/ ONE_SECOND
}
override fun onFinish() {
_currentTime.value = DONE
onGameFinish()
}
}.start()
}
뷰모델이 종료되는 시점에는 타이머또한 메모리에서 해제해주어 메모리 누수를 막아준다.
override fun onCleared() {
super.onCleared()
// Cancel the timer
timer.cancel()
}
타이머의 기능 구현을 끝냈다. 이제 카드가 더이상 존재하지 않을때 시간이 남아있다면 카드를 다시 섞어주도록 기능을 개선해준다.
private fun nextWord() {
if (!wordList.isEmpty()) {
//Select and remove a word from the list
_word.value = wordList.removeAt(0)
} else {
// 단어가 없을때 게임이 종료될 수 있도록
resetList()
}
지금 시점에서 currentTime에는 LiveData가 담겨 있으며, 1초라면 1000L, 2초라면 2000L과 같은 데이터가 담겨져 있을 것이다. 우리가 화면에 표현해야하는 것은 00:01과 같은 형식이기에 라이브 데이터를 변환시켜야 한다.
# Transformations 사용
Transformations.map()을 사용한다면 이러한 LiveData를 손쉽게 변환할 수 있다. 라이브데이터source를 건네받고 함수안에서 변환시킨다음에 변환된 LiveData를 반환 시킨다.
특히 주목해야 할 것은, Transformations.map()을 사용한다면, source로 주어지는 원본 LiveData의 value가 갱신될때마다 함께 갱신된다. 참고로 적어놨던 블로그에서 MediatiorLiveData를 사용하기 때문에 가능하다고 적어주셨다.
위에 설명을 참고하며 아래와 같이 코드를 작성해주자. DateUtils를 통해서 남은시간을 손쉽게 00:00과 같은 형태로 만들어 줄 수 있고 이 것이 LiveData가 되어 반환된다.
// The String version of the current time
val currentTimeString = Transformations.map(currentTime) { time ->
DateUtils.formatElapsedTime(time)
}
이렇게 생성된 currentTimeString을 UI에 업데이트 시켜줘야한다. 프래그먼트에서 Observer를 통해서 구현할 수 있겠지만 이번 코드랩에서는 data-binding을 사용했기 때문에 xml에 추가해주는 것만으로도 손쉽게 연결이 가능했다.
<TextView
android:id="@+id/timer_text"
...
android:text="@{gameViewModel.currentTimeString}"
... />
# 마치며
ViewModel과 LiveData에 대해서 배우게 되면서 지금껏 내가 작성했던 코드들이 매우 체계가 없었음을 깨달아 가고 있다.