Android Kotlin Fundamentals 05.3 #1 Data binding with ViewModel and LiveData - Add ViewModel data binding

이전 코드랩에서 GuessTheWord App을 구현 할 때, view에 접근하기 위해 data binding을 type-safe 방식으로 사용 하였습니다. 그러나 data binding의 진정한 힘은 view 객체에 데이터를 직접 바인딩 하는 것 입니다.

 

이전에는 아래와 같이 binding 객체의 scoreText에 접근해서 text를 셋팅 해 주었습니다.

       viewModel.score.observe(this, Observer { newScore ->
            binding.scoreText.text = newScore.toString();
        })

 

Current app architecture

 

현재 앱의 구조에 대해 살펴 보겠습니다. 앱에서 View들은 XML문서에 정의되어 있습니다. 그리고 view에 나타내기 위한 data들은 ViewModel에서 관리합니다. 각 view와 연관된 Viewmodel 사이에 UI Controller가있습니다. 

 

 

예를 들어서

 

  • Got it 버튼은 game_fragment.xml안에 Button View로 정의되어 있습니다.
  • 사용자가 Got It 버튼을 눌렀을 때, GameFragment안의 클릭 리스너는 연관된 ViewModel의 클릭 리스너를 호출 합니다.
  • 그러면 GameViewModel에서 score가 업데이트 됩니다.
//GameFragment의 클릭 리스너
binding.correctButton.setOnClickListener { onCorrect() }
//GameViewModel의 클릭 리스너
fun onCorrect() {
        if (!wordList.isEmpty()) {
            score.value?.plus(1)
        }
        nextWord()
    }

 

Button과 GameViewModel은 직접적으로 커뮤니케이션 하지 않습니다. Button과 ViewModel 커뮤니케이션 하기 위해서는 현재 GameFragment 안에 있는 클릭 리스너가 필요 합니다.

 

 

ViewModel passed into the data binding

 

GameFragment와 같은 UI controller를 거치지 않고, View와 ViewModel이 직접적으로 커뮤니케이션 할 수 있다면 앱은 조금 더 심플해 질것 입니다.

 

VIewModel 객체는 모든 UI 관련 데이터를 가지고 있습니다. ViewModel 객체를 databinding으로 전달하면 view들과 ViewModel 객체간 커뮤니케이션 중 일부를 자동화 할 수 있습니다.

 

이번 태스크에서는 GameViewModel과 ScoreViewModle을 관련 레이아웃들과 연결 시켜 볼 것입니다. click이벤트를 다루는 리스너들도 데이터 바인딩 설정을 할 것입니다.

 

Step 1: Add data binding for the GameViewModel


이번 단계에서 GameViewModel을 game_fragment.xml과 연결 시켜 보겠습니다.

 

1. game_fragment.xml에서 GameViewModel 타입의 data-binding 변수를 추가 해 주세요. 만약 에러가 난다면 AndroidStudio에서 프로젝트를 clean하고 rebuild 해주세요.

 

<layout ...>

   <data>

       <variable
           name="gameViewModel"
           type="com.example.android.guesstheword.screens.game.GameViewModel" />
   </data>
  
   <androidx.constraintlayout...

 

2. GameFragment에서 GameViewModel을 databinding으로 전달 합니다. viewModel을 바로 위에서 선언한 binding.gameViewModel에 할당해 줘야 합니다. 해당 코드는 onCreateView()안 viewModel이 초기화 된 후에 위치하도록 합니다. 만약 에러가 발생하면 clean - rebuild 해주세요.

 

viewModel = ViewModelProviders.of(this).get(GameViewModel::class.java)
// 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 bindings는 onClick(), onZoomIn() 또는 onZoomOut과 같은 이벤트가 트리거될 때 실행되는 바인딩 표현식 입니다.

 

Data binding은 리스너를 생성하고, 리스너를 view에 붙힙니다. 그리고 이벤트가 발생 했을 때, 이를 수신하고, 람다 표현식을 실행 시킵니다. Listener binding은 Android Gradle Plugin 버전 2.0 이상 부터 동작 합니다. 조금 더 배우기 위해 아래 url을 참조 하세요.

https://developer.android.com/topic/libraries/data-binding/expressions#listener_bindings

 

레이아웃 및 결합 표현식  |  Android Developers

표현식 언어를 사용하면 뷰에 의해 전달된 이벤트를 처리하는 표현식을 작성할 수 있습니다. 데이터 결합 라이브러리는 레이아웃의 뷰를 데이터 객체와 결합하는 데 필요한 클래스를 자동으로 생성합니다. 데이터 결합 레이아웃 파일은 약간 차이가 있으며 layout 루트 태그로 시작하고 data 요소 및 view 루트 요소가 뒤따릅니다. 이 뷰 요소는 결합되지 않은 레이아웃 파일에 루트가 있는 요소입니다. 다음 코드는 샘플 레이아웃 파일을 보여줍니다. data 내의

developer.android.com

 

이번 스텝에서 GameFragment에 정의된 click 리스너들을 game_fragment의 listener binding으로 교체 해 보겠습니다.

 

1. game_fragment.xml에서 skip_button의 onClick속성에 binding 표현식과 GameViewModel의 onSkip()을 호출하는 코드를 추가 해주세요. 이와 같은 binding 표현식을 listener binding이라고 부릅니다.

<Button
   android:id="@+id/skip_button"
   ...
   android:onClick="@{() -> gameViewModel.onSkip()}"
   ... />

 

2. onCorrect() 또한 correct_buttion에서 listener binding 시켜 주세요.

<Button
   android:id="@+id/correct_button"
   ...
   android:onClick="@{() -> gameViewModel.onCorrect()}"
   ... />

 

3. onGameFinish() 또한 마찬가지 입니다. end_game_buttion의 onClick속성에서 listener binding 시켜 주세요.

<Button
   android:id="@+id/end_game_button"
   ...
   android:onClick="@{() -> gameViewModel.onGameFinish()}"
   ... />

 

Step3 : Add data binding for the ScoreViewModel


이번 단계에서는 ScoreViewModel을 연관된 layout file인 score_fragment.xml과 연결 시켜 보겠습니다.

 

1. score_fragment.xml에서 ScoreViewModel 타입의 데이터 바인딩 변수를 추가해 주세요. 이번 스텝은 이전에 GameViewModel을 바인딩 시켰던 작업과 유사 합니다.

 

<layout ...>
   <data>
       <variable
           name="scoreViewModel"
           type="com.example.android.guesstheword.screens.score.ScoreViewModel" />
   </data>
   <androidx.constraintlayout.widget.ConstraintLayout

 

2. score_fragment.xml에서 play_again_buttion의 onClick 속성에 listener binding을 정의하고 ScoreViewModel의 onPlayAgain()이 호출 되도록 해 줍니다.

 

<Button
   android:id="@+id/play_again_button"
   ...
   android:onClick="@{() -> scoreViewModel.onPlayAgain()}"
   ... />

 

3. In ScoreFragment의 onCreateView()안에서 viewModel을 초기화 해 주고, 그런 다음 binding.scoreViewModel 변수에 해당 ViewModel을 할당 해 줍니다.

 

viewModel = ViewModelProviders.of(this).get(ScoreViewModel::class.java)
binding.scoreViewModel = viewModel

 

4. ScoreFragment에서 PlayAgainButtion에 click listener를 셋팅한 부분을 제거해 줍니다. 만약 에러가 발생 한다면 프로젝트를 clean - rebuild 해 주세요.

 

아래 부분을 제거 하시면 됩니다.

binding.playAgainButton.setOnClickListener {  viewModel.onPlayAgain()  }

 

5. 앱은 이전과 같이 동작하지만, 현재 우리의 앱은 view와 viewModel이 직접적으로 통신하고 있습니다. view는 더이상 ui controller을 통해 viewModel과 통신하지 않습니다.

댓글



Designed by JB FACTORY