Skip to content

Commit

Permalink
Save state in nested routers
Browse files Browse the repository at this point in the history
  • Loading branch information
arkivanov committed Aug 30, 2020
1 parent 41972c9 commit 5a6dfc7
Show file tree
Hide file tree
Showing 32 changed files with 654 additions and 425 deletions.

This file was deleted.

26 changes: 0 additions & 26 deletions decompose/src/main/java/com/arkivanov/decompose/BackStack.kt

This file was deleted.

19 changes: 0 additions & 19 deletions decompose/src/main/java/com/arkivanov/decompose/BackStackExt.kt

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.arkivanov.decompose

import androidx.activity.OnBackPressedDispatcher
import androidx.lifecycle.Lifecycle

interface ComponentContext : RouterFactory {

val lifecycle: Lifecycle
val savedStateKeeper: SavedStateKeeper
val onBackPressedDispatcher: OnBackPressedDispatcher
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.arkivanov.decompose

import androidx.lifecycle.Lifecycle

fun ComponentContext.child(key: String, lifecycle: Lifecycle = this.lifecycle): ComponentContext {
val childSavedStateKeeper = savedStateKeeper.child(key)

return ComponentContextImpl(
lifecycle,
savedStateKeeper = childSavedStateKeeper,
onBackPressedDispatcher = onBackPressedDispatcher,
routerFactory = DefaultRouterFactory(lifecycle, childSavedStateKeeper, onBackPressedDispatcher)
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.arkivanov.decompose

import androidx.activity.OnBackPressedDispatcher
import androidx.lifecycle.Lifecycle

internal class ComponentContextImpl(
override val lifecycle: Lifecycle,
override val savedStateKeeper: SavedStateKeeper,
override val onBackPressedDispatcher: OnBackPressedDispatcher,
routerFactory: RouterFactory
) : ComponentContext, RouterFactory by routerFactory
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.arkivanov.decompose

import android.os.Parcelable
import androidx.activity.OnBackPressedDispatcher
import androidx.lifecycle.Lifecycle

internal class DefaultRouterFactory(
private val lifecycle: Lifecycle,
private val savedStateKeeper: SavedStateKeeper,
private val onBackPressedDispatcher: OnBackPressedDispatcher
) : RouterFactory {

override fun <C : Parcelable> router(
initialConfiguration: C,
configurationClassLoader: ClassLoader?,
key: String,
handleBackButton: Boolean,
componentFactory: (configuration: C, ComponentContext) -> Component
): Router<C> =
RouterImpl(
initialConfiguration = initialConfiguration,
configurationClassLoader = configurationClassLoader,
lifecycle = lifecycle,
savedStateKeeper = savedStateKeeper,
savedStateKey = key,
onBackPressedDispatcher = onBackPressedDispatcher.takeIf { handleBackButton }
) { configuration, lifecycle, savedStateKeeper ->
componentFactory(
configuration,
ComponentContextImpl(
lifecycle,
savedStateKeeper,
onBackPressedDispatcher,
DefaultRouterFactory(lifecycle, savedStateKeeper, onBackPressedDispatcher)
)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.arkivanov.decompose

import android.os.Bundle

internal class DefaultSavedStateKeeper(
private val savedState: Bundle?
) : SavedStateKeeper {

private val suppliers = HashMap<String, () -> Bundle>()
private var onFirstSupplierAdded: (() -> Unit)? = null
private var onLastSupplierRemoved: (() -> Unit)? = null

fun setCallbacks(onFirstSupplierAdded: (() -> Unit)? = null, onLastSupplierRemoved: (() -> Unit)? = null) {
this.onFirstSupplierAdded = onFirstSupplierAdded
this.onLastSupplierRemoved = onLastSupplierRemoved
}

override fun consume(key: String): Bundle? {
val bundle = savedState?.getBundle(key) ?: return null
savedState.remove(key)

return bundle
}

override fun register(key: String, supplier: () -> Bundle) {
check(!suppliers.containsKey(key)) { "Another supplier is already registered for the key: $key" }

val isFirst = suppliers.isEmpty()
suppliers[key] = supplier

if (isFirst) {
onFirstSupplierAdded?.invoke()
}
}

override fun unregister(key: String) {
check(key in suppliers) { "No supplier is registered for the key: $key" }

suppliers -= key

if (suppliers.isEmpty()) {
onLastSupplierRemoved?.invoke()
}
}

fun save(): Bundle =
Bundle().apply {
suppliers.forEach { (key, supplier) ->
putBundle(key, supplier())
}
}
}
15 changes: 15 additions & 0 deletions decompose/src/main/java/com/arkivanov/decompose/LifecycleExt.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.arkivanov.decompose

import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner

internal inline fun Lifecycle.doOnDestroy(crossinline block: () -> Unit) {
addObserver(
object : DefaultLifecycleObserver {
override fun onDestroy(owner: LifecycleOwner) {
block()
}
}
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ package com.arkivanov.decompose
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry

internal class MergedLifecycle(lifecycle1: Lifecycle, lifecycle2: Lifecycle) {

private val holder = LifecycleHolder() // Must be stored in a field
val registry: LifecycleRegistry = holder.registry
val lifecycle: Lifecycle = holder.registry

init {
lifecycle1.addObserver(LifecycleObserverImpl(lifecycle2))
Expand Down Expand Up @@ -44,13 +43,13 @@ internal class MergedLifecycle(lifecycle1: Lifecycle, lifecycle2: Lifecycle) {

private fun onUp(state: Lifecycle.State, event: Lifecycle.Event) {
if (state <= other.currentState) {
registry.handleLifecycleEvent(event)
holder.registry.handleLifecycleEvent(event)
}
}

private fun onDown(state: Lifecycle.State, event: Lifecycle.Event) {
if (state < other.currentState) {
registry.handleLifecycleEvent(event)
holder.registry.handleLifecycleEvent(event)
}
}
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.arkivanov.decompose

import androidx.activity.OnBackPressedDispatcher
import androidx.activity.OnBackPressedDispatcherOwner
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.savedstate.SavedStateRegistry
import androidx.savedstate.SavedStateRegistryOwner

@Composable
fun RootComponent(
savedStateRegistry: SavedStateRegistry,
onBackPressedDispatcher: OnBackPressedDispatcher,
factory: (ComponentContext) -> Component
) {
val lifecycle = lifecycle()

val component =
remember {
val savedStateKeeper = rootSavedStateKeeper(savedStateRegistry)
val routerFactory = DefaultRouterFactory(lifecycle, savedStateKeeper, onBackPressedDispatcher)
factory(ComponentContextImpl(lifecycle, savedStateKeeper, onBackPressedDispatcher, routerFactory))
}

component.content()
}

@Composable
fun <T> T.RootComponent(factory: (ComponentContext) -> Component) where T : SavedStateRegistryOwner, T : OnBackPressedDispatcherOwner {
RootComponent(savedStateRegistry, onBackPressedDispatcher, factory)
}

private fun rootSavedStateKeeper(registry: SavedStateRegistry): SavedStateKeeper =
DefaultSavedStateKeeper(registry.consumeRestoredStateForKey(KEY_ROOT_SAVED_STATE_KEEPER_STATE)).also { keeper ->
keeper.setCallbacks(
onFirstSupplierAdded = { registry.registerSavedStateProvider(KEY_ROOT_SAVED_STATE_KEEPER_STATE, keeper::save) },
onLastSupplierRemoved = { registry.unregisterSavedStateProvider(KEY_ROOT_SAVED_STATE_KEEPER_STATE) }
)
}

private const val KEY_ROOT_SAVED_STATE_KEEPER_STATE = "ROOT_SAVED_STATE_KEEPER_STATE"
25 changes: 6 additions & 19 deletions decompose/src/main/java/com/arkivanov/decompose/Router.kt
Original file line number Diff line number Diff line change
@@ -1,27 +1,14 @@
package com.arkivanov.decompose

import androidx.activity.OnBackPressedDispatcher
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.Lifecycle
import android.os.Parcelable

interface Router<in C> : Component {
interface Router<C : Parcelable> : Component {

val stackSize: Int

fun getStack(): List<C>

fun push(configuration: C)

fun pop()
}

fun <C> Router(
initialConfiguration: C,
lifecycle: Lifecycle,
stateKeeper: RouterStateKeeper<C>? = null,
onBackPressedDispatcher: OnBackPressedDispatcher? = null,
resolve: (configuration: C, Lifecycle) -> Component
): Router<C> =
RouterImpl(
initialConfiguration = initialConfiguration,
lifecycle = lifecycle,
stateKeeper = stateKeeper,
backPressedDispatcher = onBackPressedDispatcher,
resolve = resolve
)
14 changes: 14 additions & 0 deletions decompose/src/main/java/com/arkivanov/decompose/RouterFactory.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.arkivanov.decompose

import android.os.Parcelable

interface RouterFactory {

fun <C : Parcelable> router(
initialConfiguration: C,
configurationClassLoader: ClassLoader?,
key: String = "DefaultRouter",
handleBackButton: Boolean = false,
componentFactory: (configuration: C, ComponentContext) -> Component
): Router<C>
}
Loading

0 comments on commit 5a6dfc7

Please sign in to comment.