Skip to content

Commit

Permalink
Added deep linking doc
Browse files Browse the repository at this point in the history
  • Loading branch information
arkivanov committed May 12, 2021
1 parent c85559b commit 3ece93f
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 26 deletions.
36 changes: 36 additions & 0 deletions docs/router/deeplinking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Deep linking

Users following links on devices have one goal in mind: to get to the content they want to see. Decompose provides ability to override
initial destinations and back stack. A typical approach is to parse deep links on the platform side and then pass the initial data to the
root component and then down the tree to all the required components.

Parsing deep links on the platform side is beyond this documentation. This information should be available in the platform's specific
documentation. For example here is the [related documentation](https://developer.android.com/training/app-links/deep-linking) for Android.

### Handling deep links

Given the basic example from the [Router overview](https://arkivanov.github.io/Decompose/router/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.

```kotlin
class RootComponent(
componentContext: ComponentContext,
initialItemId: Long? = null
) : Root, ComponentContext by componentContext {

private val router =
router<Config, Root.Child>(
initialConfiguration = if (initialItemId == null) Config.List else Config.Details(itemId = initialItemId),
initialBackStack = if (initialItemId == null) emptyList() else listOf(Config.List),
handleBackButton = true,
childFactory = ::createChild
)

// Omitted code
}
```

Now, if the `initialItemId` is supplied, the first screen will be the `ItemDetails` component. The `ItemList` component will be in the back
stack, so the user will be able to go back.
120 changes: 94 additions & 26 deletions docs/router/overview.md
Original file line number Diff line number Diff line change
@@ -1,59 +1,127 @@
# Router Overview
# Router Overview

## The Router

A key unit is the [Router](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/Router.kt). It is responsible for managing components, just like `FragmentManager`.
A key unit is
the [Router](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/Router.kt). It is
responsible for managing components, just like `FragmentManager`.

The `Router` supports back stack and so each component has its own `Lifecycle`. Each time a new component is pushed, the currently active component is stopped. When a component is popped from the back stack, the previous component is resumed. This allows business logic to run while the component is in the back stack.
The `Router` supports back stack and so each component has its own `Lifecycle`. Each time a new component is pushed, the currently active
component is stopped. When a component is popped from the back stack, the previous component is resumed. This allows business logic to run
while the component is in the back stack.

Each component is created based on an associated `Configuration`. `Configurations` can be persisted via Android's `saved state`, thus allowing back stack restoration after configurations change or process death. When the back stack is restored, only currently active components are recreated. All others in the back stack remain destroyed, and recreated on demand when navigating back. Decompose defines both `Parcelable` interface and `@Parcelize` annotation in common code using expect/actual, which works well with Kotlin Multiplatform. You can read more about it [here](https://kotlinlang.org/docs/reference/compiler-plugins.html#parcelable-implementations-generator).
Each component is created based on an associated `Configuration`. `Configurations` can be persisted via Android's `saved state`, thus
allowing back stack restoration after configurations change or process death. When the back stack is restored, only currently active
components are recreated. All others in the back stack remain destroyed, and recreated on demand when navigating back. Decompose defines
both `Parcelable` interface and `@Parcelize` annotation in common code using expect/actual, which works well with Kotlin Multiplatform. You
can read more about it [here](https://kotlinlang.org/docs/reference/compiler-plugins.html#parcelable-implementations-generator).

The `Router` has a state consisting of a currently active component and a back stack, so it can be rendered as any other state.

`Routers` can be nested, and each component can have more than one `Router`.

## Routing example

Here is a very basic example of navigation between two children components:
Here is a very basic example of navigation between two child components:

```kotlin
class Child1(componentContext: ComponentContext) : ComponentContext by componentContext {
// omitted code
// ItemList component

interface ItemList {

// Omitted code

fun onItemClicked(id: Long)
}

class Child2(componentContext: ComponentContext, data: String) : ComponentContext by componentContext {
// omitted code
class ItemListComponent(
componentContext: ComponentContext,
private val onItemSelected: (id: Long) -> Unit
) : ItemList, ComponentContext by componentContext {

// Omitted code

override fun onItemClicked(id: Long) {
onItemSelected(id)
}
}
```

```kotlin
// ItemDetails component

interface ItemDetails {

// Omitted code

fun onCloseClicked()
}

class ItemDetailsComponent(
componentContext: ComponentContext,
itemId: Long,
private val onFinished: () -> Unit
) : ItemDetails, ComponentContext by componentContext {

// Omitted code

override fun onCloseClicked() {
onFinished()
}
}
```

```kotlin
// Root component

interface Root {

val routerState: Value<RouterState<*, Child>>

sealed class Child {
class List(val component: ItemList) : Child()
class Details(val component: ItemDetails) : Child()
}
}

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

class Parent(componentContext: ComponentContext) : ComponentContext by componentContext {
private val router =
router<Config, Any>(
initialConfiguration = Config.Child1,
router<Config, Root.Child>(
initialConfiguration = Config.List,
handleBackButton = true,
childFactory = ::createChild
)

val children: Value<RouterState<*, Any>> get() = router.state

private fun createChild(config: Config, componentContext: ComponentContext): Any =
override val routerState: Value<RouterState<*, Root.Child>> = router.state

private fun createChild(config: Config, componentContext: ComponentContext): Root.Child =
when (config) {
is Config.Child1 -> Child1(componentContext)
is Config.Child2 -> Child2(componentContext, data = config.data)
is Config.List -> Root.Child.List(itemList(componentContext))
is Config.Details -> Root.Child.Details(itemDetails(componentContext, config))
}

fun showChild2(data: String) {
router.push(Config.Child2(data = data))
}

fun popChild() {
router.pop()
}
private fun itemList(componentContext: ComponentContext): ItemList =
ItemListComponent(
componentContext = componentContext,
onItemSelected = { router.push(Config.Details(itemId = it)) }
)

private fun itemDetails(componentContext: ComponentContext, config: Config.Details): ItemDetails =
ItemDetailsComponent(
componentContext = componentContext,
itemId = config.itemId,
onFinished = router::pop
)

private sealed class Config : Parcelable {
@Parcelize
object Child1 : Config()
object List : Config()

@Parcelize
class Child2(val data: String) : Config()
data class Details(val itemId: Long) : Config()
}
}
```
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ nav:

- Router:
- Overview: router/overview.md
- Deep linking: router/deeplinking.md


- Extensions:
Expand Down

0 comments on commit 3ece93f

Please sign in to comment.