깨알 개념/Android

[Android] ViewModel - 기초

interfacer_han 2024. 1. 13. 16:22

#1 View Model의 필요성

#1-1 예제

버튼을 누르면 TextView의 text가 1씩 증가하는 예시 앱이다. MainActivity.kt 코드는 다음과 같다.
 

// package com.example.viewmodelbasics

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import com.example.viewmodelbasics.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    private var count = 0
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        binding.countText.text = count.toString()
        binding.countButton.setOnClickListener {
            count++
            binding.countText.text = count.toString()
        }
    }
}

 

#1-2 문제

이 때, 화면을 회전시켜보면 count 수치가 초기 수치인 0이 되어버린다. 안드로이드 시스템은, 화면 회전과 같은 환경의 변화(configuration change)가 일어나면 Activity를 파괴(destory)하고 재생성(recreate)한다. 그래서 MainActivity의 count 변수 또한 파괴되고 재생성된 것이다.
 
예를 들어 REST API로부터 데이터를 받아들여 View에 표시하는 어떤 앱을 가정해보자. 화면 회전 등의 이유로 Activity가 파괴된 후 재생성되면 같은 Data를 REST API에게 요청하며 자원 낭비를 발생시킨다. 사용자 입장에서도 멀쩡히 다운로드했던 Data가 눈 앞에서 날아가고 다시 다운로드하는 걸 기다려야하니 사용자 경험도 저하된다. 따라서 탁월한 안드로이드 개발자가 되기 위해선 이러한 configuration change를 고려한 설계를 할 줄 알아야 한다.
 

#1-3 ViewModel

 

ViewModel 개요  |  Android 개발자  |  Android Developers

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

developer.android.com

안드로이드 제트팩(JetPack)의 View Model Architecture Component는 이러한 문제를 해결할 수 있는 좋은 수단이다. 소프트웨어 개발에서  View는 화면에 보이는 UI 부분, Model은 데이터를 처리하는 부분을 의미한다. View Model이란, 말 그대로 View에 관련된 Model로 위의 구글 공식 문서에 따르면, "UI에 표시하는 데이터를 보관하고 관리"하는 Model로서 도입되었다. View Model은 View가 아무리 파괴와 재생성을 반복해도, 그 영향을 받지 않는 데이터 보관소다.
 

#1-4 ViewModel의 생명주기

https://developer.android.com/topic/libraries/architecture/viewmodel?hl=ko

Activity는 생명주기는 위 그림과 같이 굉장히 세부적으로 나뉜다. 아까 예시 앱의 화면을 회전시켰을 때 호출되었던 Activity 클래스의 onDestroy()와 onCreate() 메소드도 보인다. 반면, 옆에 있는 View Model의 생명주기는 굉장히 단순하다. 생성 후 상태변화 없이 쭉 살아있다가, 맨 마지막에 onCleared()되면 그제서야 비로소 생명주기가 끝난다. onCleared()가 되는 시점은, 뷰 모델이 더 이상 필요하지 않은 시점으로 앱이 백그라운드에서 돌아가는 상황에서 안드로이드 시스템이 메모리를 확보하기 위해 해당 앱을 종료시킬 때를 예로 들 수 있다. 혹은 사용자가 명시적으로 앱을 종료시키거나, 뒤로가기 버튼 등으로 이용해 Activity에서 벗어날 때(예를 들어, 카카오톡 쇼핑하기 창에서 뒤로가기 버튼으로 카카오톡 메인 화면으로 돌아왔을 때)다.
 
이제부터 이 View Model을 프로젝트에 접목시키는 방법을 알아본다.
 

#2 View Model 사용하기

#2-1 build.gradle.kts (Module: app)

plugins {
    ...
}

android {
    ...
}

dependencies {
    val lifecycle_version = "2.5.1"
    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version") // ViewModel

    ...
}

먼저, ViewModel 클래스를 불러오기 위한 라이브러리를 추가한다. 모듈 수준 gradle에 위와 같은 구문을 추가한다. 해당 구문은 여기에서 추린 구문이다.
 

#2-2 MainActivityViewModel.kt

// package com.example.viewmodelbasics

import androidx.lifecycle.ViewModel

class MainActivityViewModel : ViewModel() {
    private var count = 0

    fun getCurrentCount(): Int {
        return count
    }

    fun getUpdatedCount(): Int {
        return ++count
    }
}

이제 MainActivity에서 View Model로 쓸 MainActivityViewModel를 만든다. 이 클래스는 ViewModel 클래스의 자식이므로, ViewModel 클래스를 extends한다.
 
MainActivity에 있던 count 변수를 MainActivityViewModel로 옮기고 해당 변수를 조작할 수 있는 메소드도 만든다.
 

#2-3 MainActivity.kt 수정

// package com.example.viewmodelbasics

...

import androidx.lifecycle.ViewModelProvider

...

class MainActivity : AppCompatActivity() {
    ...
    private lateinit var viewModel: MainActivityViewModel
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        
        viewModel = ViewModelProvider(this).get(MainActivityViewModel::class.java)
        binding.countText.text = viewModel.getCurrentCount().toString()
        binding.countButton.setOnClickListener {
            binding.countText.text = viewModel.getUpdatedCount().toString()
        }
    }
}

MainActivityViewModel(View Model)은 자체적인 생성자가 있음에도, MainActivity에선 그 생성자를 통해 인스턴스(객체)를 만들지 않는다. 대신, ViewModelProvider의 생성자를 이용해 인스턴스를 만든다. 왜냐하면 일종의 싱글톤(생성자를 통해서 여러 번 호출이 되더라도 인스턴스를 새로 생성하지 않고 최초 호출 시에 만들어두었던 인스턴스를 재활용) 개념으로 ViewModel을 관리해야만하기 때문이다.왜 그래야만할까?  MainActivity는 계속해서 파괴되고 재생성된다고 했다. 그렇다면, MainActivity에서 ViewModel의 인스턴스를 만드는 코드도 계속 실행된다는 얘기다. ViewModel은 단일 인스턴스로 존재해서 사용자에게 일관된 UI를 보여주기 위한 목적을 가지고 있다. 그래서 ViewModel의 인스턴스는 싱글톤 패턴으로 관리되어야 한다. 그런데 그 역할을 ViewModel의 자체 생성자는 수행할 수 없다. 그래서 ViewModelProvider가 대신 수행하는 것이다.
 

#2-4 앱 테스트

Activity는 destroy 및 recreate되지만, ViewModel은 그렇지 않기 때문에 count 변수가 계속 살아있는 모습이다.

#3 요약

Model이 '재료'고 View가 '요리'라면, View Model은 '필요한 재료가 올라가있는 도마'다.

 

#4 완성된 앱

https://github.com/Kanmanemone/android-practice/tree/master/view-model/ViewModelBasics

 

#5 이어지는 글 

 

[Android] ViewModel - 뷰 모델에 인자(Argument) 전달

#1 ViewModelProvider 클래스 분석 #1-1 이전 글의 예제 수정 [Android] View Model - 기초 #1 View Model의 필요성#1-1 예제버튼을 누르면 TextView의 text가 1씩 증가하는 예시 앱이다. MainActivity.kt 코드는 다음과 같다

kenel.tistory.com

ViewModel에 인자(Argument)를 전달하는 방법이다.