Android/Jetpack

[안드로이드] ViewModel - AAC (Android Architecture Components)

_hong 2023. 3. 21. 13:10

ViewModel

ViewModel 클래스는 비즈니스 로직 또는 화면 상태 홀더이다.

 

 

기존의 문제점들과 사용하는 이유

 

단순한 적은 데이터들은 Activity의 onSavedState() 메소드를 통해 번들 형태로 저장할 수 있다.

하지만 더 큰 데이터들은 해당 방법이 부적합하다.

 

또 뷰에서 비동기 호출을 하게 되면 액티비티의 생명주기에 따라 리소스가 낭비되는 경우가 있다. 

 

ViewModel의 이점

  • UI 상태를 유지할 수 있습니다.
  • 비즈니스 로직에 대한 액세스 권한을 제공합니다. 

 

ViewModel의 생명주기

 

ViewModel의 생명주기는 Activity의 생명주기보다 길다. 

ViewModelStoreOwner의 생명주기를 따른다. 그렇기 때문에 액티비티 화면이 회전되어도 ViewModel의 데이터는 유지된다.

 

 

*일반적으로 ViewModel은 뷰, Lifecycle, 또는 활동 컨텍스트 참조를 보유할 수 있는 클래스를 참조해서는 안 됩니다. ViewModel 수명 주기가 UI 수명 주기보다 크므로 ViewModel에 수명 주기 관련 API를 보유하면 메모리 누수가 발생할 수 있습니다.

 

뷰모델의 onCleared() Viewmodel 이상 호출되지 않는 순간에 호출된다. (내용 추가하기)

 

VIewModelStoreOwner, ViewModelStore

interface ViewModelStoreOwner {

    /**
     * The owned [ViewModelStore]
     */
    val viewModelStore: ViewModelStore
}

 

ViewModelStoreOwner를 통해 viewModelStore를 얻는다.

ViewModelStoreOwner 인터페이스를 구현하는 객체는 ViewModelStore를 관리한다.

어떤 owner(ComponentActivity,Fragment 등)를 통해 생성하느냐에 따라 ViewModel의 생명주기가 결정됨.

 

open class ViewModelStore {

    private val map = mutableMapOf<String, ViewModel>()

    /**
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    fun put(key: String, viewModel: ViewModel) {
        val oldViewModel = map.put(key, viewModel)
        oldViewModel?.onCleared()
    }

    /**
     * Returns the `ViewModel` mapped to the given `key` or null if none exists.
     */
    /**
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    operator fun get(key: String): ViewModel? {
        return map[key]
    }

    /**
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    fun keys(): Set<String> {
        return HashSet(map.keys)
    }

    /**
     * Clears internal storage and notifies `ViewModel`s that they are no longer used.
     */
    fun clear() {
        for (vm in map.values) {
            vm.clear()
        }
        map.clear()
    }
}

 

내부적으로 해쉬맵을 통해 ViewModel을 관리한다.

 

 

ViewModelProvider

 

ViewModel을 인스턴스화할 때 필요한 클래스, ViewModelStoreOwner 인터페이스 구현 객체를 전달한다. 

그러면 ViewModel의 생명주기가 VIewModelStoreOwner의 생명주기로 결정된다.

 

ComponentActivity, Fragment가 ViewModelStoreOwner 인터페이스를 구현한다.

ComponentActivity의 서브 클래스인 AppCompatActivity를 사용하고 있다면 별도로 ViewModelStoreOwner를 구현할 필요는 없다.

 @Suppress("UNCHECKED_CAST")
    @MainThread
    public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
        val viewModel = store[key]
        if (modelClass.isInstance(viewModel)) {
            (factory as? OnRequeryFactory)?.onRequery(viewModel!!)
            return viewModel as T
        } else {
            @Suppress("ControlFlowWithEmptyBody")
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }
        val extras = MutableCreationExtras(defaultCreationExtras)
        extras[VIEW_MODEL_KEY] = key
        // AGP has some desugaring issues associated with compileOnly dependencies so we need to
        // fall back to the other create method to keep from crashing.
        return try {
            factory.create(modelClass, extras)
        } catch (e: AbstractMethodError) {
            factory.create(modelClass)
        }.also { store.put(key, it) }
    }

ViewModelProvider를 통해 ViewModel을 얻는다.  

아래의 과정과 같다.

 

  1. ViewModelProvider를 통해 ViewModel 인스턴스를 요청한다.
  2. ViewModelProvider 내부에서는 ViewModelStoreOwner를 참조하여 ViewModelStore를 가져온다.
  3. ViewModelStore에게 이미 생성된(저장된) ViewModel 인스턴스를 요청한다.
  4. 만약 ViewModelStore가 적합한 ViewModel 인스턴스를 가지고 있지 않다면,  Factory를 통해 ViewModel인스턴스를 생성한다.
  5. 생성한 ViewModel 인스턴스를 ViewModeStore에 저장하고 만들어진 ViewModel 인스턴스를 클라이언트에게 반환한다.
  6. 똑같은 ViewModel 인스턴스 요청이 들어온다면, 1~3번의 과정을 반복하게 된다.

ViewModelStore가 유지되는 이유는 ViewModelProvider가 getViewModelStore로 store를 얻을 때 기존에 만들어진 store를 재사용한다. 재사용을 하기 위해서 액티비티가 종료되기 전에 일부 변수(ViewModelStore)들을 저장하고 불러오기 때문이다.

 

 

ViewModel을 이용한 Fragment 간의 Data 공유

 

Fragment들과 ViewModel을 공유해 Fragment - Activity 간 결합을 느슨하게 할 수 있다. 

Providerder의 매개변수로 공유된 ViewModel은 Activity를 전달하면 해당 ViewModel은 Activity의 생명주기를 따른다. 

 

 

 

Reference.

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

2. https://taetae98.tistory.com/m/10

3. https://charlezz.medium.com/viewmodel이란-무엇인가-viewmodel-초보를-위한-가이드-e1be5dc1ac18