Android ViewModel 주요 클래스 및 인터페이스

홍성덕·2024년 8월 13일

Android ViewModel

목록 보기
1/4

Android의 ViewModel 클래스는 UI와 관련된 데이터들을 관리하는 역할을 하는 클래스이다. UI에 상태(state)를 노출하고 관련 비즈니스 로직을 캡슐화한다. 여기서 비즈니스 로직은 UI 레이어의 비즈니스 로직이다.

ViewModel 클래스의 주요 목적은 화면의 데이터를 유지하는 것이다. 안드로이드에서는 화면 회전 같은 Configuration Change가 발생하면 화면의 데이터가 파괴된다.

그래서 InstanceState 저장 메커니즘(saving instance state mechanism)을 사용하여 데이터를 저장하고 복구하는 과정을 거쳐야 데이터를 유지할 수 있다. (예를 들어 액티비티에서는 onSaveInstanceState())
하지만 InstanstState 저장 메커니즘은 대량의 데이터 및 복잡한 데이터 구조를 저장하기에 부적합하다.

ViewModel은 이러한 Configuration Change에도 데이터를 유지하기 때문에 데이터소스에서 데이터를 새로 가져올 필요가 없다.


이제부터는 주요 클래스와 인터페이스를 살펴보겠다.

ViewModelStore

ViewModelStore는 ViewModel을 저장하는 클래스이다. ViewModelStore 인스턴스는 configuration change 중에도 유지되어야 한다.

만약 해당 ViewModelStore의 owner(예를 들면 액티비티)가 configuration change 때문에 파괴되고 재생성된다면, 새로운 owner 인스턴스는 기존의 ViewModelStore 인스턴스를 가져야 한다.

만약 해당 ViewModelStore의 owner가 파괴되고 재생성되지 않으면, owner는 ViewModelStore의 clear() 메서드를 호출하여, ViewModel들에게 더 이상 사용되지 않을 거라고 알려야 한다.

open class ViewModelStore {

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

    /**
     */
    @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.
     */
    /**
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    operator fun get(key: String): ViewModel? {
        return map[key]
    }

    /**
     */
    @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()
    }
}

map이라는 프로퍼티가 있는데 이를 통해 ViewModel들을 관리한다. String 타입의 Key와 ViewModel 타입의 Value가 LinkedHashMap 구조로 저장되는 것을 알 수 있다. (mutableMapOf() 함수가 비어있는 LinkedHashMap을 리턴한다.)

clear() 메서드를 보면, map에 있는 각 ViewModel의 clear() 메서드를 호출하고, map도 clear 처리를 한다는 것을 알 수 있다.

ViewModelStoreOwner

/**
 * A scope that owns [ViewModelStore].
 *
 * A responsibility of an implementation of this interface is to retain owned ViewModelStore
 * during the configuration changes and call [ViewModelStore.clear], when this scope is
 * going to be destroyed.
 *
 * @see ViewTreeViewModelStoreOwner
 */
interface ViewModelStoreOwner {

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

ViewModelStore를 추상 프로퍼티로 소유하고 있는 interface이다. ViewModelStoreOwner 인터페이스를 구현하는 객체는 소유하고 있는 ViewModelStore를 configuration change 동안 유지하는 것을 담당한다.

또한 해당 스코프가 destroy 될 때 ViewModelStore 클래스의 clear() 메서드를 호출하여 ViewModel들에게 더 이상 사용되지 않을 거라고 알려야 한다. 아까 ViewModelStore 부분에서 설명한 것과 동일한 내용이다.

그리고 여기서 스코프라는 용어가 나오는데, 스코프는 객체가 유효하고 존재할 수 있는 범위를 말한다. 약간 용어가 다르지만, 사실상 생명주기라고 이해해도 될 것 같다. 예를 들어 액티비티가 완전히 파괴되고 재생성하지 않는다면 액티비티의 스코프가 destroy되었다고 이해하면 된다.

ViewModelProvider

ViewModel을 제공하는 유틸리티 클래스이다. ViewModel 인스턴스를 생성하려면 ViewModelProvider 클래스를 사용해야 한다.

public open class ViewModelProvider

	//...

    public constructor(
        owner: ViewModelStoreOwner
    ) : this(owner.viewModelStore, defaultFactory(owner), defaultCreationExtras(owner))

ViewModelProvider 클래스의 constructor 선언부를 보면 ViewModelStoreOwner를 생성자 파라미터로 선언한 것을 알 수 있다. 그래서 ViewModelProvider 객체를 생성할 때 ViewModelStoreOwner interface를 구현하는 객체를 인자로 전달해야 한다.

// SearchFragment.kt
private val viewModel = ViewModelProvider(this@SearchFragment).get(SearchViewModel::class.java)

보통 위와 같은 방식으로 사용한다. ViewModelStoreOwner interface를 직접적으로 구현하는 클래스(direct subclass)는 ComponentActivity, Fragment, NavBackStackEntry이다. 그래서 보통 ViewModel 인스턴스를 생성할 때 이 클래스들을 상속한 클래스의 인스턴스가 인자로 전달된다. 예시에서는 Fragment를 상속한 SearchFragment가 인자로 전달되었다.

ViewModelProvider.Factory

    /**
     * Implementations of `Factory` interface are responsible to instantiate ViewModels.
     */
    public interface Factory {
        /**
         * Creates a new instance of the given `Class`.
         *
         * Default implementation throws [UnsupportedOperationException].
         *
         * @param modelClass a `Class` whose instance is requested
         * @return a newly created ViewModel
         */
        public fun <T : ViewModel> create(modelClass: Class<T>): T {
            throw UnsupportedOperationException(
                "Factory.create(String) is unsupported.  This Factory requires " +
                    "`CreationExtras` to be passed into `create` method."
            )
        }

        /**
         * Creates a new instance of the given `Class`.
         *
         * @param modelClass a `Class` whose instance is requested
         * @param extras an additional information for this creation request
         * @return a newly created ViewModel
         */
        public fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T =
            create(modelClass)

        companion object {
            /**
             * Creates an [InitializerViewModelFactory] using the given initializers.
             *
             * @param initializers the class initializer pairs used for the factory to create
             * simple view models
             */
            @JvmStatic
            fun from(vararg initializers: ViewModelInitializer<*>): Factory =
                InitializerViewModelFactory(*initializers)
        }
    }

ViewModelProvider.Factory는 ViewModel들을 인스턴스화(객체를 생성)하기 위한 목적으로 만들어진 인터페이스이다. 이 인터페이스의 구현체들은 ViewModel들을 인스턴스화 해야 할 책임을 가지며, create() 메서드를 통해서 새로운 ViewModel 객체를 생성한다. 디자인 패턴 중 하나인 팩토리 메소드 패턴이 사용되었다.


참고자료

profile
안드로이드 주니어 개발자

0개의 댓글