yourginieus
ViewModel 및 LiveData를 통한 데이터 바인딩 본문
1. Add ViewModel data binding
- 이전까지는 앱의 view에 접근하기 위한 type-safe 방법으롤 data binding을 사용함
- 하지만 data binding의 진정한 가치는, 이름에서 알 수 있듯이, 앱의 view 개체에 데이터를 직접 바인딩할 수 있다는 것!
- 현재의 app architecture
- view는 XML 레이아웃에서 정의되며, 뷰들을 위한 data는 ViewModel objects에 저장됨
- 각 view와 관련된 ViewModel 사이에는 UI controller가 있어서 이들 사이의 중계자 역할을 함

- Git it 버튼은 game_fragment.xml 레이아웃 파일의 Button에 정의됨
- 사용자가 Got it 버튼을 탭하면, GameFragment 프래그먼트의 클릭리스너가 그에 대응하는, GameViewModel에 있는 클릭리스너를 호출함
- GameViewModel에서 스코어가 업데이트됨
- -> Button view와 GameViewModel은 서로 직접 통신하지 않음 : GameFragment에 있는 클릭리스너를 필요로 함
ViewModel을 data binding으로 전달하기
- layout의 view가 ViewModel의 데이터와 직접(UI controller에 의존하지 않고) 통신하는 게 더 간단할 것!

- ViewModel object는 앱의 모든 UI data를 보유함
- ViewModel object를 data binding으로 전달함으로써, view와 viewModel object의 통신 중 일부를 자동화할 수 있음
- 지금은 GameViewModel과 ScoreViewModel 클래스를 그와 관련된 xml layout과 관련지을 것
- 또한 클릭 이벤트를 처리하기 위해 listener binding을 설정할 것
STEP 1 : Add data binding for the GameViewModel
- GameViewModel을 그와 관련된 레이아웃 파일인 game_fragment.xml 과 연결할 것
- game_fragment.xml 파일에 GameViewModel 타입의 data-binding 변수 추가하기
<layout ...>
<data>
<variable
name="gameViewModel"
type="com.example.android.guesstheword.screens.game.GameViewModel" />
</data>
<androidx.constraintlayout...
- GameFragment 파일에서, GameViewModel을 data binding에 전달하기
- viewModel을 binding.gameViewModel 변수로 지정해줘야 함
- 그 코드를 onCreateView() 안에 viewModel을 초기화한 코드 이후에 넣기
// Set the viewmodel for databinding - this allows the bound layout access
// to all the data in the ViewModel
binding.gameViewModel = viewModel
STEP 2 : Use listener bindings for event handling
- Listener binding : onClick(), onZoomIn(), onZoomOut() 등의 이벤트가 발생됐을 때 실행되는 binding expressions
- Listener binding은 람다식으로 작성됨
- Data binding은 listener를 생성하고 view에 해당 리스너를 설정함
- event를 위한 리스너가 작동하면, 리스너는 람다식을 검토함
- Listener binding은 Android Gradle Plugin 2.0 이상에서 작동함
- 이번 스텝에서는 GameFragment 안에 있는 클릭리스너를 game_fragment.xml 파일의 리스너 바인딩으로 치환할 것
- game_fragment.xml에 skip_button을 위한 onClick 속성 추가
- binding 표현을 정의하고 GameViewModel의 onSkip() 호출하기
- 이러한 바인딩 표현을 listener binding이라고 함
- game_fragment.xml에 skip_button을 위한 onClick 속성 추가
<Button
android:id="@+id/skip_button"
...
android:onClick="@{() -> gameViewModel.onSkip()}"
... />
- 마찬가지로 correct_button의 클릭이벤트로 GameViewModel의 onCorrect() 호출
<Button
android:id="@+id/correct_button"
...
android:onClick="@{() -> gameViewModel.onCorrect()}"
... />
- end_game_button 의 클릭이벤트도 GameViewModel의 onGameFinish()로 연결
<Button
android:id="@+id/end_game_button"
...
android:onClick="@{() -> gameViewModel.onGameFinish()}"
... />
- GameFragment에서, 클릭리스너를 set 하는 코드와 그 클릭리스너가 호출하는 함수 모두 삭제
//제거할 코드
binding.correctButton.setOnClickListener { onCorrect() }
binding.skipButton.setOnClickListener { onSkip() }
binding.endGameButton.setOnClickListener { onEndGame() }
/** Methods for buttons presses **/
private fun onSkip() {
viewModel.onSkip()
}
private fun onCorrect() {
viewModel.onCorrect()
}
private fun onEndGame() {
gameFinished()
}
STEP 3 : Add data binding for the ScoreViewModel
- 이번에는 ScoreViewModel을 관련된 레이아웃 파일인 score_fragment.xml과 연결할 것
- score_fragment.xml에 ScoreViewModel 타입의 바인딩 변수 추가(위와 같은 방법)
<layout ...>
<data>
<variable
name="scoreViewModel"
type="com.example.android.guesstheword.screens.score.ScoreViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
- score_fragment.xml의 play_again_button에 onClick 속성 추가
- listener binding을 정의하고 ScoreViewModel의 onPlayAgain()호출
<Button
android:id="@+id/play_again_button"
...
android:onClick="@{() -> scoreViewModel.onPlayAgain()}"
... />
- ScoreFragment의 onCreateView() 안에서, viewModel 초기화하기
- 그리고 binding variable인 binding.scoreViewModel을 초기화하기
viewModel = ...
binding.scoreViewModel = viewModel
- ScoreFragment에서, playAgainButton을 위한 clicke listener를 정의하는 코드 제거하기
//제거할 코드
binding.playAgainButton.setOnClickListener { viewModel.onPlayAgain() }
- 앱을 실행하면, 이전과 같지만 button view가 ViewModel과 직접 통신하는 상태가 된 것!
- view는 더 이상 ScoreFragment의 button click listener를 이용해 통신하지 않음!
2. Add LiveData to data binding
- Data binding은 ViewModel objects와 함께 사용되는 LiveData로 동작함
- 우리는 지금 data binding을 ViewModel object에 추가했으므로, LiveData를 통합할 준비가 됨
- 앱이 UI에게 data가 변경되었음을 알리기 위한 data-binding source로 LiveData를 사용하도록 변경할 것! LiveData observer method를 사용하지 않고!
STEP 1 : Add word LiveData to the game_fragment.xml file
- 지금의 word text view를 ViewModel의 LiveData object에 직접적으로 바인딩할 것
- game_fragment.xml의 word_text 텍스트뷰에 android:text 속성 추가
- 그걸 LiveData object로 설정 : GameViewModel의 word
- binding 변수인 gameViewModel 사용
<TextView
android:id="@+id/word_text"
...
android:text="@{gameViewModel.word}"
... />
- word.value를 사용하지 않음! LiveData object를 그대로 사용하면 됨
- LiveData object는 word의 현재 값을 표시함
- word가 null 이라면, LiveData object는 빈 문자열을 표시함
- GameFragment의 onCreateView()에서, gameViewModel을 초기화한 후, fragment를 binding변수의 lifecycle owner로서 set하기
- 위의 LiveData object의 scope를 설정함 : object가 game_fragment.xml 레이아웃의 view를 자동으로 업데이트할 수 있도록 설정함
binding.gameViewModel = ...
// Specify the fragment view as the lifecycle owner of the binding.
// This is used so that the binding can observe LiveData updates
binding.lifecycleOwner = viewLifecycleOwner
- GameFragment에서 LiveData word의 옵저버 삭제하기
//제거할 코드
/** Setting up LiveData observation relationship **/
viewModel.word.observe(viewLifecycleOwner, Observer { newWord ->
binding.wordText.text = newWord
})
- 그럼 현재 word는 UI 컨트롤러의 observer method 없이 업데이트됨!
STEP 2 : Add score LiveData to the score_fragment.xml file
- LiveData인 score를 score fragment의 score 텍스트뷰에 연결
- score_fragment.xml의 score text view에 android:text 속성 추가
- scoreViewModel.score를 그 text 속성에 할당
- score는 정수이므로, String.valueOf() 를 이용해 문자열로 변환
<TextView
android:id="@+id/score_text"
...
android:text="@{String.valueOf(scoreViewModel.score)}"
... />
- ScoreFragment에서, scoreViewModel을 초기화한 후, current activity를 binding 변수의 lifecycle owner로 설정
binding.scoreViewModel = ...
// Specify the fragment view as the lifecycle owner of the binding.
// This is used so that the binding can observe LiveData updates
binding.lifecycleOwner = viewLifecycleOwner
- ScoreFragment에서 score object를 위한 옵저버 삭제
//제거할 코드
// Add observer for score
viewModel.score.observe(viewLifecycleOwner, Observer { newScore ->
binding.scoreText.text = newScore.toString()
})
- 이제 score fragment 내의 score는 scoreFragment 내의 옵저버 없이 자동으로 표시됨
STEP 3 : Add string formatting with data binding
- layout에 data binding으로 string formatting을 추가할 수 있음
- 현재 word의 format을 큰따옴표가 둘러싸는 방식으로 설정할 것
- 또한 Current Score를 앞에 붙여서 나타나도록 설정할 것

- string.xml 파일에 다음의 문자열 추가
- word와 score 텍스트뷰를 format 하는 데 사용될 것
- %s 와 %d 는 현재 단어와 점수를 위한 placeholders
<string name="quote_format">\"%s\"</string>
<string name="score_format">Current Score: %d</string>
- game_fragment.xml에서, word_text 텍스트뷰의 text 속성을 quote_format string resource를 사용하도록 업데이트
- 이 formatting string의 argument로 gameViewModel.word 전달하기
<TextView
android:id="@+id/word_text"
...
android:text="@{@string/quote_format(gameViewModel.word)}"
... />
- word_text처럼 score text view도 포맷하기
- game_fragment.xml에서 score_text 텍스트뷰의 text 속성에 추가
- score_format 문자열 리소스를 사용하여 한 개의 숫자 argument를 전달 : LiveData object인 score를 formatting string의 argument로 전달
<TextView
android:id="@+id/score_text"
...
android:text="@{@string/score_format(gameViewModel.score)}"
... />
- GameFragment 클래스의 onCreateView() 안에서 score observer 코드 삭제
//제거할 코드
viewModel.score.observe(viewLifecycleOwner, Observer { newScore ->
binding.scoreText.text = newScore.toString()
})

'Android > Android Kotlin 기초 실습 정리' 카테고리의 다른 글
Create a RoomDB (0) | 2022.10.27 |
---|---|
LiveData transformations (0) | 2022.10.27 |
LiveData 및 LiveData Observer (0) | 2022.10.27 |
ViewModel : ViewModelFactory (0) | 2022.10.26 |
LifecycleObserver, onSaveInstanceState() (0) | 2022.10.26 |
Comments