Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor rename rememberViewModel to be architecture agnostic #5

Merged
merged 3 commits into from
May 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 12 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,24 +145,23 @@ fun ListDetailScreen() {
}
}

// If you want your view-model to survive config changes 🔁, make it implement the [Instance] interface
class ListViewModel : InstanceKeeper.Instance

// If you want your state to survive process death ☠️, derive your initial state from [SavedStateHandle]
class DetailViewModel(savedState: SavedStateHandle, detail: String) : InstanceKeeper.Instance {
private val initialState: DetailState = savedState.get() ?: DetailState(detail)
private val stateFlow = MutableStateFlow(initialState)
val states: StateFlow<DetailState> = stateFlow
}

@Composable
fun DetailsScreen(detail: String) {
// Scope your view models to screen so that they get cleared when user leaves the screen
val viewModel: DetailViewModel = rememberViewModel { savedState -> DetailViewModel(savedState, detail) }
// 📦 Scope an instance (a view model, a state-holder or whatever) to a route with [rememberOnRoute]
// 1. Makes your instances survive configuration changes (on android) 🔁
// 2. Holds-on the instance as long as it is in the backstack 🔗
val instance: DetailInstance = rememberOnRoute { savedState -> DetailInstance(savedState, detail) }

val state: DetailState by viewModel.states.collectAsState()
val state: DetailState by instance.states.collectAsState()
Text(text = state.detail)
}

// If you want your state to survive process death ☠️ derive your initial state from [SavedStateHandle]
class DetailInstance(savedState: SavedStateHandle, detail: String) : InstanceKeeper.Instance {
private val initialState: DetailState = savedState.get() ?: DetailState(detail)
private val stateFlow = MutableStateFlow(initialState)
val states: StateFlow<DetailState> = stateFlow
}
```

## Platform configurations 🚉
Expand Down
5 changes: 1 addition & 4 deletions decompose-router/api/android/decompose-router.api
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public final class io/github/xxfast/decompose/router/Router : com/arkivanov/deco

public final class io/github/xxfast/decompose/router/RouterKt {
public static final fun getLocalRouter ()Landroidx/compose/runtime/ProvidableCompositionLocal;
public static final fun rememberOnRoute (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper$Instance;
public static final fun rememberRouter (Lkotlin/reflect/KClass;Ljava/util/List;ZLandroidx/compose/runtime/Composer;II)Lio/github/xxfast/decompose/router/Router;
}

Expand Down Expand Up @@ -48,10 +49,6 @@ public final class io/github/xxfast/decompose/router/SavedStateHandle : com/arki
public final fun set (Landroid/os/Parcelable;)V
}

public final class io/github/xxfast/decompose/router/ViewModelKt {
public static final fun rememberViewModel (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper$Instance;
}

public final class io/github/xxfast/decompose/router/content/RoutedContentKt {
public static final fun RoutedContent (Lio/github/xxfast/decompose/router/Router;Landroidx/compose/ui/Modifier;Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/StackAnimation;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V
}
Expand Down
5 changes: 1 addition & 4 deletions decompose-router/api/desktop/decompose-router.api
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public final class io/github/xxfast/decompose/router/Router : com/arkivanov/deco

public final class io/github/xxfast/decompose/router/RouterKt {
public static final fun getLocalRouter ()Landroidx/compose/runtime/ProvidableCompositionLocal;
public static final fun rememberOnRoute (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper$Instance;
public static final fun rememberRouter (Lkotlin/reflect/KClass;Ljava/util/List;ZLandroidx/compose/runtime/Composer;II)Lio/github/xxfast/decompose/router/Router;
}

Expand All @@ -37,10 +38,6 @@ public final class io/github/xxfast/decompose/router/SavedStateHandle : com/arki
public final fun set (Lcom/arkivanov/essenty/parcelable/Parcelable;)V
}

public final class io/github/xxfast/decompose/router/ViewModelKt {
public static final fun rememberViewModel (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper$Instance;
}

public final class io/github/xxfast/decompose/router/content/RoutedContentKt {
public static final fun RoutedContent (Lio/github/xxfast/decompose/router/Router;Landroidx/compose/ui/Modifier;Lcom/arkivanov/decompose/extensions/compose/jetbrains/stack/animation/StackAnimation;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ import com.arkivanov.decompose.router.stack.pop
import com.arkivanov.decompose.router.stack.push
import io.github.xxfast.decompose.router.Router
import io.github.xxfast.decompose.router.content.RoutedContent
import io.github.xxfast.decompose.router.rememberOnRoute
import io.github.xxfast.decompose.router.rememberRouter
import io.github.xxfast.decompose.router.rememberViewModel
import io.github.xxfast.decompose.screen.Screen.Round

const val TOOLBAR_TAG = "toolbar"
Expand Down Expand Up @@ -82,7 +82,7 @@ fun HomeScreen() {
fun ListScreen(
onSelect: (count: Int) -> Unit,
) {
val instance: ListInstance = rememberViewModel(ListInstance::class) { savedState ->
val instance: ListInstance = rememberOnRoute(ListInstance::class) { savedState ->
ListInstance(savedState)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
package io.github.xxfast.decompose.router

import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisallowComposableCalls
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.ProvidableCompositionLocal
import androidx.compose.runtime.State
import androidx.compose.runtime.remember
import androidx.compose.runtime.staticCompositionLocalOf
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.router.stack.ChildStack
import com.arkivanov.decompose.router.stack.StackNavigation
import com.arkivanov.essenty.instancekeeper.InstanceKeeper
import com.arkivanov.essenty.instancekeeper.InstanceKeeper.Instance
import com.arkivanov.essenty.instancekeeper.getOrCreate
import com.arkivanov.essenty.parcelable.Parcelable
import com.arkivanov.essenty.statekeeper.StateKeeper
import io.github.xxfast.decompose.LocalComponentContext
import io.github.xxfast.decompose.rememberChildStack
import kotlin.reflect.KClass

Expand Down Expand Up @@ -58,3 +65,43 @@ fun <C : Parcelable> rememberRouter(

return remember { Router(navigator = navigator, stack = childStackState) }
}

/***
* Creates a instance of [T] that is scoped to the current route
*
* @param instanceClass class of [T] instance
* @param block lambda to create an instance of [T] with a given [SavedStateHandle]
*/
@Suppress("UNCHECKED_CAST")
@Composable
fun <T : Instance> rememberOnRoute(
instanceClass: KClass<T>,
block: @DisallowComposableCalls (savedState: SavedStateHandle) -> T
): T {
val component: ComponentContext = LocalComponentContext.current
val stateKeeper: StateKeeper = component.stateKeeper
val instanceKeeper: InstanceKeeper = component.instanceKeeper

val packageName: String =
requireNotNull(instanceClass.simpleName) { "Unable to retain anonymous instance of $instanceClass"}
val instanceKey = "$packageName.instance"
val stateKey = "$packageName.savedState"

val (instance, savedState) = remember(instanceClass) {
val savedState: SavedStateHandle = instanceKeeper
.getOrCreate(stateKey) { SavedStateHandle(stateKeeper.consume(stateKey, SavedState::class)) }
var instance: T? = instanceKeeper.get(instanceKey) as T?
if (instance == null) {
instance = block(savedState)
instanceKeeper.put(instanceKey, instance)
}
instance to savedState
}

LaunchedEffect(Unit) {
if (!stateKeeper.isRegistered(stateKey))
stateKeeper.register(stateKey) { savedState.value }
}

return instance
}

This file was deleted.