-
-
Notifications
You must be signed in to change notification settings - Fork 85
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
131 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,6 +31,7 @@ nav: | |
|
||
- Router: | ||
- Overview: router/overview.md | ||
- Deep linking: router/deeplinking.md | ||
|
||
|
||
- Extensions: | ||
|