Android Kotlin 기초 05.2: LiveData 및 LiveData 관찰자

이 Codelab은 Android Kotlin 기초 과정의 일부입니다. Codelab을 순서대로 진행하면 이 과정의 학습 효과를 극대화할 수 있습니다. 모든 과정 Codelab은 Android Kotlin 기본사항 Codelab 방문 페이지에 나열되어 있습니다.

소개

이전 Codelab에서는 GuessTheWord 앱에서 ViewModel을 사용하여 기기 구성 변경 후에도 앱의 데이터가 유지되도록 했습니다. 이 Codelab에서는 LiveDataViewModel 클래스의 데이터와 통합하는 방법을 알아봅니다. LiveDataAndroid 아키텍처 구성요소 중 하나로, 기본 데이터베이스가 변경될 때 뷰에 알리는 데이터 객체를 빌드할 수 있습니다.

LiveData 클래스를 사용하려면 앱 데이터의 변경사항을 관찰하는 '관찰자' (예: 활동 또는 프래그먼트)를 설정합니다. LiveData는 수명 주기를 인식하므로 활성 수명 주기 상태에 있는 앱 구성요소 관찰자만 업데이트합니다.

기본 요건

  • Kotlin으로 기본 Android 앱을 만드는 방법
  • 앱의 대상 간에 이동하는 방법
  • 활동 및 프래그먼트 수명 주기
  • 앱에서 ViewModel 객체를 사용하는 방법
  • ViewModelProvider.Factory 인터페이스를 사용하여 ViewModel 객체를 만드는 방법

학습할 내용

  • LiveData 객체를 유용하게 만드는 요소
  • ViewModel에 저장된 데이터에 LiveData를 추가하는 방법
  • MutableLiveData을 사용하는 경우와 방법
  • LiveData.에서 변경사항을 관찰하는 관찰자 메서드를 추가하는 방법
  • 지원 속성을 사용하여 LiveData를 캡슐화하는 방법
  • UI 컨트롤러와 해당 ViewModel 간에 통신하는 방법

실습할 내용

  • GuessTheWord 앱에서 단어와 점수에 LiveData를 사용합니다.
  • 단어나 점수가 변경될 때 이를 감지하는 관찰자를 추가합니다.
  • 변경된 값을 표시하는 텍스트 뷰를 업데이트합니다.
  • LiveData 옵저버 패턴을 사용하여 게임 종료 이벤트를 추가합니다.
  • 다시 재생 버튼을 구현합니다.

5단원 Codelab에서는 시작 코드로 시작하여 GuessTheWord 앱을 개발합니다. GuessTheWord는 플레이어들이 가능한 최고 점수를 달성하기 위해 협력하는 2인용 제스처 스타일 게임입니다.

첫 번째 플레이어는 앱의 단어를 보고 두 번째 플레이어에게 단어를 보여주지 않으면서 각 단어를 차례로 연기합니다. 두 번째 플레이어가 단어를 추측합니다.

게임을 플레이하려면 첫 번째 플레이어가 기기에서 앱을 열고 아래 스크린샷과 같이 '기타'와 같은 단어를 확인합니다.

첫 번째 플레이어는 단어를 실제로 말하지 않도록 주의하면서 단어를 연기합니다.

  • 두 번째 플레이어가 단어를 맞히면 첫 번째 플레이어가 맞혔어요 버튼을 눌러 카운트를 1만큼 늘리고 다음 단어를 표시합니다.
  • 두 번째 플레이어가 단어를 맞히지 못하면 첫 번째 플레이어가 건너뛰기 버튼을 눌러 카운트를 1만큼 줄이고 다음 단어로 건너뜁니다.
  • 게임을 종료하려면 게임 종료 버튼을 누릅니다. (이 기능은 시리즈의 첫 번째 Codelab의 시작 코드에 없습니다.)

이 Codelab에서는 사용자가 앱의 모든 단어를 순환할 때 게임을 종료하는 이벤트를 추가하여 GuessTheWord 앱을 개선합니다. 또한 사용자가 게임을 다시 플레이할 수 있도록 점수 프래그먼트에 다시 플레이 버튼을 추가합니다.

타이틀 스크린

게임 화면

점수 화면

이 작업에서는 이 Codelab의 시작 코드를 찾아 실행합니다. 이전 Codelab에서 빌드한 GuessTheWord 앱을 시작 코드로 사용하거나 시작 앱을 다운로드할 수 있습니다.

  1. (선택사항) 이전 Codelab의 코드를 사용하지 않는 경우 이 Codelab의 시작 코드를 다운로드합니다. 코드의 압축을 풀고 Android 스튜디오에서 프로젝트를 엽니다.
  2. 앱을 실행하고 게임을 플레이합니다.
  3. Skip 버튼을 누르면 다음 단어가 표시되고 점수가 1점 감소하며, Got It 버튼을 누르면 다음 단어가 표시되고 점수가 1점 증가합니다. 게임 종료 버튼을 누르면 게임이 종료됩니다.

LiveData는 수명 주기를 인식하는 관찰 가능한 데이터 홀더 클래스입니다. 예를 들어 GuessTheWord 앱에서 현재 점수를 LiveData로 래핑할 수 있습니다. 이 Codelab에서는 LiveData의 여러 특성을 알아봅니다.

  • LiveData는 관찰 가능합니다. 즉, LiveData 객체에서 보유한 데이터가 변경되면 관찰자에 알림이 제공됩니다.
  • LiveData는 데이터를 보유합니다. LiveData는 모든 데이터에 사용할 수 있는 래퍼입니다.
  • LiveData는 수명 주기를 인식합니다. 즉, STARTED 또는 RESUMED와 같은 활성 수명 주기 상태인 관찰자만 업데이트합니다.

이 작업에서는 GameViewModel의 현재 점수와 현재 단어 데이터를 LiveData로 변환하여 데이터 유형을 LiveData 객체로 래핑하는 방법을 알아봅니다. 이후 작업에서는 이 LiveData 객체에 관찰자를 추가하고 LiveData를 관찰하는 방법을 알아봅니다.

1단계: LiveData를 사용하도록 점수와 단어 변경

  1. screens/game 패키지에서 GameViewModel 파일을 엽니다.
  2. scoreword 변수의 유형을 MutableLiveData로 변경합니다.

    MutableLiveData은 값이 변경될 수 있는 LiveData입니다. MutableLiveData는 일반 클래스이므로 이러한 클래스에 보유되는 데이터의 유형을 지정해야 합니다.
// The current word
val word = MutableLiveData<String>()
// The current score
val score = MutableLiveData<Int>()
  1. GameViewModelinit 블록 내에서 scoreword를 초기화합니다. LiveData 변수의 값을 변경하려면 변수에 setValue() 메서드를 사용합니다. Kotlin에서는 value 속성을 사용하여 setValue()를 호출할 수 있습니다.
init {

   word.value = ""
   score.value = 0
  ...
}

2단계: LiveData 객체 참조 업데이트

이제 scoreword 변수의 유형이 LiveData입니다. 이 단계에서는 value 속성을 사용하여 이러한 변수에 대한 참조를 변경합니다.

  1. GameViewModelonSkip() 메서드에서 scorescore.value로 변경합니다. scorenull일 수 있다는 오류가 표시됩니다. 이 오류는 다음에 수정합니다.
  2. 오류를 해결하려면 onSkip()에서 score.valuenull 검사를 추가하세요. 그런 다음 score에서 minus() 함수를 호출하여 null-안전으로 뺄셈을 실행합니다.
fun onSkip() {
   if (!wordList.isEmpty()) {
       score.value = (score.value)?.minus(1)
   }
   nextWord()
}
  1. onCorrect() 메서드를 동일한 방식으로 업데이트합니다. score 변수에 null 검사를 추가하고 plus() 함수를 사용합니다.
fun onCorrect() {
   if (!wordList.isEmpty()) {
       score.value = (score.value)?.plus(1)
   }
   nextWord()
}
  1. GameViewModelnextWord() 메서드 내에서 word 참조를 word.value로 변경합니다.
private fun nextWord() {
   if (!wordList.isEmpty()) {
       //Select and remove a word from the list
       word.value = wordList.removeAt(0)
   }
}
  1. GameFragmentupdateWordText() 메서드 내에서 viewModel.word의 참조를 viewModel.word.value.로 변경합니다.
/** Methods for updating the UI **/
private fun updateWordText() {
   binding.wordText.text = viewModel.word.value
}
  1. GameFragmentupdateScoreText() 메서드 내에서 viewModel.score에 대한 참조를 viewModel.score.value.로 변경합니다.
private fun updateScoreText() {
   binding.scoreText.text = viewModel.score.value.toString()
}
  1. GameFragmentgameFinished() 메서드 내에서 viewModel.score의 참조를 viewModel.score.value로 변경합니다. 필요한 null-안전 확인을 추가합니다.
private fun gameFinished() {
   Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show()
   val action = GameFragmentDirections.actionGameToScore()
   action.score = viewModel.score.value?:0
   NavHostFragment.findNavController(this).navigate(action)
}
  1. 코드에 오류가 없는지 확인합니다. 앱을 컴파일하고 실행합니다. 앱의 기능은 이전과 동일해야 합니다.

이 작업은 점수와 단어 데이터를 LiveData 객체로 변환한 이전 작업과 밀접한 관련이 있습니다. 이 작업에서는 Observer 객체를 이러한 LiveData 객체에 연결합니다.

  1. onCreateView() 메서드 내의 GameFragment,에서 현재 점수 viewModel.scoreLiveData 객체에 Observer 객체를 연결합니다. observe() 메서드를 사용하고 viewModel 초기화 뒤에 코드를 배치합니다. 람다 표현식을 사용하여 코드를 단순화합니다. (람다 표현식은 선언되지 않지만 즉시 표현식으로 전달되는 익명 함수입니다.)
viewModel.score.observe(this, Observer { newScore ->
})

Observer 참조를 해결합니다. 이렇게 하려면 Observer를 클릭하고 Alt+Enter (Mac에서는 Option+Enter)를 누른 다음 androidx.lifecycle.Observer를 가져옵니다.

  1. 방금 만든 관찰자는 관찰된 LiveData 객체에서 보유한 데이터가 변경되면 이벤트를 수신합니다. 관찰자 내에서 점수 TextView를 새 점수로 업데이트합니다.
/** Setting up LiveData observation relationship **/
viewModel.score.observe(this, Observer { newScore ->
   binding.scoreText.text = newScore.toString()
})
  1. Observer 객체를 현재 단어 LiveData 객체에 연결합니다. Observer 객체를 현재 점수에 연결한 것과 동일한 방식으로 연결합니다.
/** Setting up LiveData observation relationship **/
viewModel.word.observe(this, Observer { newWord ->
   binding.wordText.text = newWord
})

score 또는 word 값이 변경되면 화면에 표시된 score 또는 word가 이제 자동으로 업데이트됩니다.

  1. GameFragment에서 메서드 updateWordText()updateScoreText()와 이 메서드의 모든 참조를 삭제합니다. 텍스트 뷰는 LiveData 관찰자 메서드에 의해 업데이트되므로 더 이상 필요하지 않습니다.
  2. 앱을 실행합니다. 게임 앱이 이전과 똑같이 작동하지만 이제 LiveDataLiveData 관찰자를 사용합니다.

캡슐화는 객체의 일부 필드로의 직접 액세스를 제한하는 방법입니다. 객체를 캡슐화할 때 비공개 내부 필드를 수정하는 공개 메서드 집합을 노출할 수 있습니다. 캡슐화를 사용하여 다른 클래스가 비공개 내부 필드를 조작하는 방식을 제어할 수 있습니다.

현재 코드에서 외부 클래스는 value 속성을 사용하여 scoreword 변수를 수정할 수 있습니다(예: viewModel.score.value 사용). 이 Codelab에서 개발하는 앱에서는 문제가 되지 않을 수 있지만 프로덕션 앱에서는 ViewModel 객체의 데이터를 제어해야 합니다.

ViewModel만 앱의 데이터를 수정해야 합니다. 하지만 UI 컨트롤러는 데이터를 읽어야 하므로 데이터 필드를 완전히 비공개로 설정할 수는 없습니다. 앱의 데이터를 캡슐화하려면 MutableLiveDataLiveData 객체를 모두 사용합니다.

MutableLiveDataLiveData:

  • MutableLiveData 객체의 데이터는 이름에서 알 수 있듯이 변경할 수 있습니다. ViewModel 내에서는 데이터를 수정할 수 있어야 하므로 MutableLiveData을 사용합니다.
  • LiveData 객체의 데이터는 읽을 수 있지만 변경할 수는 없습니다. ViewModel 외부에서는 데이터를 읽을 수 있지만 수정할 수는 없어야 하므로 데이터는 LiveData로 노출되어야 합니다.

이 전략을 실행하려면 Kotlin 지원 속성을 사용합니다. 지원 속성을 사용하면 정확한 객체가 아닌 getter에서 무언가를 반환할 수 있습니다. 이 작업에서는 GuessTheWord 앱에서 scoreword 객체의 지원 속성을 구현합니다.

점수와 단어에 지원 속성 추가

  1. GameViewModel에서 현재 score 객체를 private로 만듭니다.
  2. 지원 속성에 사용된 이름 지정 규칙을 따르려면 score_score로 변경하세요. 이제 _score 속성은 내부적으로 사용되는 게임 점수의 변경 가능한 버전입니다.
  3. score이라는 LiveData 유형의 공개 버전을 만듭니다.
// The current score
private val _score = MutableLiveData<Int>()
val score: LiveData<Int>
  1. 초기화 오류가 표시됩니다. 이 오류는 GameFragment 내에서 scoreLiveData 참조이고 score이 더 이상 setter에 액세스할 수 없기 때문에 발생합니다. Kotlin의 게터 및 세터에 관한 자세한 내용은 게터 및 세터를 참고하세요.

    오류를 해결하려면 GameViewModel에서 score 객체의 get() 메서드를 재정의하고 지원 속성 _score을 반환하세요.
val score: LiveData<Int>
   get() = _score
  1. GameViewModel에서 score의 참조를 내부 변경 가능한 버전인 _score로 변경합니다.
init {
   ...
   _score.value = 0
   ...
}

...
fun onSkip() {
   if (!wordList.isEmpty()) {
       _score.value = (score.value)?.minus(1)
   }
  ...
}

fun onCorrect() {
   if (!wordList.isEmpty()) {
       _score.value = (score.value)?.plus(1)
   }
   ...
}
  1. word 객체를 _word로 이름을 바꾸고 score 객체에서와 같이 지원 속성을 추가합니다.
// The current word
private val _word = MutableLiveData<String>()
val word: LiveData<String>
   get() = _word
...
init {
   _word.value = ""
   ...
}
...
private fun nextWord() {
   if (!wordList.isEmpty()) {
       //Select and remove a word from the list
       _word.value = wordList.removeAt(0)
   }
}

잘하셨습니다. LiveData 객체 wordscore를 캡슐화했습니다.

사용자가 게임 종료 버튼을 탭하면 현재 앱이 점수 화면으로 이동합니다. 또한 플레이어가 모든 단어를 순환하면 앱이 점수 화면으로 이동하도록 해야 합니다. 플레이어가 마지막 단어를 입력한 후 사용자가 버튼을 탭하지 않아도 게임이 자동으로 종료되도록 하려고 합니다.

이 기능을 구현하려면 모든 단어가 표시될 때 ViewModel에서 프래그먼트로 트리거되고 전달되는 이벤트가 필요합니다. 이를 위해 LiveData 관찰자 패턴을 사용하여 게임 종료 이벤트를 모델링합니다.

관찰자 패턴

관찰자 패턴은 소프트웨어 디자인 패턴입니다. 객체 간 통신 (관찰 대상인 관찰 가능관찰자)을 지정합니다. 관찰 대상은 상태 변경사항을 관찰자에게 알리는 객체입니다.

이 앱의 LiveData의 경우 관찰 가능한 항목 (주체)은 LiveData 객체이고 관찰자는 프래그먼트와 같은 UI 컨트롤러의 메서드입니다. LiveData 내에 래핑된 데이터가 변경될 때마다 상태 변경이 발생합니다. LiveData 클래스는 ViewModel에서 프래그먼트로 통신하는 데 중요합니다.

1단계: LiveData를 사용하여 게임 종료 이벤트 감지

이 작업에서는 LiveData 옵저버 패턴을 사용하여 게임 종료 이벤트를 모델링합니다.

  1. GameViewModel에서 _eventGameFinish이라는 Boolean MutableLiveData 객체를 만듭니다. 이 객체는 게임 종료 이벤트를 보유합니다.
  2. _eventGameFinish 객체를 초기화한 후 eventGameFinish라는 지원 속성을 만들고 초기화합니다.
// Event which triggers the end of the game
private val _eventGameFinish = MutableLiveData<Boolean>()
val eventGameFinish: LiveData<Boolean>
   get() = _eventGameFinish
  1. GameViewModel에서 onGameFinish() 메서드를 추가합니다. 메서드에서 게임 종료 이벤트 eventGameFinishtrue로 설정합니다.
/** Method for the game completed event **/
fun onGameFinish() {
   _eventGameFinish.value = true
}
  1. GameViewModelnextWord() 메서드 내에서 단어 목록이 비어 있으면 게임을 종료합니다.
private fun nextWord() {
   if (wordList.isEmpty()) {
       onGameFinish()
   } else {
       //Select and remove a _word from the list
       _word.value = wordList.removeAt(0)
   }
}
  1. GameFragmentonCreateView() 내에서 viewModel을 초기화한 후 eventGameFinish에 관찰자를 연결합니다. observe() 메서드를 사용합니다. 람다 함수 내에서 gameFinished() 메서드를 호출합니다.
// Observer for the Game finished event
viewModel.eventGameFinish.observe(this, Observer<Boolean> { hasFinished ->
   if (hasFinished) gameFinished()
})
  1. 앱을 실행하고 게임을 플레이하면서 모든 단어를 살펴봅니다. 게임 종료를 탭할 때까지 게임 프래그먼트에 머무는 대신 앱이 자동으로 점수 화면으로 이동합니다.

    단어 목록이 비어 있으면 eventGameFinish이 설정되고 게임 프래그먼트의 연결된 관찰자 메서드가 호출되며 앱이 화면 프래그먼트로 이동합니다.
  2. 추가한 코드로 인해 수명 주기 문제가 발생했습니다. 문제를 파악하려면 GameFragment 클래스에서 gameFinished() 메서드의 탐색 코드를 주석 처리합니다. 메서드에 Toast 메시지를 유지해야 합니다.
private fun gameFinished() {
       Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show()
//        val action = GameFragmentDirections.actionGameToScore()
//        action.score = viewModel.score.value?:0
//        NavHostFragment.findNavController(this).navigate(action)
   }
  1. 앱을 실행하고 게임을 플레이하면서 모든 단어를 살펴봅니다. '게임이 방금 종료되었습니다'라는 토스트 메시지가 게임 화면 하단에 잠시 표시됩니다. 이는 예상된 동작입니다.

이제 기기나 에뮬레이터를 회전합니다. 토스트가 다시 표시됩니다. 기기를 몇 번 더 회전하면 매번 토스트가 표시될 것입니다. 게임이 종료될 때 토스트가 한 번만 표시되어야 하므로 이는 버그입니다. 토스트는 프래그먼트가 다시 생성될 때마다 표시되지 않아야 합니다. 다음 작업에서 이 문제를 해결합니다.

2단계: game-finished 이벤트 재설정

일반적으로 LiveData는 데이터가 변경될 때만 관찰자에게 업데이트를 전달합니다. 이 동작의 예외로, 관찰자가 비활성 상태에서 활성 상태로 변경될 때에도 관찰자는 업데이트를 받습니다.

이로 인해 앱에서 게임 종료 토스트가 반복적으로 트리거됩니다. 화면 회전 후 게임 프래그먼트가 다시 생성되면 비활성 상태에서 활성 상태로 이동합니다. 프래그먼트의 옵저버가 기존 ViewModel에 다시 연결되고 현재 데이터를 수신합니다. gameFinished() 메서드가 다시 트리거되고 토스트가 표시됩니다.

이 작업에서는 GameViewModel에서 eventGameFinish 플래그를 재설정하여 이 문제를 해결하고 토스트를 한 번만 표시합니다.

  1. GameViewModel에서 onGameFinishComplete() 메서드를 추가하여 게임 종료 이벤트인 _eventGameFinish를 재설정합니다.
/** Method for the game completed event **/

fun onGameFinishComplete() {
   _eventGameFinish.value = false
}
  1. GameFragmentgameFinished() 끝에서 viewModel 객체에 onGameFinishComplete()를 호출합니다. (탐색 코드는 현재 gameFinished()에서 주석 처리된 상태로 둡니다.)
private fun gameFinished() {
   ...
   viewModel.onGameFinishComplete()
}
  1. 앱을 실행하고 게임을 플레이합니다. 모든 단어를 살펴본 다음 기기의 화면 방향을 변경합니다. 토스트는 한 번만 표시됩니다.
  2. GameFragmentgameFinished() 메서드 내에서 탐색 코드의 주석을 해제합니다.

    Android 스튜디오에서 주석을 해제하려면 주석 처리된 줄을 선택하고 Control+/ (Mac은 Command+/)를 누릅니다.
private fun gameFinished() {
   Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show()
   val action = GameFragmentDirections.actionGameToScore()
   action.score = viewModel.score.value?:0
   findNavController(this).navigate(action)
   viewModel.onGameFinishComplete()
}

Android 스튜디오에서 메시지가 표시되면 androidx.navigation.fragment.NavHostFragment.findNavController를 가져옵니다.

  1. 앱을 실행하고 게임을 플레이합니다. 모든 단어를 거친 후 앱이 최종 점수 화면으로 자동 이동하는지 확인합니다.

좋습니다. 앱은 LiveData를 사용하여 게임 종료 이벤트를 트리거하여 GameViewModel에서 단어 목록이 비어 있음을 게임 프래그먼트로 전달합니다. 그런 다음 게임 프래그먼트가 점수 프래그먼트로 이동합니다.

이 작업에서는 점수를 ScoreViewModelLiveData 객체로 변경하고 관찰자를 연결합니다. 이 작업은 GameViewModelLiveData를 추가할 때 실행한 작업과 유사합니다.

앱의 모든 데이터가 LiveData을 사용하도록 완전성을 위해 ScoreViewModel을 변경합니다.

  1. ScoreViewModel에서 score 변수 유형을 MutableLiveData로 변경합니다. 규칙에 따라 _score로 이름을 바꾸고 지원 속성을 추가합니다.
private val _score = MutableLiveData<Int>()
val score: LiveData<Int>
   get() = _score
  1. ScoreViewModel에서 init 블록 내에 _score를 초기화합니다. init 블록에서 로그를 삭제하거나 그대로 둘 수 있습니다.
init {
   _score.value = finalScore
}
  1. ScoreFragmentonCreateView() 내에서 viewModel를 초기화한 후 점수 LiveData 객체의 관찰자를 연결합니다. 람다 표현식 내에서 점수 값을 점수 텍스트 뷰로 설정합니다. ViewModel에서 점수 값으로 텍스트 뷰를 직접 할당하는 코드를 삭제합니다.

추가할 코드:

// Add observer for score
viewModel.score.observe(this, Observer { newScore ->
   binding.scoreText.text = newScore.toString()
})

삭제할 코드:

binding.scoreText.text = viewModel.score.toString()

Android 스튜디오에서 메시지가 표시되면 androidx.lifecycle.Observer를 가져옵니다.

  1. 앱을 실행하고 게임을 플레이합니다. 앱은 이전과 같이 작동하지만 이제 LiveData와 관찰자를 사용하여 점수를 업데이트합니다.

이 작업에서는 점수 화면에 다시 플레이 버튼을 추가하고 LiveData 이벤트를 사용하여 클릭 리스너를 구현합니다. 버튼을 누르면 점수 화면에서 게임 화면으로 이동하는 이벤트가 트리거됩니다.

앱의 시작 코드에는 다시 플레이 버튼이 포함되어 있지만 버튼은 숨겨져 있습니다.

  1. res/layout/score_fragment.xml에서 play_again_button 버튼의 경우 visibility 속성 값을 visible으로 변경합니다.
<Button
   android:id="@+id/play_again_button"
...
   android:visibility="visible"
 />
  1. ScoreViewModel에서 _eventPlayAgain이라는 Boolean을 보유하는 LiveData 객체를 추가합니다. 이 객체는 점수 화면에서 게임 화면으로 이동하는 LiveData 이벤트를 저장하는 데 사용됩니다.
private val _eventPlayAgain = MutableLiveData<Boolean>()
val eventPlayAgain: LiveData<Boolean>
   get() = _eventPlayAgain
  1. ScoreViewModel에서 이벤트 _eventPlayAgain를 설정하고 재설정하는 메서드를 정의합니다.
fun onPlayAgain() {
   _eventPlayAgain.value = true
}
fun onPlayAgainComplete() {
   _eventPlayAgain.value = false
}
  1. ScoreFragment에서 eventPlayAgain의 관찰자를 추가합니다. onCreateView() 끝에 return 문 앞에 코드를 넣습니다. 람다 표현식 내에서 게임 화면으로 다시 이동하고 eventPlayAgain를 재설정합니다.
// Navigates back to game when button is pressed
viewModel.eventPlayAgain.observe(this, Observer { playAgain ->
   if (playAgain) {
      findNavController().navigate(ScoreFragmentDirections.actionRestart())
       viewModel.onPlayAgainComplete()
   }
})

Android 스튜디오에서 메시지가 표시되면 androidx.navigation.fragment.findNavController을 가져옵니다.

  1. ScoreFragmentonCreateView()에서 PlayAgain 버튼에 클릭 리스너를 추가하고 viewModel.onPlayAgain()을 호출합니다.
binding.playAgainButton.setOnClickListener {  viewModel.onPlayAgain()  }
  1. 앱을 실행하고 게임을 플레이합니다. 게임이 끝나면 점수 화면에 최종 점수와 다시 플레이 버튼이 표시됩니다. PlayAgain 버튼을 탭하면 앱이 게임 화면으로 이동하여 게임을 다시 플레이할 수 있습니다.

잘하고 있습니다! ViewModel에서 LiveData 객체를 사용하도록 앱의 아키텍처를 변경하고 LiveData 객체에 관찰자를 연결했습니다. LiveDataLiveData가 보유한 값이 변경되면 관찰자 객체에 알립니다.

Android 스튜디오 프로젝트: GuessTheWord

LiveData

  • LiveData는 수명 주기를 인식하는 관찰 가능한 데이터 홀더 클래스이며 Android 아키텍처 구성요소 중 하나입니다.
  • LiveData를 사용하여 데이터가 업데이트될 때 UI가 자동으로 업데이트되도록 할 수 있습니다.
  • LiveData는 관찰 가능합니다. 즉, 활동 또는 프래그먼트와 같은 관찰자는 LiveData 객체에서 보유한 데이터가 변경되면 알림을 받을 수 있습니다.
  • LiveData는 데이터를 보유합니다. 모든 데이터에 사용할 수 있는 래퍼입니다.
  • LiveData는 수명 주기를 인식합니다. 즉, STARTED 또는 RESUMED와 같은 활성 수명 주기 상태인 관찰자만 업데이트합니다.

LiveData 추가

  • ViewModel의 데이터 변수 유형을 LiveData 또는 MutableLiveData로 변경합니다.

MutableLiveData은 값이 변경될 수 있는 LiveData 객체입니다. MutableLiveData는 일반 클래스이므로 이러한 클래스에 보유되는 데이터의 유형을 지정해야 합니다.

  • LiveData에 저장된 데이터의 값을 변경하려면 LiveData 변수에서 setValue() 메서드를 사용합니다.

LiveData를 캡슐화하려면 다음을 실행하세요.

  • ViewModel 내부의 LiveData는 수정 가능해야 합니다. ViewModel 외부에서는 LiveData을 읽을 수 있어야 합니다. 이는 Kotlin 지원 속성을 사용하여 구현할 수 있습니다.
  • Kotlin 지원 속성을 사용하면 정확한 객체가 아닌 getter에서 무언가를 반환할 수 있습니다.
  • LiveData를 캡슐화하려면 ViewModel 내에서 private MutableLiveData를 사용하고 ViewModel 외부에서 LiveData 지원 속성을 반환합니다.

관찰 가능한 LiveData

  • LiveData는 관찰자 패턴을 따릅니다. '관찰 대상'은 LiveData 객체이고 관찰자는 프래그먼트와 같은 UI 컨트롤러의 메서드입니다. LiveData 내부에 래핑된 데이터가 변경될 때마다 UI 컨트롤러의 관찰자 메서드에 알림이 전송됩니다.
  • LiveData를 관찰 가능하게 하려면 observe() 메서드를 사용하여 관찰자 (예: 활동 및 프래그먼트)의 LiveData 참조에 관찰자 객체를 연결합니다.
  • LiveData 관찰자 패턴은 ViewModel에서 UI 컨트롤러로 통신하는 데 사용할 수 있습니다.

Udacity 과정:

Android 개발자 문서:

기타:

이 섹션에는 강사가 진행하는 과정의 일부로 이 Codelab을 진행하는 학생에게 출제할 수 있는 과제가 나열되어 있습니다. 다음 작업은 강사가 결정합니다.

  • 필요한 경우 과제를 할당합니다.
  • 과제 제출 방법을 학생에게 알립니다.
  • 과제를 채점합니다.

강사는 이러한 추천을 원하는 만큼 사용할 수 있으며 적절하다고 생각되는 다른 과제를 출제해도 됩니다.

이 Codelab을 직접 진행하는 경우 이러한 과제를 자유롭게 사용하여 배운 내용을 테스트해 보세요.

질문에 답하세요

질문 1

데이터를 업데이트하지 않고도 외부 객체가 데이터를 읽을 수 있도록 ViewModel에 저장된 LiveData를 캡슐화하려면 어떻게 해야 하나요?

  • ViewModel 객체 내에서 데이터의 데이터 유형을 private LiveData로 변경합니다. 지원 속성을 사용하여 MutableLiveData 유형의 읽기 전용 데이터를 노출합니다.
  • ViewModel 객체 내에서 데이터의 데이터 유형을 private MutableLiveData로 변경합니다. 지원 속성을 사용하여 LiveData 유형의 읽기 전용 데이터를 노출합니다.
  • UI 컨트롤러 내에서 데이터의 데이터 유형을 private MutableLiveData로 변경합니다. 지원 속성을 사용하여 LiveData 유형의 읽기 전용 데이터를 노출합니다.
  • ViewModel 객체 내에서 데이터의 데이터 유형을 LiveData로 변경합니다. 지원 속성을 사용하여 LiveData 유형의 읽기 전용 데이터를 노출합니다.

질문 2

UI 컨트롤러가 다음 중 어떤 상태에 있는 경우에 LiveData가 UI 컨트롤러(예: 프래그먼트)를 업데이트하나요?

  • 재개됨
  • 백그라운드
  • 일시중지됨
  • 중지됨

질문 3

LiveData 관찰자 패턴에서 관찰 가능한 항목 (관찰되는 항목)은 무엇인가요?

  • 관찰자 메서드
  • LiveData 객체의 데이터
  • UI 컨트롤러
  • ViewModel 객체

다음 강의 시작: 5.3: ViewModel 및 LiveData를 사용한 데이터 결합

이 과정의 다른 Codelab 링크는 Android Kotlin 기초 Codelab 방문 페이지를 참고하세요.