Skip to content

Instantly share code, notes, and snippets.

@pbprateek
Last active August 16, 2024 13:24
Show Gist options
  • Save pbprateek/78ff641ae6d5871af52f33a7fc59a4f0 to your computer and use it in GitHub Desktop.
Save pbprateek/78ff641ae6d5871af52f33a7fc59a4f0 to your computer and use it in GitHub Desktop.
Viewmodel Scoped to Compose
import android.os.Bundle
import androidx.compose.runtime.Composable
import androidx.compose.runtime.RememberObserver
import androidx.compose.runtime.remember
import androidx.lifecycle.*
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.MutableCreationExtras
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
import androidx.lifecycle.viewmodel.compose.viewModel
//This is creating using help from,Thanks for that
//https://gist.github.com/manuelvicnt/a2e4c4812243ac1b218b24d0ac8d22bb#file-provideviewmodels-kt
//This works with hilt
// This is a way to scope ViewModels to the Composition.
// However, this doesn't survive configuration changes or procress death on its own.
// You can handle all config changes in compose by making the activity handle those in the Manifest file
// e.g. android:configChanges="colorMode|density|fontScale|keyboard|keyboardHidden|layoutDirection|locale|mcc|mnc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|touchscreen|uiMode">
@Composable
public inline fun <reified VM : ViewModel> ComposeScopedViewModelProvider(
key: String? = null,
extraParams: Bundle? = null
): VM {
val originalViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) {
"No ViewModelStoreOwner was provided via LocalViewModelStoreOwner"
}
val composeViewModelStoreOwner =
remember() {
CompositionScopedViewModelStoreOwner(
originalViewModelStoreOwner as HasDefaultViewModelProviderFactory,
extraParams
)
}
return viewModel(viewModelStoreOwner = composeViewModelStoreOwner, key = key)
}
class CompositionScopedViewModelStoreOwner(
private val originalFactoryProvider: HasDefaultViewModelProviderFactory,
private val extraParams: Bundle?
) :
ViewModelStoreOwner, RememberObserver, HasDefaultViewModelProviderFactory {
private val viewModelStoreNew = ViewModelStore()
override val viewModelStore: ViewModelStore = viewModelStoreNew
override fun onAbandoned() {
viewModelStore.clear()
}
override fun onForgotten() {
viewModelStore.clear()
}
override fun onRemembered() {
// Nothing to do here
}
override val defaultViewModelCreationExtras: CreationExtras =
MutableCreationExtras(originalFactoryProvider.defaultViewModelCreationExtras).apply {
val bundle = Bundle()
get(DEFAULT_ARGS_KEY)?.let {
bundle.putAll(it)
}
extraParams?.let {
bundle.putAll(it)
}
set(DEFAULT_ARGS_KEY, bundle)
}
override val defaultViewModelProviderFactory: ViewModelProvider.Factory
get() = originalFactoryProvider.defaultViewModelProviderFactory
}
@Composable
fun ExampleCompose(
modifier: Modifier = Modifier
) {
val mcqViewModel: TestViewModel = ComposeScopedViewModelProvider()
//or
val viewModel: TestViewModel = ComposeScopedViewModelProvider(
key = id,
extraParams = Bundle().apply {
putString(EXTRAS_ID, id)
}
)
}
@leshchenko
Copy link

leshchenko commented Sep 18, 2023

For using it with a hilt I added HiltViewModelFactory, otherwise, I had a crash:

@Composable
public inline fun <reified VM : ViewModel> ComposeScopedViewModelProvider(
    key: String? = null,
    extraParams: Bundle? = null
): VM {
    val originalViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) {
        "No ViewModelStoreOwner was provided via LocalViewModelStoreOwner"
    }
    val composeViewModelStoreOwner =
        remember() {
            CompositionScopedViewModelStoreOwner(
                originalViewModelStoreOwner as HasDefaultViewModelProviderFactory,
                extraParams
            )
        }
    val factory = HiltViewModelFactory(
        context = LocalContext.current,
        navBackStackEntry = originalViewModelStoreOwner as NavBackStackEntry,
    )
    return viewModel(viewModelStoreOwner = composeViewModelStoreOwner, key = key, factory = factory)
}

@pbprateek
Copy link
Author

Yeah , I don't use Compose -navigation, just compose and Hilt , that's why it forced u to handle NavBackStackEntry

But i will suggest just implement it , in CompositionScopedViewModelStoreOwner file the same was i have implemented
ViewModelStoreOwner, RememberObserver, HasDefaultViewModelProviderFactory.

Also , i have fixed a bug related to extra_param for case when parent ViewModel had empty savedStateHandler

@pbprateek
Copy link
Author

Also, on the original link
https://gist.github.com/manuelvicnt/a2e4c4812243ac1b218b24d0ac8d22bb#file-provideviewmodels-kt
Someone has added a way to survive conf changes as well and it looks promising

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment