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

Use Children in Stack and Overlay #242

Merged
merged 1 commit into from
Oct 24, 2022
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
22 changes: 5 additions & 17 deletions decompose/api/android/decompose.api
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,7 @@ public final class com/arkivanov/decompose/router/overlay/OverlayNavigationFacto
public static final fun OverlayNavigation ()Lcom/arkivanov/decompose/router/overlay/OverlayNavigation;
}

public abstract interface class com/arkivanov/decompose/router/overlay/OverlayNavigationSource {
public abstract fun subscribe (Lkotlin/jvm/functions/Function1;)V
public abstract fun unsubscribe (Lkotlin/jvm/functions/Function1;)V
public abstract interface class com/arkivanov/decompose/router/overlay/OverlayNavigationSource : com/arkivanov/decompose/router/children/NavigationSource {
}

public final class com/arkivanov/decompose/router/overlay/OverlayNavigationSource$Event {
Expand Down Expand Up @@ -188,9 +186,7 @@ public final class com/arkivanov/decompose/router/stack/StackNavigationFactoryKt
public static final fun StackNavigation ()Lcom/arkivanov/decompose/router/stack/StackNavigation;
}

public abstract interface class com/arkivanov/decompose/router/stack/StackNavigationSource {
public abstract fun subscribe (Lkotlin/jvm/functions/Function1;)V
public abstract fun unsubscribe (Lkotlin/jvm/functions/Function1;)V
public abstract interface class com/arkivanov/decompose/router/stack/StackNavigationSource : com/arkivanov/decompose/router/children/NavigationSource {
}

public final class com/arkivanov/decompose/router/stack/StackNavigationSource$Event {
Expand Down Expand Up @@ -220,19 +216,11 @@ public final class com/arkivanov/decompose/router/stack/StackNavigatorExtKt {
public static synthetic fun replaceCurrent$default (Lcom/arkivanov/decompose/router/stack/StackNavigator;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)V
}

public final class com/arkivanov/decompose/router/stack/StackSaverImpl$SavedEntry$Creator : android/os/Parcelable$Creator {
public fun <init> ()V
public final fun createFromParcel (Landroid/os/Parcel;)Lcom/arkivanov/decompose/router/stack/StackSaverImpl$SavedEntry;
public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object;
public final fun newArray (I)[Lcom/arkivanov/decompose/router/stack/StackSaverImpl$SavedEntry;
public synthetic fun newArray (I)[Ljava/lang/Object;
}

public final class com/arkivanov/decompose/router/stack/StackSaverImpl$SavedState$Creator : android/os/Parcelable$Creator {
public final class com/arkivanov/decompose/router/stack/StackSavedNavState$Creator : android/os/Parcelable$Creator {
public fun <init> ()V
public final fun createFromParcel (Landroid/os/Parcel;)Lcom/arkivanov/decompose/router/stack/StackSaverImpl$SavedState;
public final fun createFromParcel (Landroid/os/Parcel;)Lcom/arkivanov/decompose/router/stack/StackSavedNavState;
public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object;
public final fun newArray (I)[Lcom/arkivanov/decompose/router/stack/StackSaverImpl$SavedState;
public final fun newArray (I)[Lcom/arkivanov/decompose/router/stack/StackSavedNavState;
public synthetic fun newArray (I)[Ljava/lang/Object;
}

Expand Down
8 changes: 2 additions & 6 deletions decompose/api/jvm/decompose.api
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,7 @@ public final class com/arkivanov/decompose/router/overlay/OverlayNavigationFacto
public static final fun OverlayNavigation ()Lcom/arkivanov/decompose/router/overlay/OverlayNavigation;
}

public abstract interface class com/arkivanov/decompose/router/overlay/OverlayNavigationSource {
public abstract fun subscribe (Lkotlin/jvm/functions/Function1;)V
public abstract fun unsubscribe (Lkotlin/jvm/functions/Function1;)V
public abstract interface class com/arkivanov/decompose/router/overlay/OverlayNavigationSource : com/arkivanov/decompose/router/children/NavigationSource {
}

public final class com/arkivanov/decompose/router/overlay/OverlayNavigationSource$Event {
Expand Down Expand Up @@ -167,9 +165,7 @@ public final class com/arkivanov/decompose/router/stack/StackNavigationFactoryKt
public static final fun StackNavigation ()Lcom/arkivanov/decompose/router/stack/StackNavigation;
}

public abstract interface class com/arkivanov/decompose/router/stack/StackNavigationSource {
public abstract fun subscribe (Lkotlin/jvm/functions/Function1;)V
public abstract fun unsubscribe (Lkotlin/jvm/functions/Function1;)V
public abstract interface class com/arkivanov/decompose/router/stack/StackNavigationSource : com/arkivanov/decompose/router/children/NavigationSource {
}

public final class com/arkivanov/decompose/router/stack/StackNavigationSource$Event {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.arkivanov.decompose

internal class GettingList<out T>(
override val size: Int,
private val get: (Int) -> T,
) : AbstractList<T>() {

override fun get(index: Int): T =
get.invoke(index)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.arkivanov.decompose.router.children

import com.arkivanov.decompose.Child
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.ExperimentalDecomposeApi
import com.arkivanov.decompose.backhandler.child
import com.arkivanov.decompose.value.MutableValue
import com.arkivanov.decompose.value.Value
Expand Down Expand Up @@ -52,6 +53,7 @@ import com.arkivanov.essenty.statekeeper.consume
* @param childFactory a factory function that creates new child component instances.
* @return an observable [Value] of the resulting children state.
*/
@ExperimentalDecomposeApi
fun <C : Any, T : Any, E : Any, N : NavState<C>, S : Any> ComponentContext.children(
source: NavigationSource<E>,
key: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,12 @@ internal class ChildrenNavigator<out C : Any, out T : Any, N : NavState<C>>(
}

private fun switch(newStates: List<ChildNavState<C>>) {
val newConfigurations = newStates.mapTo(HashSet(), ChildNavState<C>::configuration)
check(newConfigurations.size == newStates.size) { "Configurations must be unique" }

val oldItems = items.associateBy(ChildItem<C, *>::configuration)
val newItems = prepareNewItems(newStates = newStates, oldItems = oldItems)
destroyOldItems(newStates = newStates, oldItems = oldItems.values)
destroyOldItems(newConfigurations = newConfigurations, oldItems = oldItems.values)
processNewItems(newItems = newItems)
}

Expand Down Expand Up @@ -176,13 +179,12 @@ internal class ChildrenNavigator<out C : Any, out T : Any, N : NavState<C>>(
}

private fun destroyOldItems(
newStates: List<ChildNavState<C>>,
newConfigurations: Set<C>,
oldItems: Collection<ChildItem<C, T>>,
) {
val newStatesSet = newStates.mapTo(HashSet(), ChildNavState<C>::configuration)
for (item in oldItems) {
val child = item as? Created ?: continue
if (item.configuration !in newStatesSet) {
if (item.configuration !in newConfigurations) {
child.backHandler.stop()
child.instanceKeeperDispatcher.destroy()
child.lifecycleRegistry.destroy()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,14 @@ package com.arkivanov.decompose.router.overlay

import com.arkivanov.decompose.Child
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.Optional
import com.arkivanov.decompose.instancekeeper.attachTo
import com.arkivanov.decompose.optionalOf
import com.arkivanov.decompose.router.stack.StackNavigationSource
import com.arkivanov.decompose.router.stack.childStack
import com.arkivanov.decompose.ExperimentalDecomposeApi
import com.arkivanov.decompose.router.children.ChildNavState.Status
import com.arkivanov.decompose.router.children.NavState
import com.arkivanov.decompose.router.children.SimpleChildNavState
import com.arkivanov.decompose.router.children.children
import com.arkivanov.decompose.value.Value
import com.arkivanov.decompose.value.operator.map
import com.arkivanov.essenty.instancekeeper.InstanceKeeperDispatcher
import com.arkivanov.essenty.parcelable.Parcelable
import com.arkivanov.essenty.parcelable.ParcelableContainer
import com.arkivanov.essenty.statekeeper.StateKeeperDispatcher
import kotlin.reflect.KClass

/**
Expand All @@ -30,6 +27,7 @@ import kotlin.reflect.KClass
* @param childFactory a factory function that creates new child instances.
* @return an observable [Value] of [ChildOverlay].
*/
@OptIn(ExperimentalDecomposeApi::class)
fun <C : Parcelable, T : Any> ComponentContext.childOverlay(
source: OverlayNavigationSource<C>,
configurationClass: KClass<out C>,
Expand All @@ -39,25 +37,36 @@ fun <C : Parcelable, T : Any> ComponentContext.childOverlay(
handleBackButton: Boolean = false,
childFactory: (configuration: C, ComponentContext) -> T,
): Value<ChildOverlay<C, T>> =
childStack(
lifecycle = lifecycle,
stateKeeper = if (persistent) stateKeeper else StateKeeperDispatcher(),
instanceKeeper = if (persistent) instanceKeeper else InstanceKeeperDispatcher().attachTo(lifecycle),
backHandler = backHandler,
source = StackNavigationSourceImpl(source),
initialStack = { configurationStack(initialConfiguration()) },
saveConfiguration = { ParcelableContainer(it.value) },
restoreConfiguration = { optionalOf(it.consume(configurationClass)) },
children(
source = source,
key = key,
handleBackButton = handleBackButton,
childFactory = { configuration, componentContext ->
if (configuration.value != null) {
optionalOf(childFactory(configuration.value, componentContext))
initialNavState = { OverlayNavState(configuration = initialConfiguration()) },
saveNavState = { navState -> ParcelableContainer(navState.configuration?.takeIf { persistent }) },
restoreNavState = { container -> OverlayNavState(container.consume(configurationClass)) },
navTransformer = { navState, event -> OverlayNavState(configuration = event.transformer(navState.configuration)) },
onEventComplete = { event, newNavState, oldNavState -> event.onComplete(newNavState.configuration, oldNavState.configuration) },
backTransformer = { navState ->
if (handleBackButton && (navState.configuration != null)) {
{ OverlayNavState(configuration = null) }
} else {
optionalOf()
null
}
},
).map { it.active.toChildOverlay() }
stateMapper = { _, children -> ChildOverlay(overlay = children.firstOrNull() as? Child.Created?) },
childFactory = childFactory,
)

private data class OverlayNavState<out C : Any>(
val configuration: C?,
) : NavState<C> {

override val children: List<SimpleChildNavState<C>> =
if (configuration == null) {
emptyList()
} else {
listOf(SimpleChildNavState(configuration = configuration, status = Status.ACTIVE))
}
}

/**
* A convenience extension function for [ComponentContext.childOverlay].
Expand All @@ -79,47 +88,3 @@ inline fun <reified C : Parcelable, T : Any> ComponentContext.childOverlay(
handleBackButton = handleBackButton,
childFactory = childFactory,
)

private fun <C : Parcelable, T : Any> Child.Created<Optional<C>, Optional<T>>.toChildOverlay(): ChildOverlay<C, T> =
if ((configuration.value != null) && (instance.value != null)) {
ChildOverlay(
overlay = Child.Created(
configuration = configuration.value,
instance = instance.value,
)
)
} else {
ChildOverlay()
}

private fun <C : Any> configurationStack(configuration: C?): List<Optional<C>> =
listOfNotNull(optionalOf(), configuration?.let(::optionalOf))

private class StackNavigationSourceImpl<C : Parcelable>(
private val delegate: OverlayNavigationSource<C>,
) : StackNavigationSource<Optional<C>> {

private var map = HashMap<(StackNavigationSource.Event<Optional<C>>) -> Unit, (OverlayNavigationSource.Event<C>) -> Unit>()

override fun subscribe(observer: (StackNavigationSource.Event<Optional<C>>) -> Unit) {
check(observer !in map)

val sourceObserver: (OverlayNavigationSource.Event<C>) -> Unit = { observer(it.toStackEvent()) }
map += observer to sourceObserver
delegate.subscribe(sourceObserver)
}

private fun OverlayNavigationSource.Event<C>.toStackEvent(): StackNavigationSource.Event<Optional<C>> =
StackNavigationSource.Event(
transformer = { stack -> configurationStack(transformer(stack.last().value)) },
onComplete = { newStack, oldStack ->
onComplete(newStack.lastOrNull()?.value, oldStack.lastOrNull()?.value)
},
)

override fun unsubscribe(observer: (StackNavigationSource.Event<Optional<C>>) -> Unit) {
map.remove(observer)?.also {
delegate.unsubscribe(it)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
package com.arkivanov.decompose.router.overlay

import com.arkivanov.decompose.router.children.NavigationSource

/**
* Represents a source of navigation events for `Child Overlay`.
*
* @see OverlayNavigator
*/
interface OverlayNavigationSource<C : Any> {

fun subscribe(observer: (Event<C>) -> Unit)

fun unsubscribe(observer: (Event<C>) -> Unit)
interface OverlayNavigationSource<C : Any> : NavigationSource<OverlayNavigationSource.Event<C>> {

class Event<C : Any>(
val transformer: (configuration: C?) -> C?,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.arkivanov.decompose.router.stack

import com.arkivanov.decompose.Child
import com.arkivanov.decompose.GettingList

/**
* A state holder for `Child Stack`.
Expand All @@ -20,19 +21,8 @@ data class ChildStack<out C : Any, out T : Any>(
/**
* Returns the full stack of component configurations, ordered from tail to head.
*/
val items: List<Child<C, T>> = Items(active = active, backStack = backStack)

private class Items<out C : Any, out T : Any>(
private val active: Child.Created<C, T>,
private val backStack: List<Child<C, T>> = emptyList(),
) : AbstractList<Child<C, T>>() {
override val size: Int get() = backStack.size + 1

override fun get(index: Int): Child<C, T> =
when {
(index < 0) || (index >= size) -> throw IndexOutOfBoundsException("Index is out of bounds: index=$index, size=$size")
index < backStack.size -> backStack[index]
else -> active
}
}
val items: List<Child<C, T>> =
GettingList(size = backStack.size + 1) { index ->
backStack.getOrNull(index) ?: active
}
}

This file was deleted.

Loading