Skip to content

Commit

Permalink
Retain instances
Browse files Browse the repository at this point in the history
  • Loading branch information
arkivanov committed Sep 2, 2020
1 parent d7ce951 commit 65483ad
Show file tree
Hide file tree
Showing 11 changed files with 207 additions and 81 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
package com.arkivanov.decompose

import androidx.activity.OnBackPressedDispatcher
import androidx.lifecycle.Lifecycle
import androidx.activity.OnBackPressedDispatcherOwner
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewModelStoreOwner

interface ComponentContext : RouterFactory {

val lifecycle: Lifecycle
val savedStateKeeper: SavedStateKeeper
val onBackPressedDispatcher: OnBackPressedDispatcher
}
interface ComponentContext : RouterFactory, SavedStateKeeperOwner, LifecycleOwner, OnBackPressedDispatcherOwner, ViewModelStoreOwner
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import androidx.lifecycle.Lifecycle

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

return ComponentContextImpl(
lifecycle,
savedStateKeeper = childSavedStateKeeper,
onBackPressedDispatcher = onBackPressedDispatcher,
routerFactory = DefaultRouterFactory(lifecycle, childSavedStateKeeper, onBackPressedDispatcher)
viewModelStore = childViewModelStore,
routerFactory = DefaultRouterFactory(lifecycle, childSavedStateKeeper, onBackPressedDispatcher, childViewModelStore)
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,19 @@ package com.arkivanov.decompose

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

internal class ComponentContextImpl(
override val lifecycle: Lifecycle,
private val lifecycle: Lifecycle,
override val savedStateKeeper: SavedStateKeeper,
override val onBackPressedDispatcher: OnBackPressedDispatcher,
private val onBackPressedDispatcher: OnBackPressedDispatcher,
private val viewModelStore: ViewModelStore,
routerFactory: RouterFactory
) : ComponentContext, RouterFactory by routerFactory
) : ComponentContext, RouterFactory by routerFactory {

override fun getLifecycle(): Lifecycle = lifecycle

override fun getOnBackPressedDispatcher(): OnBackPressedDispatcher = onBackPressedDispatcher

override fun getViewModelStore(): ViewModelStore = viewModelStore
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package com.arkivanov.decompose
import android.os.Parcelable
import androidx.activity.OnBackPressedDispatcher
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModelStore

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

override fun <C : Parcelable> router(
Expand All @@ -22,16 +24,18 @@ internal class DefaultRouterFactory(
configurationClassLoader = configurationClassLoader,
lifecycle = lifecycle,
savedStateKeeper = savedStateKeeper,
savedStateKey = key,
onBackPressedDispatcher = onBackPressedDispatcher.takeIf { handleBackButton }
) { configuration, lifecycle, savedStateKeeper ->
key = key,
onBackPressedDispatcher = onBackPressedDispatcher.takeIf { handleBackButton },
viewModelStore = viewModelStore
) { configuration, lifecycle, savedStateKeeper, viewModelStore ->
componentFactory(
configuration,
ComponentContextImpl(
lifecycle,
savedStateKeeper,
onBackPressedDispatcher,
DefaultRouterFactory(lifecycle, savedStateKeeper, onBackPressedDispatcher)
viewModelStore,
DefaultRouterFactory(lifecycle, savedStateKeeper, onBackPressedDispatcher, viewModelStore)
)
)
}
Expand Down
45 changes: 45 additions & 0 deletions decompose/src/main/java/com/arkivanov/decompose/LifecycleExt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.arkivanov.decompose
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry

internal inline fun Lifecycle.doOnDestroy(crossinline block: () -> Unit) {
addObserver(
Expand All @@ -13,3 +14,47 @@ internal inline fun Lifecycle.doOnDestroy(crossinline block: () -> Unit) {
}
)
}

internal fun LifecycleRegistry.create() {
if (currentState == Lifecycle.State.INITIALIZED) {
handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
}
}

internal fun LifecycleRegistry.start() {
create()

if (currentState == Lifecycle.State.CREATED) {
handleLifecycleEvent(Lifecycle.Event.ON_START)
}
}

internal fun LifecycleRegistry.resume() {
start()

if (currentState == Lifecycle.State.STARTED) {
handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
}
}

internal fun LifecycleRegistry.pause() {
if (currentState == Lifecycle.State.RESUMED) {
handleLifecycleEvent(Lifecycle.Event.ON_PAUSE)
}
}

internal fun LifecycleRegistry.stop() {
pause()

if (currentState == Lifecycle.State.STARTED) {
handleLifecycleEvent(Lifecycle.Event.ON_STOP)
}
}

internal fun LifecycleRegistry.destroy() {
stop()

if (currentState == Lifecycle.State.CREATED) {
handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,35 @@ import androidx.activity.OnBackPressedDispatcher
import androidx.activity.OnBackPressedDispatcherOwner
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
import androidx.savedstate.SavedStateRegistry
import androidx.savedstate.SavedStateRegistryOwner

@Composable
fun RootComponent(
savedStateRegistry: SavedStateRegistry,
onBackPressedDispatcher: OnBackPressedDispatcher,
viewModelStore: ViewModelStore,
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))
val routerFactory = DefaultRouterFactory(lifecycle, savedStateKeeper, onBackPressedDispatcher, viewModelStore)
factory(ComponentContextImpl(lifecycle, savedStateKeeper, onBackPressedDispatcher, viewModelStore, routerFactory))
}

component.content()
}

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

private fun rootSavedStateKeeper(registry: SavedStateRegistry): SavedStateKeeper =
Expand Down
98 changes: 61 additions & 37 deletions decompose/src/main/java/com/arkivanov/decompose/RouterImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,18 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelStore

internal class RouterImpl<C : Parcelable>(
initialConfiguration: C,
private val configurationClassLoader: ClassLoader?,
private val lifecycle: Lifecycle,
private val savedStateKeeper: SavedStateKeeper,
private val savedStateKey: String,
private val key: String,
onBackPressedDispatcher: OnBackPressedDispatcher?,
private val componentFactory: (C, Lifecycle, SavedStateKeeper) -> Component
viewModelStore: ViewModelStore,
private val componentFactory: (C, Lifecycle, SavedStateKeeper, ViewModelStore) -> Component
) : Router<C>, LifecycleOwner {

private val onBackPressedCallback = OnBackPressedCallbackImpl()
Expand All @@ -26,15 +28,37 @@ internal class RouterImpl<C : Parcelable>(
onBackPressedDispatcher?.addCallback(this, onBackPressedCallback)
}

private val viewModel = viewModelStore.viewModel(key) { ViewModelImpl<C>() }
private val stackState = mutableStateOf(restoreState() ?: Stack(active = createComponent(initialConfiguration)))

init {
savedStateKeeper.register(savedStateKey, ::saveState)
lifecycle.doOnDestroy { savedStateKeeper.unregister(savedStateKey) }
savedStateKeeper.register(key, ::saveState)

updateOnBackPressedCallback()

viewModel.activeEntry = stackState.value.active
stackState.value.active.lifecycleHolder.registry.resume()

lifecycle.doOnDestroy(::destroy)
}

private fun destroy() {
savedStateKeeper.unregister(key)

val stack = stackState.value

stack.active.lifecycleHolder.registry.destroy()

stack.backStack.reversed().forEach { entry ->
when (entry) {
is Stack.Entry.Created -> {
entry.viewModelStore.clear()
entry.lifecycleHolder.registry.destroy()
}

is Stack.Entry.Destroyed -> Unit
}.let {}
}
}

override val stackSize: Int get() = stackState.value.backStack.size + 1
Expand All @@ -51,7 +75,7 @@ internal class RouterImpl<C : Parcelable>(
override fun getLifecycle(): Lifecycle = lifecycle

private fun restoreState(): Stack<C>? {
val savedState = savedStateKeeper.consume(savedStateKey) ?: return null
val savedState = savedStateKeeper.consume(key) ?: return null

val configurations =
savedState
Expand All @@ -61,7 +85,8 @@ internal class RouterImpl<C : Parcelable>(

val last = configurations.last()
val lastBundle: Bundle? = savedState.getBundle(last.toString())
val lastEntry: Stack.Entry.Created<C> = createComponent(last, lastBundle)
val lastViewModelStore = viewModel.activeEntry?.viewModelStore
val lastEntry: Stack.Entry.Created<C> = createComponent(last, lastBundle, lastViewModelStore)

val backStack =
configurations.dropLast(1).map {
Expand Down Expand Up @@ -96,40 +121,33 @@ internal class RouterImpl<C : Parcelable>(

val savedEntry: Stack.Entry.Created<C> = oldStack.active.let { it.copy(savedState = it.savedStateKeeper.save()) }

savedEntry.lifecycleHolder.registry.apply {
handleLifecycleEvent(Lifecycle.Event.ON_PAUSE)
}

savedEntry.lifecycleHolder.registry.pause()
newEntry.lifecycleHolder.registry.resume()
savedEntry.lifecycleHolder.registry.stop()

savedEntry.lifecycleHolder.registry.apply {
handleLifecycleEvent(Lifecycle.Event.ON_STOP)
}

viewModel.activeEntry = newEntry
stackState.value = oldStack.copy(active = newEntry, backStack = oldStack.backStack + savedEntry)

updateOnBackPressedCallback()
}

private fun LifecycleRegistry.resume() {
if (currentState == Lifecycle.State.INITIALIZED) {
handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
}
handleLifecycleEvent(Lifecycle.Event.ON_START)
handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
}

private fun createComponent(configuration: C, savedState: Bundle? = null): Stack.Entry.Created<C> {
private fun createComponent(
configuration: C,
savedState: Bundle? = null,
savedViewModelStore: ViewModelStore? = null
): Stack.Entry.Created<C> {
val componentLifecycleHolder = LifecycleHolder()
val mergedLifecycle = MergedLifecycle(lifecycle, componentLifecycleHolder.registry)
val savedStateKeeper = DefaultSavedStateKeeper(savedState)
val component = componentFactory(configuration, mergedLifecycle.lifecycle, savedStateKeeper)
val viewModelStore = savedViewModelStore ?: ViewModelStore()
val component = componentFactory(configuration, mergedLifecycle.lifecycle, savedStateKeeper, viewModelStore)

return Stack.Entry.Created(
configuration = configuration,
component = component,
lifecycleHolder = componentLifecycleHolder,
savedStateKeeper = savedStateKeeper
savedStateKeeper = savedStateKeeper,
viewModelStore = viewModelStore
)
}

Expand All @@ -147,17 +165,12 @@ internal class RouterImpl<C : Parcelable>(

val topEntry: Stack.Entry.Created<C> = oldStack.active

topEntry.lifecycleHolder.registry.apply {
handleLifecycleEvent(Lifecycle.Event.ON_PAUSE)
}

topEntry.lifecycleHolder.registry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE)
nextActiveEntry.lifecycleHolder.registry.resume()
topEntry.viewModelStore.clear()
topEntry.lifecycleHolder.registry.destroy()

topEntry.lifecycleHolder.registry.apply {
handleLifecycleEvent(Lifecycle.Event.ON_STOP)
handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
}

viewModel.activeEntry = nextActiveEntry
stackState.value = oldStack.copy(active = nextActiveEntry, backStack = oldStack.backStack.dropLast(1))

updateOnBackPressedCallback()
Expand All @@ -167,7 +180,7 @@ internal class RouterImpl<C : Parcelable>(
onBackPressedCallback.isEnabled = stackState.value.backStack.isNotEmpty()
}

private data class Stack<out C : Parcelable>(
internal data class Stack<out C : Parcelable>(
val active: Entry.Created<C>,
val backStack: List<Entry<C>> = emptyList()
) {
Expand All @@ -180,7 +193,8 @@ internal class RouterImpl<C : Parcelable>(
override val savedState: Bundle? = null,
val component: Component,
val lifecycleHolder: LifecycleHolder,
val savedStateKeeper: DefaultSavedStateKeeper
val savedStateKeeper: DefaultSavedStateKeeper,
val viewModelStore: ViewModelStore
) : Entry<C>()

data class Destroyed<out C : Parcelable>(
Expand All @@ -195,4 +209,14 @@ internal class RouterImpl<C : Parcelable>(
pop()
}
}

private class ViewModelImpl<C : Parcelable> : ViewModel() {
var activeEntry: Stack.Entry.Created<C>? = null

override fun onCleared() {
activeEntry?.viewModelStore?.clear()

super.onCleared()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.arkivanov.decompose

interface SavedStateKeeperOwner {

val savedStateKeeper: SavedStateKeeper
}
Loading

0 comments on commit 65483ad

Please sign in to comment.