import androidx.compose.runtime.* import androidx.lifecycle.* import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner import androidx.lifecycle.viewmodel.compose.viewModel import kotlinx.coroutines.* import kotlinx.coroutines.flow.* @Composable fun rememberViewModelStoreOwner( key: Any?, ): ViewModelStoreOwner { val viewModelKey = "rememberViewModelStoreOwner#" + currentCompositeKeyHash.toString(36) val localLifecycle = LocalLifecycleOwner.current.lifecycle val currentViewModelStoreOwner = LocalViewModelStoreOwner.current val viewModelStoreOwnerViewModel = viewModel { ViewModelStoreOwnerViewModel() } val viewModelStoreOwner = viewModelStoreOwnerViewModel.get(viewModelKey, key, localLifecycle) remember { object : RememberObserver { override fun onAbandoned() { viewModelStoreOwnerViewModel.detachComposable(viewModelKey) } override fun onForgotten() { viewModelStoreOwnerViewModel.detachComposable(viewModelKey) } override fun onRemembered() { viewModelStoreOwnerViewModel.attachComposable(viewModelKey) } } } return remember(currentViewModelStoreOwner, viewModelStoreOwner) { if (currentViewModelStoreOwner is HasDefaultViewModelProviderFactory) { object : ViewModelStoreOwner by viewModelStoreOwner, HasDefaultViewModelProviderFactory by currentViewModelStoreOwner {} } else { viewModelStoreOwner } } } @Composable fun WithViewModelStoreOwner( key: Any?, content: @Composable () -> Unit, ) { CompositionLocalProvider( value = LocalViewModelStoreOwner provides rememberViewModelStoreOwner(key), content = content, ) } @Stable private class ViewModelStoreOwnerViewModel : ViewModel() { private var attachedComposables = mapOf() fun get(hashKey: String, key: Any?, lifecycle: Lifecycle) = attachedComposables[hashKey]?.let { it.update(key, lifecycle) it.viewModelStoreOwner } ?: run { val attachedComposable = AttachedComposable(hashKey, key, lifecycle) attachedComposables += hashKey to attachedComposable attachedComposable.viewModelStoreOwner } fun attachComposable(hashKey: String) { attachedComposables[hashKey]?.attachComposable() } fun detachComposable(hashKey: String) { attachedComposables[hashKey]?.detachComposable() } override fun onCleared() { attachedComposables.keys.forEach { dispose(it) } super.onCleared() } private fun dispose(hashKey: String) { attachedComposables[hashKey]?.let { it.clear() attachedComposables -= hashKey } } private inner class AttachedComposable( private val hashKey: String, private var key: Any?, initialLifecycle: Lifecycle, ) { val viewModelStoreOwner: ViewModelStoreOwner = object : ViewModelStoreOwner { override val viewModelStore = ViewModelStore() } private val supervisorJob: CompletableJob = SupervisorJob() private val scope: CoroutineScope = viewModelScope + supervisorJob private val attachedLifecycle = MutableStateFlow(initialLifecycle) private val isAttachedToComposable = MutableSharedFlow() init { scope.launch { attachedLifecycle .flatMapLatest { lifecycle -> lifecycle?.eventFlow ?: emptyFlow() } .collectLatest { if (it == Lifecycle.Event.ON_DESTROY) { attachedLifecycle.update { null } } } } scope.launch { isAttachedToComposable.collectLatest { isAttached -> when { // If we are attached or we are destroyed, we do not need to do anything isAttached || attachedLifecycle.value == null -> return@collectLatest // If we are detached and the lifecycle state is resumed, we should reset the view model store attachedLifecycle.value?.currentState == Lifecycle.State.RESUMED -> { dispose(hashKey) } else -> { // We wait for the lifecycle event ON_RESUME to be triggered before resetting the ViewModelStore // If in the mean time we are attached again, this work is cancelled attachedLifecycle .flatMapLatest { lifecycle -> lifecycle?.eventFlow ?: emptyFlow() } // Wait for first event that matches ON_RESUME. .firstOrNull { it == Lifecycle.Event.ON_RESUME } ?: return@collectLatest dispose(hashKey) } } } } } fun clear() { supervisorJob.cancelChildren() viewModelStoreOwner.viewModelStore.clear() } fun update(key: Any?, lifecycle: Lifecycle) { if (key != this.key) { this.key = key viewModelStoreOwner.viewModelStore.clear() } attachedLifecycle.update { lifecycle } } fun attachComposable() { scope.launch { isAttachedToComposable.emit(true) } } fun detachComposable() { scope.launch { isAttachedToComposable.emit(false) } } } }