버그 잡이

[Udacity android with kotlin] 5. Architecture - viewmodel 본문

모던 안드로이드/Udacity Android with kotlin

[Udacity android with kotlin] 5. Architecture - viewmodel

버그잡이 2020. 6. 15. 10:22

 

1. UI & ViewModel 

 

UI vs ViewModel

(LiveData는 다음 시간에 다루기에 일단은 크게 두 분류로 나눈다.)

 

UI

- 자료를 UI에 표현

- 시스템이나 사용자가 보내는 이벤트를 수신

 

ViewModel

- UI에 보여줄 data를 가지고 있다 UI에 전달

- 어떤 data를 보여줄지 판단(데이터에 대한 계산을 여기서 한다)

 

 

(GameFragment의 예로 살펴보자)

 

  • GameFragment는
    • data를 UI에 나타내준다
    • button 클릭된 경우 Fragment 사실을 인식하고 viewmodel에게 알린다.
  • GameViewModel
    • 점수, 단어 목록, 현재 단어 등에 대한 데이터를 가지고 있다.
    • gameFragment에서 신호가 오면 관련된 data를 처리하고 data를 변경한다.(ui를 참조하여 직접 바꾸지는 않는다. ui를 참조하는 것은 fragment에서 한다.)

 

 

 

 

 

2. 왜 이런 번거로운 작업을 하는가? (왜 Viewmodel을 쓰는가?)

 

 

4강 lifycycle을 배우면서 configuration change 문제를 알아봤다. 화면 전환시, 홈스크린을 눌러 백그라운드로 갈 경우 데이터가 초기화 되는 문제이다. ViewModel은 이러한 문제를 해결해준다.

viewmodel lifecycle

위 생명주기를 정확히 이해는 못 했지만 핵심은 configuration change시 viewmodel은 소멸되지 않기 떄문에 data를 가지고 있다 fragment가 재생성될때 data를 그대로 보여줄 수 있다는 것이다.

(물론, 백 버튼을 눌러 나간 경우는 viewmodel이 소멸된다)

 

 

 

 

UI, VIewModel에는 각각 어떤 것이 들어가야 하는가?

 

 

정답은 없다. 기본 원칙을 가지고 개발자의 판단에 맡긴다.

 

판단의 기준이 될 원칙들..

 

그렇다면 예제로 만들 스피드 퀴즈 앱을 통해서 한번 구성해보자.

 

스피드 퀴즈앱은 '몸으로 말해요'를 앱으로 구현한 것이다. 한 사람이 몸으로 단어를 설명하고 이를 맞추면 다음 단어로 넘기고 점수가 올라가고 문제를 다 맞추거나 정해진 시간이 다 되면 종료되며 최종 점수가 뜨는 구조의 앱이다.

 

(아래 문제를 풀어보자)

"다음 중 ViewModel에 들어갈 항목은?"

체크된 항목이 정답이다.

 

text나 score를 띄우는 행위는 ui에 대한 참조가 필요하기 때문에 -> UI

리스트를 갱신, 다음 단어로 뭘 선택할지 decision-making하는 것은 -> ViewModel

decision-making을 위한 기반 data도 -> ViewModel

 

 

 

(코드로 살펴보자)

 

GameFragment.kt

class GameFragment : Fragment() {

    private  lateinit var viewModel : GameViewModel


    private lateinit var binding: GameFragmentBinding

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {

        // Inflate view and obtain an instance of the binding class
        binding = DataBindingUtil.inflate(
                inflater,
                R.layout.game_fragment,
                container,
                false
        )

        //뷰모델은 viewModelProvider를 통해서 생성해준다.
        viewModel = ViewModelProviders.of(this).get(GameViewModel::class.java)

        
        //버튼 클릭시 viewmodel에 신호를 보내고 viewmodel에서 처리된 데이터를 화면에 나타낸다.
        binding.correctButton.setOnClickListener {
            viewModel.onCorrect()
            updateScoreText()
            updateWordText()
        }
        binding.skipButton.setOnClickListener {
            viewModel.onSkip()
            updateScoreText()
            updateWordText()
        }
        
        updateScoreText()
        updateWordText()
        
        return binding.root

    }
    
    
    //업데이트된 단어와 점수를 화면에 표시하는 메서드이다.
    //뷰를 참조하기 때문에 Fragment에 선언해주어야한다.
    private fun updateWordText() {
        binding.wordText.text = viewModel.word

    }

    private fun updateScoreText() {
        binding.scoreText.text = viewModel.score.toString()
    }

    
    //게임을 끝내는 메서드 (이 부분은 다음 강좌에서 자세히 다루니 지금은 넘어가자)
    private fun gameFinished() {
        val action = GameFragmentDirections.actionGameToScore(viewModel.score)
        findNavController(this).navigate(action)
    }
}

 

GameViewModel.kt

class GameViewModel : ViewModel() {

    // 현재 단어와 점수 data
    var word = ""
    var score = 0

    // 단어 목록(여기서 하나를 선택해 현재 단어로 정함)
    private lateinit var wordList: MutableList<String>


    //처음 생성될때 단어 리스트를 섞어주고 단어를 하나 선태함과 동시에 리스트에서 지워준다
    init{
        Log.i("GameViewModel", "GameViewModel created!")
        resetList()
        nextWord()
    }

    /**
     * Resets the list of words and randomizes the order
     */
    private fun resetList() {
        wordList = mutableListOf(
                "queen",
                "hospital",
                "basketball",
                "cat",
                "change",
                "snail",
                "soup",
                "calendar",
                "sad",
                "desk",
                "guitar",
                "home",
                "railway",
                "zebra",
                "jelly",
                "car",
                "crow",
                "trade",
                "bag",
                "roll",
                "bubble"
        )
        wordList.shuffle()
    }

    /**
     * Moves to the next word in the list
     */
    private fun nextWord() {
        //Select and remove a word from the list
        if (wordList.isEmpty()) {
            //gameFinished()
        } else {
            word = wordList.removeAt(0)
        }

    }

    /** 버튼 클릭에 대한 데이터 처리 **/
    fun onSkip() {
        score--
        nextWord()
    }

    fun onCorrect() {
        score++
        nextWord()
    }


    override fun onCleared() {
        super.onCleared()
        Log.i("GameViewModel", "GameViewModel destroyed!")
    }
}

 

 

*ViewModel 사용시 주의점

 

ViewModel 사용시 activity, fragment, view에 대한 컨택스트를 저장해서는 안된다. configuration change 뷰가 재생성될때 viewmodel은 그 수명주기가 다르기 때문에 메모리 릭을 발생시킬 수 있다. viewmodel에서 context가 필요한 경우는 application 컨택스트를 저장해야하며 이런 용도로 AndroidViewModel 클래스를 제공한다.

 

 

-------------------------------------추가 수정(20.6.15)----------------------------------------------

 

 

*ViewModelProvider로 viewmodel을 생성하는 이유

viewmodel은 그 목적상 activity/fragment와 생명주기를 달리 해야한다.

그렇기 때문에 new로 생성해주는 것이 아니라 provider의 개념으로 생성해준것이다.

(내부 코드를 들어가보면 ViewModelStore 라는 곳에 hashmap 형태로 저장되어 여기서 viewmodel을 등록하고 가져올 수 있는 형태이다.)

 

 

 

 

*Viewmodel의 또 다른 장점 : fragment강 data 공유

 

viewmodel의 장점으로는 configuration change에 대응할 수 있다는 점을 꼽았다. 구글측에서 viewmodel 클래스를 더욱 잘 활용할 수 있도록 여러 기능들을 추가해주고 있는데 그 중 하나가 "Fragment간 data 공유"이다.

fragment는 activity 위에 만들어지는데 activity의 viewmodel을 공유함으로써 fragment간 데이터 공유가 가능하게 된다.

 

(구체적인 사용 방법은 아래 글을 참고)

developer.android.com/topic/libraries/architecture/viewmodel#java

 

ViewModel 개요  |  Android 개발자  |  Android Developers

ViewModel을 사용하면 수명 주기를 인식하는 방식으로 UI 데이터를 관리할 수 있습니다.

developer.android.com

*여기서 주의사항은 fragment에서 viewmodel을 만들때 Activity의 viewmodel이기 때문에 requireActivity()를 넣어주어 activity의 viewmodelstore를 전달해야한다.

 

 

 

 

*참고 자료

https://medium.com/@jungil.han/%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-viewmodel-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-2e4d136d28d2

반응형
Comments