Notice
Recent Posts
Recent Comments
Link
«   2025/09   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
Archives
Today
Total
관리 메뉴

코린이 개발로그

Compose ViewModel 본문

Kotlin & Android

Compose ViewModel

코드징어 2025. 2. 26. 22:09

viewModel()

Android 를 개발할때 ViewModel 은 필수적인 요소로 사용되고있습니다.

Compose 로 개발할때도 ViewModel 은 여전히 많이 사용됩니다.

일반적인 Compose 의 예제를 확인했을때 우리는 Compose 에서 제공하는 viewModel() 함수를 주로사용하는것을 볼 수 있습니다.

@Composable
MyScreen() {
	val viewModel = viewModel()
	...
}

일반적으로 사이드 프로젝트나 비교적 간단한 어플리케이션의 경우에는 해당 함수를 간단하게 사용해도 큰 문제가 없습니다.

하지만 해당 함수는 몇가지 파라미터를 받고있습니다.

@Suppress("MissingJvmstatic")
@Composable
public inline fun <reified VM : ViewModel> viewModel(
    viewModelStoreOwner: ViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) {
        "No ViewModelStoreOwner was provided via LocalViewModelStoreOwner"
    },
    key: String? = null,
    factory: ViewModelProvider.Factory? = null,
    extras: CreationExtras = if (viewModelStoreOwner is HasDefaultViewModelProviderFactory) {
        viewModelStoreOwner.defaultViewModelCreationExtras
    } else {
        CreationExtras.Empty
    }
): VM = viewModel(VM::class, viewModelStoreOwner, key, factory, extras)

각각 파라미터에 대한 설명을 보겠습니다.


viewModelStoreOwner - The owner of the ViewModel that controls the scope and lifetime of the returned ViewModel. Defaults to using LocalViewModelStoreOwner.

viewModel 의 생명주기를 관리해주는 값의 파라미터입니다.


key - The key to use to identify the ViewModel.

viewModel 을 식별하기위한 키로 보입니다.


factory - The ViewModelProvider. Factory that should be used to create the ViewModel or null if you would like to use the default factory from the LocalViewModelStoreOwner

ViewModelFactory 를 파라미터로 받고있습니다. 파라미터를 가진 viewModel 을 생성할경우 ViewModelFactory 를 통해 커스터마이징이 필요합니다.


extras - The default extras used to create the ViewModel.

ViewModel 을 생성할때 필요한 인자를 전달할 수 있는 extras 입니다.

 

viewModelStoreOwner

우리가 Android View 에서 ViewModel 을 사용했을때는 context 를 제공받아 해당 Activity 나 Fragment 의 생명주기를 따라가게 됩니다.

하나의 Activity 에서 여러 Fragment 를 사용하게 되는 경우에는 같은 데이터를 공유하기위해 context 를 requireActivity() 를 통해 제공받아 해당 context 를 통해 ViewModel 을 생성하게 됩니다.

viewModelStoreOwner 또한 같은 논리로 Compose 에서의 생명주기를 관리하는 객체입니다.

/**
 * The CompositionLocal containing the current [ViewModelStoreOwner].
 */
public object LocalViewModelStoreOwner {
    private val LocalViewModelStoreOwner =
        compositionLocalOf<ViewModelStoreOwner?> { null }

    /**
     * Returns current composition local value for the owner or `null` if one has not
     * been provided nor is one available via [findViewTreeViewModelStoreOwner] on the
     * current [androidx.compose.ui.platform.LocalView].
     */
    public val current: ViewModelStoreOwner?
        @Composable
        get() = LocalViewModelStoreOwner.current ?: findViewTreeViewModelStoreOwner()

    /**
     * Associates a [LocalViewModelStoreOwner] key to a value in a call to
     * [CompositionLocalProvider].
     */
    public infix fun provides(viewModelStoreOwner: ViewModelStoreOwner):
        ProvidedValue<ViewModelStoreOwner?> {
        return LocalViewModelStoreOwner.provides(viewModelStoreOwner)
    }
}

default 값인 LocalViewModelStoreOwner.current 코드를 보겠습니다.

내부 변수인 current 는 기본적으로 findViewTreeViewModelStoreOwner() 함수를 사용하고 있으며 해당 함수는 아래와 같습니다.

@Composable
internal actual fun findViewTreeViewModelStoreOwner(): ViewModelStoreOwner? =
    LocalView.current.findViewTreeViewModelStoreOwner()

즉 LocalView 의 ViewModelStoreOwner 를 반환하게됩니다. 그렇다면 여기서 LocalView 는 무엇일까요?

/**
 * The CompositionLocal containing the current Compose [View].
 */
val LocalView = staticCompositionLocalOf<View> {
    noLocalProvidedFor("LocalView")
}

설명을 보면 가장최근의 Compose "View" 를 담고있는 compositionLocalOf 로 볼수있습니다. 즉 최상위의 AndroidComposeView 를 가져오게됩니다.

즉 하나의 Compose View 내부에서 여러가지 내부 Composable 함수가 불려도 결과적으론 같은 최상단의 AndroidComposeView의 ViewModelStoreOwner 를 반환하게 됩니다.

@Composable
fun MainScreen() {
    println(LocalViewModelStoreOwner.current) // com.xx.composestudy.MainActivity@3c7e4ed
    SubScreenA()
    SubScreenB()
}

@Composable
fun SubScreenA(){
    println(LocalViewModelStoreOwner.current) // com.xx.composestudy.MainActivity@3c7e4ed
}

...

위의 함수에서 볼수있듯이 하나의 Composable 함수내에선 같은 결과값을 리턴하는것을 볼 수 있습니다.

하지만 우리는 Compose 도입이후 하나의 Fragment 를 대채했고 기존에 우리는 Fragment 의 생명주기를 따르는 ViewModel 을 사용하고있습니다.

이런 경우에는 NavHost 를 사용해여 해결하기도 합니다.

Factory

ViewModel 이 파라미터가 있는 경우에는 ViewModelProvider 의 Factory 를 사용하여 커스터 마이징이 필요합니다.

기존의 경우에는 ViewModelProvier를 직접적으로 사용하는 경우도 있지만 해당 viewModel() 함수를 사용하기위해선 ViewModelProvider 의 Factory 를 만들어야합니다.

일반적으로 ViewModel 을 생성할때 External 클래스나 Repository 클래스를 파라미터로 두는 경우들이 있습니다. 그런경우에는 viewModel() 함수를 사용했을때 자동적으로 클래스를 생성할 수 없게됩니다. 따라서 해당 클래스의 파라미터들을 생성해주는 ViewModelProvider.Factory 를 만들어줘야합니다.

 class MyViewModel(
        private val myRepository: MyRepository,
        private val savedStateHandle: SavedStateHandle
    ) : ViewModel() {

        // ViewModel logic
        // ...

        // Define ViewModel factory in a companion object
        companion object {

            val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
                @Suppress("UNCHECKED_CAST")
                override fun <T : ViewModel> create(
                    modelClass: Class<T>,
                    extras: CreationExtras
                ): T {
                    // Get the Application object from extras
                    val application = checkNotNull(extras[APPLICATION_KEY])
                    // Create a SavedStateHandle for this ViewModel from extras
                    val savedStateHandle = extras.createSavedStateHandle()

                    return MyViewModel(
                        (application as MyApplication).myRepository,
                        savedStateHandle
                    ) as T
                }
            }
        }
    }

이렇게 해당 ViewModel 클래스 내부에 작성하면 가독성 및 더 쉬운 검색을 지원합니다.

이렇게 작성된 코드는 viewModel() 함수의 파라미터로 사용됩니다.

@Composable
MyScreen() {
	val viewModel = viewModel(factory = MyViewModel.Factory)
	...
}

당연히 기존의 Android View 에서도 사용이 가능합니다.

class MyActivity : AppCompatActivity() {

    private val viewModel: MyViewModel by viewModels { MyViewModel.Factory }

    ...
}

또 Factory 를 정의하는 코드는 ViewModel 팩토리 DSL 을 사용하여 더 자연스러운 Kotlin API 로 생성할 수 있습니다.

class MyViewModel(
    private val myRepository: MyRepository,
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {
    // ViewModel logic

    // Define ViewModel factory in a companion object
    companion object {
        val Factory: ViewModelProvider.Factory = viewModelFactory {
            initializer {
                val savedStateHandle = createSavedStateHandle()
                val myRepository = (this[APPLICATION_KEY] as MyApplication).myRepository
                MyViewModel(
                    myRepository = myRepository,
                    savedStateHandle = savedStateHandle
                )
            }
        }
    }
}

 

결론

Compose 에서 viewModel 을 생성하는 viewModel API 에 대해 공부해 보았습니다. 기존에 Activity 와 Fragment 의 생명주기를 따라가던 ViewModel 을 어떤식으로 Compose 에서도 효율적으로 사용하기 위해선 위와같은 파라미터들을 잘 이해하는게 중요한거같습니다.

'Kotlin & Android' 카테고리의 다른 글

DI 와 Hilt 와 Repository패턴  (0) 2021.11.29
Android ListView Widget을 만들어보자  (0) 2021.11.29
AAC 와 MVVM  (0) 2021.11.09
FireBase Gradle build 오류  (2) 2021.04.24
Comments