Skip to content

Commit

Permalink
Merge pull request #218 from arkivanov/navigation-docs
Browse files Browse the repository at this point in the history
Navigation docs
  • Loading branch information
arkivanov authored Oct 2, 2022
2 parents 9a0ce55 + 0906022 commit 74e8cf7
Show file tree
Hide file tree
Showing 14 changed files with 212 additions and 62 deletions.
4 changes: 3 additions & 1 deletion docs/component/back-button.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ Some devices (e.g. Android) have hardware back buttons. A very common use case i

## Navigation with back button

`Child Stack` can automatically navigate back when the back button is pressed. All you need to do is to supply the `handleBackButton=true` argument when you initialize the `ChildStack`. Please see the related [documentation page](https://arkivanov.github.io/Decompose/child-stack/overview/) for more information.
`Child Stack` can automatically navigate back when the back button is pressed. All you need to do is to supply the `handleBackButton=true` argument when you initialize the `ChildStack`. Please see the [Child Stack](/Decompose/navigation/stack/overview/) documentation page for more information.

Similarly, `Child Overlay` can automatically dismiss the child component when the back button is pressed. see the [Child Overlay](/Decompose/navigation/overlay/overview/) documentation page for more information.

## Manual back button handling

Expand Down
10 changes: 5 additions & 5 deletions docs/component/child-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@

Decompose provides ability to organize components as trees, so each parent component is only aware of its immediate children. Hence the name of the library - "Decompose". You decompose your project by multiple independent reusable components. When adding a subtree into another place (reusing), you only need to satisfy its top component's dependencies.

There are two common ways to add a child component:
There are two common ways to work with child components:

- Using `Child Stack` - prefer this option when a stack navigation between components is required. Please head to
the [Child Stack documentation page](https://arkivanov.github.io/Decompose/child-stack/overview/) for more
- Navigation - when you need to dynamically switch child components. Please head to
the [Navigation](/Decompose/navigation/overview/) documentation page for more
information.
- Manually - prefer this option if you need to add a permanent child component, or to manually control its `Lifecycle`.
- Manually - when you need to add a permanent child component, or to manually control its `Lifecycle`.

## Adding a child component manually

A permanent child component should be always instantiated during the initialisation of the parent, and it is automatically destroyed at the end of the parent's lifecycle. It is possible to manually control the lifecycle of a permanent child component, e.g. resume it, pause or stop. But permanent child components must never be destroyed manually.

!!!warning
Every child component needs its own `ComponentContext`. Never pass parent's `ComponentContext` to children, always use either the `Child Stack` or the `childContext(...)` function.
Every child component needs its own `ComponentContext`. Never pass parent's `ComponentContext` to children, always use either the navigation or the `childContext(...)` function.

A child `ComponentContext` can be created using the following extension function:

Expand Down
4 changes: 2 additions & 2 deletions docs/component/overview.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Component Overview

A component is just a normal class that encapsulates some logic and possibly another (child) components. Every component has its own lifecycle, which is automatically managed by Decompose. So everything encapsulated by a component is scoped. Please head to the [Lifecycle documentation page](https://arkivanov.github.io/Decompose/component/lifecycle/) for more information.
A component is just a normal class that encapsulates some logic and possibly another (child) components. Every component has its own lifecycle, which is automatically managed by Decompose. So everything encapsulated by a component is scoped. Please head to the [Lifecycle documentation page](/Decompose/component/lifecycle/) for more information.

UI is optional and is pluggable from outside of components. Components do not depend on UI, the UI depends on components.

Expand Down Expand Up @@ -139,7 +139,7 @@ fun main() {

Using `Value` is not mandatory, you can use any other state holders, e.g. [StateFlow](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-state-flow/), [State](https://developer.android.com/jetpack/compose/state), [Observable](https://github.com/badoo/Reaktive), [LiveData](https://developer.android.com/topic/libraries/architecture/livedata), etc.

If you are using Jetpack/JetBrains Compose, `Value` can be observed in Composable functions using one of the Compose [extension modules](https://arkivanov.github.io/Decompose/extensions/compose/).
If you are using Jetpack/JetBrains Compose, `Value` can be observed in Composable functions using one of the Compose [extension modules](/Decompose/extensions/compose/).

!!!warning
`Value` is not thread-safe, it should be accessed only from the main thread.
Expand Down
2 changes: 1 addition & 1 deletion docs/extensions/compose.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ fun main() {

### Navigating between Composable components

The [Child Stack](https://arkivanov.github.io/Decompose/child-stack/overview/) feature provides [ChildStack](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/ChildStack.kt) as `Value<ChildStack>` that can be observed in a `Composable` component. This makes it possible to switch child `Composable` components following the `ChildStack` changes.
The [Child Stack](/Decompose/navigation/stack/overview/) feature provides [ChildStack](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/ChildStack.kt) as `Value<ChildStack>` that can be observed in a `Composable` component. This makes it possible to switch child `Composable` components following the `ChildStack` changes.

Both Compose extension modules provide the [Children(...)](https://github.com/arkivanov/Decompose/blob/master/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/stack/Children.kt) function which has the following features:

Expand Down
4 changes: 2 additions & 2 deletions docs/extensions/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

Decompose provides extension modules for various popular libraries and frameworks:

- [Extensions for Jetpack/JetBrains Compose](https://arkivanov.github.io/Decompose/extensions/compose/)
- [Extensions for Android views](https://arkivanov.github.io/Decompose/extensions/android/)
- [Extensions for Jetpack/JetBrains Compose](/Decompose/extensions/compose/)
- [Extensions for Android views](/Decompose/extensions/android/)
6 changes: 3 additions & 3 deletions docs/getting-started/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Decompose provides a number of modules, they are all published to Maven Central

## The main Decompose module

The main functionality is provided by the `decompose` module. It contains the core functionality, like [Child Stack](https://arkivanov.github.io/Decompose/child-stack/overview/), [ComponentContext](https://arkivanov.github.io/Decompose/component/overview/#componentcontext), etc.
The main functionality is provided by the `decompose` module. It contains some core features like [ComponentContext](/Decompose/component/overview/#componentcontext), [Child Stack](/Decompose/navigation/stack/overview/), [Child Overlay](/Decompose/navigation/overlay/overview/), etc.

### Gradle setup

Expand Down Expand Up @@ -34,7 +34,7 @@ Due to this fragmentation Decompose provides two separate extension modules for
- `extensions-compose-jetpack` - Android library for Jetpack Compose
- `extensions-compose-jetbrains` - Kotlin Multiplatform library for JetBrains Compose, supports `android` and `jvm` targets

Both modules are used to connect Compose UI to Decompose components. Please see the corresponding [documentation page](https://arkivanov.github.io/Decompose/extensions/compose/).
Both modules are used to connect Compose UI to Decompose components. Please see the corresponding [documentation page](/Decompose/extensions/compose/).

### Gradle setup

Expand All @@ -58,7 +58,7 @@ Typically only one module should be selected, depending on the Compose UI varian

## Extensions for Android views

The `extensions-android` module provides extensions to connect Android views based UI to Decompose components. Please head to the corresponding [documentation page](https://arkivanov.github.io/Decompose/extensions/android/) for more information.
The `extensions-android` module provides extensions to connect Android views based UI to Decompose components. Please head to the corresponding [documentation page](/Decompose/extensions/android/) for more information.

### Gradle setup

Expand Down
42 changes: 42 additions & 0 deletions docs/navigation/overlay/navigation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Navigation with Child Overlay

## The OverlayNavigator

All navigation in `Child Overlay` is performed using the [`OverlayNavigator`](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/overlay/OverlayNavigator.kt) interface, which is extended by the `OverlayNavigation` interface.

`OverlayNavigator` contains the `navigate` method with the following arguments:

- `transformer` - converts the current configuration (if any) into a new one or `null`.
- `onComplete` - called when navigation is finished.

There is also `navigate` extension function without the `onComplete` callback, for convenience.

```kotlin title="Creating the navigation"
val navigation = OverlayNavigation<DialogConfig>()
```

### The navigation process

During the navigation process, `Child Overlay` compares the new configuration with the previous one. If both are the same, then no navigation is performed. Otherwise, the currently active component is destroyed (if any), and a new one is activated (if the new configuration is not `null`).

`Child Overlay` usually performs the navigation synchronously, which means that by the time the `navigate` method returns, the navigation is finished and all component lifecycles are moved into required states. However, the navigation is performed asynchronously in case of recursive invocations - e.g. `dismiss` is called from `onResume` lifecycle callback of a component being activated. All recursive invocations are queued and performed one by one once the current navigation is finished.

## OverlayNavigator extension functions

There are `OverlayNavigator` [extension functions](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/overlay/OverlayNavigatorExt.kt) to simplify the navigation.

### activate

Activates a component with the provided `Configuration` (if not `null`). Any currently active component is destroyed.

```kotlin
navigation.activate(DialogConfig(title = "Some title"))
```

### dismiss

Destroys the currently active component, if any.

```kotlin
navigation.dismiss()
```
83 changes: 83 additions & 0 deletions docs/navigation/overlay/overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Child Overlay overview

## The Child Overlay

`Child Overlay` is a navigation model with either one active component at a time, or none. In other words, each `Child Overlay` allows to activate a child component, replace with another child component, or dismiss when not needed. It is possible to have more than one `Child Overlay` in a component, nested overlays are also supported.

The `Child Overlay` navigation consists of two main entities:

- [ChildOverlay](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/overlay/ChildOverlay.kt) - a simple data class that holds the currently active child, if any.
- [OverlayNavigation](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/overlay/OverlayNavigation.kt) - an interface that accepts navigation commands and forwards them to all subscribed observers.

### Component Configurations

Each component created and managed by the `Child Overlay` has a configuration, please read the documentation about [child configurations](/Decompose/navigation/overview/#component-configurations-and-child-factories).

### Initializing the Child Overlay

There are three steps to initialize the `Child Overlay`:

- Create a new instance of `OverlayNavigation` and assign it to a variable or a property.
- Initialize the `Child Overlay` using the `ComponentContext#childOverlay` extension function and pass `OverlayNavigation` into it along with other arguments.
- The `childOverlay` function returns `Value<ChildOverlay>` that can be observed in the UI. Assign the returned `Value` to another property or a variable.

## Example

Here is a very basic example of a child overlay:

```kotlin title="Dialog component"
interface DialogComponent {

fun onDismissClicked()
}

class DefaultDialogComponent(
private val componentContext: ComponentContext,
private val message: String,
private val onDismissed: () -> Unit,
) : DialogComponent, ComponentContext by componentContext {

override fun onDismissClicked() {
onDismissed()
}
}
```

```kotlin title="Root component"
interface RootComponent {

val dialog: Value<ChildOverlay<*, DialogComponent>>
}

class DefaultRootComponent(
componentContext: ComponentContext,
) : RootComponent, ComponentContext by componentContext {

private val dialogNavigation = OverlayNavigation<DialogConfig>()

private val _dialog =
childOverlay(
source = dialogNavigation,
key = "Dialog",
// persistent = false, // Disable navigation state saving, if needed
handleBackButton = true, // Close the dialog on back button press
) { config, componentContext ->
DefaultDialogComponent(
componentContext = componentContext,
message = config.message,
onDismissed = dialogNavigation::dismiss,
)
}

override val dialog: Value<ChildOverlay<*, DialogComponent>> = _dialog

private fun showDialog(message: String) {
dialogNavigation.activate(DialogConfig(message = message))
}

@Parcelize
private data class DialogConfig(
val message: String,
) : Parcelable
}
```
33 changes: 33 additions & 0 deletions docs/navigation/overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Navigation overview

Decompose provides the ability to create [permanent child components](/Decompose/navigation/stack/overview/) using the `childStack` extension function. But if you need to dynamically switch child components, then navigation comes in handy.

Currently, Decompose provides two ways to navigate:

- [Child Stack](/Decompose/navigation/stack/overview/) - prefer this way if you need to organize child components in a stack and navigate between them.
- [Child Overlay](/Decompose/navigation/overlay/overview/) - prefer this way if you need to activate-dismiss a child component.

## Component configurations and child factories

The term `Configuration` is widely used in Decompose navigation. A configuration is a persistent class that represents a child component and contains all its arguments (not dependencies). Decompose automatically persists child configurations using [StateKeeper](/Decompose/component/state-preservation/), so child components are automatically recreated on events like Android configuration changes, process death, etc.

Usually, you initialize a navigation by supplying a child factory function to Decompose. The function accepts a child configuration and `ComponentContext` and returns a new instance of the corresponding child component - `(Config, ComponentContext) -> Child`. When you need to navigate, you call a navigation method and pass a configuration there. Decompose automatically creates and manages a [ComponentContext](/Decompose/component/overview/#componentcontext) for every child component, and calls the provided factory function when a new instance of a child component is required. This is where you should instantiate child components and supply dependencies, the configuration only provides persistent arguments and is used to distinguish which component to create.

### Configuration requirements

Configurations must meet the following requirements:

1. Be immutable
2. [Correctly](https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#hashCode--) implement `equals()` and `hashCode()` methods
3. Implement `Parcelable` interface

Different kinds of navigation may have additional requirements for configurations. It's recommended to define configurations as `data class`, and use only `val` properties and immutable data structures.

### Configurations are Parcelable

`Configurations` can be persisted via Android's [saved state](https://developer.android.com/guide/components/activities/activity-lifecycle#save-simple,-lightweight-ui-state-using-onsaveinstancestate), thus allowing the navigation state to be restored after configuration changes or process death.

Decompose uses [Essenty](https://github.com/arkivanov/Essenty) library, which provides both `Parcelable` interface and `@Parcelize` annotation in common code using expect/actual, which works well with Kotlin Multiplatform. Please familiarise yourself with Essenty library.

!!!warning
On Android the amount of data that can be preserved is [limited](https://developer.android.com/guide/components/activities/parcelables-and-bundles). Please mind the size of configurations.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Web Browser History
# Web browser history

By default `Child Stack` navigation does not affect URLs in the browser address bar. But sometimes it is necessary to have different URLs for
different `Child Stack` destinations. For this purpose Decompose provides an **experimental** API - [WebHistoryController](https://github.com/arkivanov/Decompose/blob/master/decompose/src/jsMain/kotlin/com/arkivanov/decompose/router/stack/webhistory/DefaultWebHistoryController.kt).
Expand Down
File renamed without changes.
Loading

0 comments on commit 74e8cf7

Please sign in to comment.