ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Android Kotlin Fundamentals - ViewModel(1)
    Android 2021. 5. 29. 13:49

    Android Kotlin Fundamentals: 5.1 ViewModel 을 요약, 정리하였다.

    (자세한 내용은 codeLab 사이트를 참고)

    GameViewModel 구현

    build.gradle(module:app) 파일에 다음 dependencies를 추가한다. (최신 버전은 문서를 참고)

    dependencies {
        //
        implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
        //
        implementation 'androidx.fragment:fragment-ktx:1.3.4'
    }

     ViewModel 클래스를 상속하는 GameViewModel을 정의한다.

    class GameViewModel:ViewModel() {
        override fun onCleared() {
            super.onCleared()
        }
    
        init {
            Log.i(TAG, "GameViewModel created!")
        }
    
        companion object{
            private val TAG = GameViewModel::class.java.simpleName
        }
    }

    ViewModel은 UI 컨트롤러와 연결하기 위해 UI 컨트롤러 내부에 ViewModel에 대한 참조를 만든다.

    해당 UI 컨트롤러 (GameFragment) 내에 GameViewModel의 참조를 생성한다.

     

    화면 회전과 같은 구성 변경이 발생하면 Fragment과 같은 UI 컨트롤러는 다시 생성된다. 그러나 ViewModel 인스턴스는 유지된다.

    • ViewModelProvider를 사용하는 경우

    ViewModelProvider.get() 메서드를 사용하여 ViewModelProvider를 생성하여 ViewModel을 초기화한다.

    GameFragment 클래스에서 ViewModelProvider.get() 메서드를 사용하고 연결된 GameFragment Context 및 GameViewModel 클래스를 전달한다.

    //Factory 필요 없는 경우
    private val viewModel by lazy {
    	ViewModelProvider(this).get(GameViewModel::class.java)
    }
    
    //Factory 필요한 경우
    private val viewModel by lazy {
        ViewModelProvider(viewModelStore, object : ViewModelProvider.Factory {
            override fun <T : ViewModel?> create(modelClass: Class<T>): T {
               return  GameViewModel() as T
            }
        }).get(GameViewModel::class.java)
    }

    viewModelStore에 대해 간략히 설명하자면 Activity와 Framgnet는 ViewModelStoreOwner라는 인터페이스를 구현하고 있고 구현체가 바로 viewModelStore이다. ViewModelStore은 HashMap을 가지고 있어 이를 사용해 키값에는 viewmodel의 class명을 value에는 viewmodel을 저장한다. 이를 통해 우리는 기존에 사용했던 viewmodel을 다시 불러올 수 있으며 공유할 수 있게 해준다.

    • viewModel-ktx를 사용하는 경우
    //fragment의 viewmodel 호출
    private val viewModel by viewModels<GameViewModel>()
    //activity의 viewmodel 호출
    private val viewModel by activityViewModels<GameViewModel>()
     
    //Factory 필요한 경우
    private val viewModel: GameViewModel by viewModels {
     	object : ViewModelProvider.Factory {
        	override fun <T : ViewModel?> create(modelClass: Class<T>): T =
            		GameViewModel() as T
    	}
    }

    GameViewModel 데이터 처리 추가

    ViewModel은 구성 변경 사항을 유지하므로 구성 변경 사항을 유지해야하는 데이터에 적합하다.

    • 화면에 표시할 데이터를 입력하고 해당 데이터를 처리하는 코드를 ViewModel에 입력한다.
    • activities, fragments, 그리고 views는 구성 변경을 유지하지 않으므로 ViewModel에는 activities, fragments, 또는 views에 대한 참조가 포함되어서는 안된다. 

    • ViewModel을 추가하기 전 : 앱이 화면 회전과 같은 configuration이 변경되면 game fragment가 파괴되고 다시 생성되면서 데이터가 손실된다.
    • ViewModel을 추가하고 game fragment의 UI 데이터를 ViewModel로 이동한 후 :  앱의 configuration이 변경되어도 ViewModel이 유지되고 데이터가 유지된다.

     

    1단계 : 데이터 필드 및 데이터 처리를 ViewModel로 이동

    - 데이터 필드와 메서드를 GameFragment에서 GameViewModel로 이동한다.

    - 바인딩 변수인 GameFragmentBinding은 View에 대한 참조를 포함하므로 ViewModel로 이동하지 않음
    이 변수는 레이아웃을 확장하고, 클릭 리스너를 설정하고, 화면에 데이터를 표시하는데 사용된다.

    2단계 : GameFragment의 클릭 핸들러 및 데이터 필드에 대한 참조 업데이트

    private fun onSkip() {
       viewModel.onSkip()
       updateWordText()
       updateScoreText()
    }
    
    private fun onCorrect() {
       viewModel.onCorrect()
       updateScoreText()
       updateWordText()
    }
    
    private fun updateWordText() {
       binding.wordText.text = viewModel.word
    }
    
    private fun updateScoreText() {
       binding.scoreText.text = viewModel.score.toString()
    }

    게임 종료 버튼에 대한 클릭 리스너 구현

    GameFragment에서 gameFinished ()라는 메서드를 추가하여 앱을 Score 화면으로 이동한다 Safe Args를 사용하여 점수를 인수로 전달한다.

    /**
    * Called when the game is finished
    */
    private fun gameFinished() {
       Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show()
       val action = GameFragmentDirections.actionGameToScore()
       action.score = viewModel.score
       NavHostFragment.findNavController(this).navigate(action)
    }

    ViewModelFactory 사용

    factory method pattern을 사용하여 ViewModel 초기화 중에 점수 값을 전달한다.

    class ScoreViewModelFactory(private val finalScore:Int) : ViewModelProvider.Factory {
        override fun <T : ViewModel?> create(modelClass: Class<T>): T {
            if(modelClass.isAssignableFrom(ScoreViewModel::class.java)){
                return ScoreViewModel(finalScore) as T
            }
            throw IllegalAccessException("Unknown ViewModel class")
        }
    }

    ScoreViewModelFactory()에 생성자 매개 변수로 Bundle의 최종 점수를 전달한다.

    viewModelFactory = ScoreViewModelFactory(ScoreFragmentArgs.fromBundle(requireArguments()).score)
    viewModel = ViewModelProvider(this, viewModelFactory).get(ScoreViewModel::class.java)
    binding.scoreText.text = viewModel.score.toString()

     

    'Android' 카테고리의 다른 글

    LiveData  (0) 2021.05.30
    Android Kotlin Fundamentals - ViewModel(2)  (0) 2021.05.30
    ViewModel  (0) 2021.05.28
    Navigation Component(2)  (0) 2021.05.19
    Navigation Component(1)  (0) 2021.05.18
Designed by Tistory.