diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildNavState.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildNavState.kt index 10e72300e..c506aeb45 100644 --- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildNavState.kt +++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildNavState.kt @@ -20,13 +20,15 @@ interface ChildNavState { */ enum class Status { /** - * The child is destroyed but still managed, e.g. it's state may be saved and restored later. + * The child component is instantiated and active. Its maximum lifecycle state is `RESUMED`, + * depending on the parent's lifecycle state. An active component can handle back button presses. + * The state of the component is saved when it switches from `ACTIVE` to any other status. */ DESTROYED, /** - * The child is created but not started. The state is saved when the child switches from - * [ACTIVE] to [INACTIVE]. Back button handling is disabled. + * The child component is instantiated and inactive. Its maximum lifecycle state is `CREATED`, + * depending on the parent's lifecycle state. An inactive component cannot handle back button presses. */ INACTIVE, diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildrenFactory.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildrenFactory.kt index d683f22f3..5a3bce3bf 100644 --- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildrenFactory.kt +++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildrenFactory.kt @@ -24,10 +24,11 @@ import com.arkivanov.essenty.statekeeper.consume * clients. [NavState] represents a persistent state of the navigation. It also holds a navigation * state for each child - [ChildNavState]. Both [NavState] and [ChildNavState] must be immutable, and * correctly implement `equals` and `hashCode` methods (or just be data classes). There must be no - * duplicated (be equality) [ChildNavState.configuration] within a [NavState]. + * duplicated (by equality) [ChildNavState.configuration] within a [NavState]. * * The navigation is performed by transforming the current [NavState] to a new one. The implementation - * calculates diffs between the old and the new [NavState], and manipulates child components as needed. + * calculates diffs between the old list of [ChildNavState] and the new one, and manipulates child + * components as needed. * * @param C a type of component configurations. * @param T a type of components. diff --git a/docs/navigation/children/overview.md b/docs/navigation/children/overview.md new file mode 100644 index 000000000..8ad8263db --- /dev/null +++ b/docs/navigation/children/overview.md @@ -0,0 +1,67 @@ +# Generic Navigation + +The `Generic Navigation` can be used to create custom navigation models, when none of the predefined models fit your needs. It offers a flexible API and allows you to create almost any kind of navigation. Please check out [Child Stack](../stack/overview.md) and [Child Overlay](../overlay/overview.md) before using the `Generic Navigation`. + +The API is based around [NavState](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/NavState.kt) and [ChildNavState](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildNavState.kt) interfaces that should be implemented by clients. `NavState` represents a persistent state of the navigation. It also holds a navigation state for each child - `ChildNavState`. Both `NavState` and `ChildNavState` must be immutable, and correctly implement `equals` and `hashCode` methods (or just be data classes). There must be no duplicated (by equality) `ChildNavState.configuration` within a `NavState`. + +The navigation is performed by transforming the current `NavState` to a new one. The `Generic Navigation` implementation calculates diffs between the old list of `ChildNavState` and the new one, and manipulates child components as needed. + +## ChildNavState + +`ChildNavState` represents a state of a child component. It holds a `Configuration` that works as a key of the child component, and a `Status` that represents the required lifecycle status of the child component. As mentioned earlier, the `Configuration` must unique within the `NavState`. + +The `Status` can be one of the following: + +* `ACTIVE` - the child component is instantiated and active. Its maximum lifecycle state is `RESUMED`, depending on the parent's lifecycle state. An active component can handle back button presses. The state of the component is saved when it switches from `ACTIVE` to any other status. + +* `INACTIVE` - the child component is instantiated and inactive. Its maximum lifecycle state is `CREATED`, depending on the parent's lifecycle state. An inactive component cannot handle back button presses. + +* `DESTROYED` - the child component is destroyed but still managed, e.g. it's state may be saved and restored later. + +If you want to completely remove the child component from the navigation, you should remove its `ChildNavState` from the `NavState` altogether. + +## Using the Generic Navigation + +Using the `Generic Navigation` is pretty similar to any other navigation model, there is [ComponentContext.children(...)](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/children/ChildrenFactory.kt) extension function. + +```kotlin +fun , S : Any> ComponentContext.children( + source: NavigationSource, + key: String, + initialNavState: () -> N, + saveNavState: (navState: N) -> ParcelableContainer, + restoreNavState: (container: ParcelableContainer) -> N, + navTransformer: (navState: N, event: E) -> N, + onEventComplete: (event: E, newNavState: N, oldNavState: N) -> Unit, + backTransformer: (navState: N) -> (() -> N)?, + stateMapper: (navState: N, children: List>) -> S, + childFactory: (configuration: C, componentContext: ComponentContext) -> T, +): Value +``` + +The `children` function has the following type parameters: + +- `C` - a type of component configurations. +- `T` - a type of components. +- `E` - a type of navigation events. +- `N` - a type of navigation state, must implement `NavState` interface. +- `S` - a type of the resulting children state. + +The `children` function accepts the following arguments: + +- `source: NavigationSource` - an observable source of navigation events, the `Generic Navigation` subscribes to the source and performs the navigation. +- `key: String` - a key of the navigation, must be unique if there are multiple `children` used in the same component. +- `initialNavState: () -> N` - an initial navigation state that should be used if there is no previously saved state. +- `saveNavState: (navState: N) -> ParcelableContainer` - a function that saves the provided navigation state into `ParcelableContainer`, called when the hosting component goes to background. +- `restoreNavState: (container: ParcelableContainer) -> N` - a function that restores the navigation state from the provided `ParcelableContainer`. The restored navigation state must have the same amount of child configurations and in the same order. The restored child `Statuses` can be any, e.g. a previously active child may become destroyed, etc. +- `navTransformer: (navState: N, event: E) -> N` - a function that transforms the current navigation state to a new one using the provided navigation event. The implementation diffs both navigation states and manipulates child components as needed. +- `onEventComplete: (event: E, newNavState: N, oldNavState: N) -> Unit` - called when a navigation event is processed and the navigation completed. +- `backTransformer: (navState: N) -> (() -> N)?` - a function that checks the provided navigation state, and either returns another function transforming the navigation state to a new one, or `null` if back button handling should be disabled. Called during the initialisation and after each navigation event. +- `stateMapper: (navState: N, children: List>) -> S` - combines the provided navigation state and list of child components to a resulting custom state. +- `childFactory: (configuration: C, componentContext: ComponentContext) -> T` - childFactory a factory function that creates new child component instances. + +The `children` function returns an observable `Value` of the resulting children state. + +## Examples + +Both [Child Stack](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/ChildStackFactory.kt) and [Child Overlay](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/overlay/ChildOverlayFactory.kt) are implemented using the `Generic Navigation`. Please refer to their source code for implementation details. diff --git a/mkdocs.yml b/mkdocs.yml index 4fa4b4dab..ac287c3d5 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -42,6 +42,8 @@ nav: - Overview: navigation/overlay/overview.md - Navigation: navigation/overlay/navigation.md - Custom ComponentContext: navigation/overlay/component-context.md + - Generic Navigation: + - Overview: navigation/children/overview.md - Extensions: - Overview: extensions/overview.md