Skip to content

Commit

Permalink
Documentation for ChildStack
Browse files Browse the repository at this point in the history
  • Loading branch information
arkivanov committed Jul 15, 2022
1 parent 98f605f commit 22e0bb3
Show file tree
Hide file tree
Showing 13 changed files with 158 additions and 128 deletions.
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
# Web Browser History

By default `Router` navigation does not affect URLs in the browser address bar. But sometimes it is necessary to have different URLs for
different `Router` 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/webhistory/DefaultWebHistoryController.kt).
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).

The controller listens for the `Router` state changes and updates the browser URL and the history accordingly:
The controller listens for the `Child Stack` state changes and updates the browser URL and the history accordingly:

- When one or more components are pushed to the `Router` stack, the controller pushes corresponding pages to the history
- When one or more components are pushed to the `Child Stack` stack, the controller pushes corresponding pages to the history
- When one or more components are popped from the stack, the controller pops corresponding pages from the history
- When some components are replaced in the stack, the controller tries its best to keep the page history aligned (there are corner cases)
- When the user presses the browser's Back button (or selects one of the previous pages in the history dropdown menu), the controller
pops the corresponding configurations from the `Router`
- When the user navigates forward in the browser history, the controller pushes the corresponding configurations to the `Router`
pops the corresponding configurations from the `Child Stack`
- When the user navigates forward in the browser history, the controller pushes the corresponding configurations to the `Child Stack`

## Corner cases

Expand All @@ -21,7 +21,7 @@ removed configurations will be pushed back to the stack (the stack will become `

## Limitations

Only one `Router` can be attached to an instance of the `WebHistoryController`. Having multiple instances of the controller is not allowed.
Only one `Child Stack` can be attached to an instance of the `WebHistoryController`. Having multiple instances of the controller is not allowed.

## Configuring the application

Expand Down Expand Up @@ -51,7 +51,7 @@ config.devServer = {

Using `WebHistoryController` is easy:

1. Create a new instance of `WebHistoryController` in the JS app and pass it via constructor to a component responsible for
1. Create an instance of `DefaultWebHistoryController` in the JS app and pass it via constructor to a component responsible for
navigation (typically it is the root component)
2. In the component, call the `WebHistoryController.attach` method and supply all arguments
3. In the JS app, pass an initial deeplink to the component
Expand Down
11 changes: 7 additions & 4 deletions docs/router/deeplinking.md → docs/child-stack/deeplinking.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ documentation. For example here is the [related documentation](https://developer

### Handling deep links

Given the basic example from the [Router overview](https://arkivanov.github.io/Decompose/router/overview) page, we can easily handle deep
Given the basic example from the [Child Stack overview](overview) page, we can easily handle deep
links. Let's say we have a link like `http://myitems.com?itemId=3`. When the user clicks on it, we want to open the details screen of the
item with the provided `id`. When the user closes the details screen, they should be navigated back to the list screen. The idea is to pass
parsed data from the deep link to a component responsible for navigation, in our case it is the `Root` component.
Expand All @@ -20,16 +20,19 @@ class RootComponent(
initialItemId: Long? = null
) : Root, ComponentContext by componentContext {

private val router =
router<Config, Root.Child>(
private val navigation = StackNavigation<Config>()

private val stack =
childStack(
source = navigation,
initialStack = {
listOfNotNull(
Config.List,
if (initialItemId != null) Config.Details(itemId = initialItemId) else null,
)
},
handleBackButton = true,
childFactory = ::createChild
childFactory = ::createChild,
)

// Omitted code
Expand Down
32 changes: 17 additions & 15 deletions docs/router/navigation.md → docs/child-stack/navigation.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Navigation

## The Router
## The StackNavigator

All navigation in Decompose is done through the [`Router`](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/Router.kt) interface. There is `navigate(transformer: (List<C>) -> List<C>, onComplete: (newStack: List<C>, oldStack: List<C>) -> Unit)` method with two arguments:
All navigation in Decompose is done through the [`StackNavigator`](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/StackNavigator.kt) interface, which is implemented by `StackNavigation`. There is `navigate(transformer: (List<C>) -> List<C>, onComplete: (newStack: List<C>, oldStack: List<C>) -> Unit)` method with two arguments:

- `transformer` - converts the current stack of configurations into a new one. The stack is represented as `List`, where the last element is the top of the stack, and the first element is the bottom of the stack.
- `onComplete` - called when navigation is finished.
Expand All @@ -14,15 +14,15 @@ There is also `navigate(transformer: (stack: List<C>) -> List<C>)` extension fun

### The navigation process

During the navigation process, the `Router` compares the new stack of configurations with the previous one. The `Router` ensures that all removed components are destroyed, and that there is only one component resumed at a time - the top one. All components in the back stack are always either stopped or destroyed.
During the navigation process, the `Child Stack` compares the new stack of configurations with the previous one. The `Child Stack` ensures that all removed components are destroyed, and that there is only one component resumed at a time - the top one. All components in the back stack are always either stopped or destroyed.

The `Router` 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. `pop` is called from `onResume` lifecycle callback of a component being pushed. All recursive invocations are queued and performed one by one once the current navigation is finished.
The `Child Stack` 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. `pop` is called from `onResume` lifecycle callback of a component being pushed. All recursive invocations are queued and performed one by one once the current navigation is finished.

## Router extension functions
## StackNavigator extension functions

There are `Router` [extension functions](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/RouterExt.kt) that provide conveniences for navigating, some of which were already used in the [router overview example](../overview/#routing-example).
There are `StackNavigator` [extension functions](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/StackNavigatorExt.kt) that provide conveniences for navigating, some of which were already used in the [Child Stack overview example](overview#routing-example).

The preceding examples will utilize the following `sealed class` & `router` for showcasing the usage of the `Router` extensions.
The preceding examples will utilize the following `sealed class` & `navigation` for showcasing the usage of the `StackNavigator` extensions.

```kotlin
sealed class Configuration {
Expand All @@ -32,16 +32,16 @@ sealed class Configuration {
object D : Configuration()
}

val router: Router<Configuration>
val navigation = StackNavigation<Configuration>()
```

### Push

Pushes the provided `Configuration` at the top of the stack.

```kotlin
router.push(Configuration.B)
router.push(Configuration.C)
navigation.push(Configuration.B)
navigation.push(Configuration.C)
```

![](../media/RouterPush.png)
Expand All @@ -51,11 +51,13 @@ router.push(Configuration.C)
Pops the latest configuration at the top of the stack.

```kotlin
router.pop()
navigation.pop()
```

Or

```kotlin
router.pop { isSuccess ->
navigation.pop { isSuccess ->
// Called when the navigation is finished.
// isSuccess - `true` if the stack size was greater than 1 and a component was popped, `false` otherwise.
}
Expand All @@ -68,7 +70,7 @@ router.pop { isSuccess ->
Drops the configurations at the top of the stack while the provided predicate returns true.

```kotlin
router.popWhile { topOfStack: Configuration -> topOfStack !is B }
navigation.popWhile { topOfStack: Configuration -> topOfStack !is B }
```

![](../media/RouterPopWhile.png)
Expand All @@ -78,7 +80,7 @@ router.popWhile { topOfStack: Configuration -> topOfStack !is B }
Replaces the current configuration at the top of the stack with the provided `Configuration`.

```kotlin
router.replaceCurrent(Configuration.D)
navigation.replaceCurrent(Configuration.D)
```

![](../media/RouterReplaceCurrent.png)
Expand All @@ -91,7 +93,7 @@ Removes all components with configurations of the provided `Configuration`'s cla
The operation is performed as one transaction. If there is already a component with the same configuration, it will not be recreated.

```kotlin
router.bringToFront(Configuration.B)
navigation.bringToFront(Configuration.B)
```

![](../media/RouterBringToFront.png)
Loading

0 comments on commit 22e0bb3

Please sign in to comment.