티스토리 뷰

ViewModel의 필요성

안드로이드에서 Activity와 fragment - UI 컨트롤러의 목적

기본적으로 UI 데이터를 표시하거나, 사용자 작업에 반응하거나, 권한 요청과 같은 운영체제 커뮤니케이션을 처리하는 것이다.

 

이러한 UI 컨트롤러에 데이터 관련 책임을 요구하면

 

  • 클래스가 팽창되어 다른 클래스로 작업이 위임되지 않고, 단일 클래스가 혼자서 앱의 작업을 모두 처리하려고 할 수 있다.
  • 이렇게 되면 테스트가 훨씬 더 어려워진다.

-> 앱의 데이터 또는 데이터에 관한 모든 의사 결정 로직은 UI 컨트롤러 클래스에 포함해서는 안된다.

 

 

 

데이터에 관한 의사 결정 로직을 ViewModel에 추가해야 한다.

 

ViewModel 클래스는

  • lifecycle을 고려하여 UI 관련 데이터를 저장하고 관리하도록 설계되었다.
  • 이를 사용하면 화면 회전과 같이 구성 변경 시에도 데이터를 유지할 수 있다.

 

뷰모델을 사용하면 화면 회전과 같은 화면 변경 시 데이터가 그대로 유지된다.

 


ViewModel이란?

뷰모델은 뷰에 표시되는 앱 데이터의 모델이다.

  • 뷰모델은 안드로이드 프레임워크에서 액티비티나 프래그먼트가 소멸되고 다시 생성될 때 폐기되지 않는 앱 관련 데이터를 저장한다.
  • 액티비티/프래그먼트와 다른 생명주기를 가지는데 아래 그림을 통해 확인 가능하다.
  • 즉 finish 메서드가 호출되거나 사용자가 직접 뒤로가기 버튼을 눌러 액티비티를 종료해야 onCleared 메서드를 통해 뷰모델은 비로소 소멸이 된다.
따라서 뷰모델 객체는 구성이 변경되는 동안 자동으로 유지되어 보유하고 있는 데이터가 다음 Activity 또는 Fragment 인스턴스에 즉시 사용될 수 있다.

 

뷰모델은 Activity에서는 Activity가 끝날 때까지 Fragment에서는 Fragment가 분리될 때 까지 메모리에 남아 있다.

 

ViewModel의 수명주기

 


정리하면

fragment/activity(UI 컨트롤러)의 책임 ViewModel의 책임
활동 및 프래그먼트는 뷰와 데이터를 화면에 그리고 사용자 이벤트에 응답한다. ViewModel은 UI에 필요한 모든 데이터를 보유하고 처리한다. 뷰 계층 구조(예: 뷰 결합 객체)에 액세스하거나 활동 또는 프래그먼트의 참조를 보유해서는 안된다.

 

 

 


ViewModel을 직접 사용해보자.

1. dependencies 추가

    // ViewModel과 LiveData 사용을 위해 추가
    def lifecycle_version = "2.5.0-alpha02"

    // ViewModel
    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version")

    // LiveData
    implementation("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version")

2. ViewModel 구현

ViewModel 객체는 구성이 변경되는 동안 자동으로 보관되므로, 이러한 객체가 보유한 데이터는 다음 활동 또는 프래그먼트 인스턴스에서 즉시 사용할 수 있다.

  • 예를 들어 room에 있는 데이터를 앱에서 리사이클러 뷰에 표시해야 한다면
    _myToDoSet 변수를 생성해두고 뷰모델 생성 시 init 함수를 통해 room의 데이터를 _myToDoSet에 불러올 수 있다.
    사용자 목록을 확보하여 활동이나 프래그먼트 대신 ViewModel에 보관하도록 책임을 할당해야 한다.
package com.gdsc.todo.ui

import android.app.Application
import android.util.Log
import androidx.lifecycle.AndroidViewModel
import com.gdsc.todo.model.ToDoRepository
import com.gdsc.todo.model.entity.MyToDoList

// UI에 데이터를 제공하는 역할을 한다.
class ToDoViewModel(application: Application) : AndroidViewModel(application) {

    // 캡슐화: 외부에서 필드로의 직접 접근을 제한 backing property
    // 내부에서는 변경 가능
    private var _myToDoSet = ArrayList<MyToDoList>()

    // 외부에서는 읽기만 가능
    val myToDoSet: List<MyToDoList>
        get() = _myToDoSet

    private val repository: ToDoRepository by lazy{
        ToDoRepository(application)
    }

    // 뷰모델 생성 시 init 함수를 통해 _myToDoSet으로 room에 있는 데이터를 불러온다.
    init {
        Log.d(TAG, "init")
        getAll()
    }

    fun getAll(){
        Thread{
            _myToDoSet = repository.getAllToDo() as ArrayList<MyToDoList>
        }.start()
        Thread.sleep(TIME)
    }

    companion object{
        const val TAG = "ToDoViewModel"
        const val TIME: Long = 1000
    }
}

 


3. Activity에서 ViewModel 접근하기

  • 뷰모델 인스턴스는 ViewModelProvider를 통해 만든다.
private lateinit var viewModel: ToDoViewModel
viewModel = ViewModelProvider(this , ViewModelProvider.AndroidViewModelFactory(getApplication())).get(
            ToDoViewModel::class.java)

 

  • 원하는 곳에서 뷰모델을 사용한다.
toDoAdapter = ToDoAdapter(viewModel.myToDoSet, viewModel)
val isNotEmpty = viewModel.checkEmpty()
        when(isNotEmpty){
            true -> {
                viewModel.addButtonClick()
                startToDoActivity()
            } else -> {
                showEmptyToDoError()
            }
        }
    }

 


+ AndroidViewModel과 ViewModel 차이

 

  ViewModel AndroidViewModel
Application의 유무 X O
-> Application을 상속받아
메모리 누수가 발생 가능

 

  • Application의 유무에서 차이가 있다.
  • Context 관련 작업을 꼭 해야 한다면 AndroidViewModel을 사용하면 되지만, 굳이 쓸 필요는 없다.
  • 일반적으로는 ViewModel을 쓰도록 권장한다.

 


참고자료

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

 

ViewModel 개요  |  Android 개발자  |  Android Developers

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

developer.android.com