10
02

프래그먼트 끼리 값을 공유하려면 번들을 이용해 값을 넘기는 방법도 있지만, 뷰모델을 공유하면 뷰가 재구성되는 경우에도 안전하게 값 공유가 가능하다. 뷰 모델을 사용하려면 뷰모델 인스턴스를 생성해서 사용해야하는데 종류 별로 알아보겠다.

위임 프로퍼티로 뷰모델을 생성하기 전 모습을 먼저 보자.

val activityViewModel = ViewModelProvider(this).get(MyActivityViewModel::class.java)
val activityViewModel = ViewModelProvider(this,factory).get(MyActivityViewModel::class.java)
 

ViewModelProvider를 사용하는 방법이다. this가 들어간 자리는 액티비티가 들어갈 수도 있고, 프래그먼트가 들어갈 수도 는데, 이때 들어간 컴포넌트의 수명주기에 종속적으로 뷰모델이 관리된다. 커스텀 팩토리도 넣을 수 있다. 액티비티를 공유하는 프래그먼트들에서 ViewModelProvider로 공유중인 액티비티를 넣으면 프래그먼트들은 뷰모델을 공유한다. 재생성과 같은 이벤트가 발생해도 데이터가 남아있게 되는 것이다.

뷰모델 수명주기를 보겠다. onCreate때 생성해서, 화면전환과 같은 재구성 이벤트가 아니라 진짜 종료될 때 onCleared를 호출해 뷰모델을 지운다.

액티비티의 주명주기를 따르는 모습인데, 프래그먼트의 경우에도 똑같다. 약간 다른점이라면 프래그먼트는 onAttach 이후에 viewModel프로퍼티에 접근할 수 있게 된다.

이걸 위임프로퍼티 방식으로 바꿔보자.

val viewModel: MyViewModel by viewModels()
val viewmodel: MyViewModel by viewmodels { myFactory }

내부적으로 ViewModelProvider를 사용하게 만들어서 코드가 굉장히 간단해진다. 위임의 목적에 잘 부합한다고 할 수 있겠다. 현재 프래그먼트/액티비티로 생성기준을 설정하여 뷰모델 인스턴스를 생성하기 때문에 수명주기는 그걸 따라간다. by viewModels()는 기본 viewmodel 생성 팩토리를 사용하기 때문에 커스텀팩토리를 사용해 인스턴스를 만들고 싶다면 람다로 넘겨야한다. by viewModels()로 일단 인스턴스를 생성하고 그 이후에 초기화하는 작업을 추가하는 방식으로도 구현할 수 있다.

내부 코드를 살펴보자.

@MainThread
public fun <VM : ViewModel> Fragment.createViewModelLazy(
    viewModelClass: KClass<VM>,
    storeProducer: () -> ViewModelStore,
    extrasProducer: () -> CreationExtras = { defaultViewModelCreationExtras },
    factoryProducer: (() -> Factory)? = null

): Lazy<VM> {
    val factoryPromise = factoryProducer ?: {
        defaultViewModelProviderFactory
    }
    return ViewModelLazy(viewModelClass, storeProducer, factoryPromise, extrasProducer)
}
 

메인스레드에서 작동하고, factory를 null로 해서 defaultViewModelProviderFactory를 ViewModelProvider.Factory에 넣는다. 리턴하는 부분으로 더 들어가보겠다.

public class ViewModelLazy<VM : ViewModel> @JvmOverloads constructor(
    private val viewModelClass: KClass<VM>,
    private val storeProducer: () -> ViewModelStore,
    private val factoryProducer: () -> ViewModelProvider.Factory,
    private val extrasProducer: () -> CreationExtras = { CreationExtras.Empty }
) : Lazy<VM> {
    private var cached: VM? = null

    override val value: VM
        get() {
            val viewModel = cached
            return if (viewModel == null) {
                val factory = factoryProducer()
                val store = storeProducer()
                ViewModelProvider(
                    store,
                    factory,
                    extrasProducer()
                ).get(viewModelClass.java).also {
                    cached = it
                }
            } else {
                viewModel
            }
        }

    override fun isInitialized(): Boolean = cached != null
}
 

이 동작을 보면, by viewModels()에는 안보이게 한 내부에서 작동하는 ViewModelProvider를 볼 수 있다.

이제 by viewModels(), by activityViewModels(), by viewModels({ requireParentFragment() })를 살펴보자.

by activityViewModels()

이건 사용할 프래그먼트가 onAttach() 상태일 때 정상작동한다. parent activity의 viewModel에 접근하는 방법이다.

@MainThread
public inline fun <reified VM : ViewModel> Fragment.activityViewModels(
    noinline extrasProducer: (() -> CreationExtras)? = null,
    noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> = createViewModelLazy(
    VM::class, { requireActivity().viewModelStore },
    { extrasProducer?.invoke() ?: requireActivity().defaultViewModelCreationExtras },
    factoryProducer ?: { requireActivity().defaultViewModelProviderFactory }

)
 

ViewModelStore?

ViewModelStore 파라미터에게 requireAcitivty의 viewModelStore를 넘기고 있다. ViewModelStore는 ViewModel을 저장하기 위한 클래스인데 ViewModelStore의 소유주가 destroy되거나 재생성되어도 이전 정보를 갖는 인스턴스를 남아있게한다.(위에 있는 액티비티 수명주기 사진과 함께 보면 좋다.) 하지만 재생성되는 경우가 아니고 destroy되었다면 clear를 불러서 인스턴스를 지운다.

by viewModels(), by viewModels({ requireParentFragment() })

이 두개는 같은 메서드를 사용하고 있고, 차이점이라면 뒤의 것은 ViewModelStore의 소유주를 명시해준다는 것이다. 프래그먼트를 사용하는 코드가 대부분 더 긴데, 이번에도 길다.

@MainThread
public inline fun <reified VM : ViewModel> Fragment.viewModels(
    noinline ownerProducer: () -> ViewModelStoreOwner = { this },
    noinline extrasProducer: (() -> CreationExtras)? = null,
    noinline factoryProducer: (() -> Factory)? = null
): Lazy<VM> {
    val owner by lazy(LazyThreadSafetyMode.NONE) { ownerProducer() }
    return createViewModelLazy(
        VM::class,
        { owner.viewModelStore },
        {
            extrasProducer?.invoke()
            ?: (owner as? HasDefaultViewModelProviderFactory)?.defaultViewModelCreationExtras
            ?: CreationExtras.Empty
        },
        factoryProducer ?: {
            (owner as? HasDefaultViewModelProviderFactory)?.defaultViewModelProviderFactory
                ?: defaultViewModelProviderFactory
        })
}
 

파라미터가 3개인데, null로 default가 선언된 파라미터들이라 람다로 넣으면 ViewModelStoreOwner를 지정할 수 있게 되는 것이다. 없으면 default가 this로 되어있기 때문에 부모-자식 간의 프래그먼트 관계를 사용한다면 반드시 requireParentFragment를 자식 프래그먼트의 뷰모델 생성시 넣어줘야한다. 안그러면 자식 프래그먼트에서 생성하는 뷰모델 인스턴스와 부모 프래그먼트의 뷰모델 인스턴스가 독립된 상태로 되어 값 공유가 이루어지지 않게된다.

createViewModelLazy는 맨 위에서 먼저 봤기 때문에 넘어가겠다.


 

참조:

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

"댓글, 공감 버튼 한 번씩 누르고 가주시면 큰 힘이 됩니다"
반응형
COMMENT