Skip to content

Commit 197f2ff

Browse files
committed
Added docs for the new Web Navigation API
1 parent 828d5b5 commit 197f2ff

File tree

3 files changed

+239
-8
lines changed

3 files changed

+239
-8
lines changed

docs/navigation/stack/browser-history.md

+5-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
# Web browser history
1+
# Web Browser History
2+
3+
!!!warning
4+
5+
This API is likely to be removed in the future. Please see the successor of this experimental API: [Web Navigation API](../web-navigation.md).
26

37
By default `Child Stack` navigation does not affect URLs in the browser address bar. But sometimes it is necessary to have different URLs for
48
different `Child Stack` destinations. For this purpose Decompose provides an **experimental** API - [WebHistoryController](https://github.com/arkivanov/Decompose/blob/master/decompose/src/webMain/kotlin/com/arkivanov/decompose/router/stack/webhistory/DefaultWebHistoryController.kt).
@@ -56,10 +60,3 @@ Using `WebHistoryController` is easy:
5660
3. In the component, call the `WebHistoryController.attach` method and supply all arguments.
5761
4. In the JS app, pass an initial deeplink to the component.
5862
5. Use the deeplink in the component to generate an initial back stack.
59-
60-
### Example
61-
62-
The sample project demonstrates the use of `WebHistoryController`:
63-
64-
- [Main.kt](https://github.com/arkivanov/Decompose/blob/master/sample/app-js/src/main/kotlin/com/arkivanov/sample/app/Main.kt) - demonstrates passing `WebHistoryController` and a deeplink via constructor to `RootComponent`
65-
- [RootComponent](https://github.com/arkivanov/Decompose/blob/master/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/root/RootComponent.kt) - demonstrates generating the initial stack from the deeplink, as well as calling `WebHistoryController.attach` and supplying the arguments

docs/navigation/web-navigation.md

+233
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
# Web Browser Navigation
2+
3+
!!!warning
4+
5+
This API is experimental, available since version `3.3.0-alpha01`.
6+
7+
The Web Navigation API is a successor of the old [Web Browser History](stack/browser-history.md) API. It is a more flexible and powerful tool for managing browser URLs and history in a web application. The API is designed to work with different navigation models and provides a way to synchronize the browser URL and history with the navigation state. Currently, the following navigation models are supported:
8+
9+
- [Child Stack](stack/overview.md)
10+
- [Child Pages](pages/overview.md)
11+
- [Child Panels](panels/overview.md)
12+
13+
The `Child Slot` navigation model is currently not supported. It will likely require additional API changes, might be implemented in the future.
14+
15+
## WebNavigation and WebNavigationOwner interfaces
16+
17+
The API consists of two main interfaces:
18+
19+
- [WebNavigation](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/webhistory/WebNavigation.kt) - A two-way navigation controller for Web browsers that connects a navigation model (e.g. `Child Stack`) with the browser's navigation history.
20+
- [WebNavigationOwner](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/webhistory/WebNavigationOwner.kt) - An interface that represents a holder of `WebNavigation`, typically implemented by a Decompose component.
21+
22+
You don't need to implement the `WebNavigation` interface directly. Instead, you should implement the `WebNavigationOwner` interface in your component using one of the provided functions available for each supported navigation model:
23+
24+
- [childStackWebNavigation](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/StackWebNavigation.kt) - For `Child Stack` navigation model.
25+
- [childPagesWebNavigation](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/pages/PagesWebNavigation.kt) - For `Child Pages` navigation model.
26+
- [childPanelsWebNavigation](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/PanelsWebNavigation.kt) - For `Child Panels` navigation model.
27+
28+
Only one instance of the `WebNavigation` controller is allowed per component. The new API also supports nested navigation, there can only be up to one (zero or one) child `WebNavigationOwner` at a time.
29+
30+
## Enabling the Web Navigation
31+
32+
To enable the Web Navigation, just use the `withWebHistory {}` function available for `js` ([link](https://github.com/arkivanov/Decompose/blob/master/decompose/src/jsMain/kotlin/com/arkivanov/decompose/router/webhistory/WebHistoryNavigation.kt)) and `wasmJs` ([link](https://github.com/arkivanov/Decompose/blob/master/decompose/src/wasmJsMain/kotlin/com/arkivanov/decompose/router/webhistory/WebHistoryNavigation.kt)) targets separately. The function provides two parameters: the current URL and an instance of `StateKeeper`. The URL should typically be used as a deep link to initialize the navigation state. The `StateKeeper` automatically saves and restores the navigation state on page reloads, it should be used to create the root `ComponentContext`.
33+
34+
Once the Web Navigation is enabled, the browser URL and history will be automatically synchronized with the navigation state. The browser history automatically follows the navigation state changes, and the navigation is automatically performed following the browser history changes.
35+
36+
## Configuring the application
37+
38+
Using Web Navigation in a single page application requires additional configuration - a catch-all strategy to return the same html resource for all paths. This strategy will be different for different server configurations.
39+
40+
### Development configuration
41+
42+
The Kotlin/JS `browser` target uses [webpack-dev-server](https://github.com/webpack/webpack-dev-server) as a local development server.
43+
It can be configured to use the same `index.html` file (or your primary html file) for all paths, by setting the [devServer.historyApiFallback](https://webpack.js.org/configuration/dev-server/#devserverhistoryapifallback) flag. The Gradle DSL for Kotlin
44+
webpack currently does not support the `historyApiFallback` flag, so a special configuration file should be used instead.
45+
46+
First, create a directory named `webpack.config.d` in the JS app module's directory. Then create a new file named `devServerConfig.js`
47+
inside that directory. Finally, put the following content to the file:
48+
49+
```javascript
50+
// <js app module>/webpack.config.d/devServerConfig.js
51+
52+
config.devServer = {
53+
...config.devServer, // Merge with other devServer settings
54+
"historyApiFallback": true
55+
};
56+
```
57+
58+
## Web Navigation with Child Stack
59+
60+
```kotlin title="Component interface"
61+
import com.arkivanov.decompose.router.stack.ChildStack
62+
import com.arkivanov.decompose.router.webhistory.WebNavigationOwner
63+
import com.arkivanov.decompose.value.Value
64+
65+
interface MyStackComponent : WebNavigationOwner {
66+
val stack: Value<ChildStack<*, Child>>
67+
68+
sealed class Child {
69+
// Omitted code
70+
}
71+
}
72+
73+
```
74+
75+
```kotlin title="Component implementation"
76+
import com.arkivanov.decompose.ComponentContext
77+
import com.arkivanov.decompose.router.stack.ChildStack
78+
import com.arkivanov.decompose.router.stack.StackNavigation
79+
import com.arkivanov.decompose.router.stack.childStack
80+
import com.arkivanov.decompose.router.stack.childStackWebNavigation
81+
import com.arkivanov.decompose.router.webhistory.WebNavigation
82+
import com.arkivanov.decompose.value.Value
83+
import kotlinx.serialization.Serializable
84+
85+
class DefaultMyStackComponent(
86+
componentContext: ComponentContext,
87+
deepLinkUrl: String?, // Or your favourite data structure, like Uri, etc.
88+
) : MyStackComponent, ComponentContext by componentContext {
89+
90+
private val nav = StackNavigation<Config>()
91+
92+
private val _stack: Value<ChildStack<Config, MyStackComponent.Child>> =
93+
childStack(
94+
source = nav,
95+
serializer = Config.serializer(),
96+
initialStack = { TODO("Use the deepLinkUrl parameter to initialize the stack") },
97+
childFactory = { ... },
98+
)
99+
100+
override val stack: Value<ChildStack<*, MyStackComponent.Child>> = _stack
101+
102+
override val webNavigation: WebNavigation<*> =
103+
childStackWebNavigation(
104+
navigator = nav,
105+
stack = _stack,
106+
serializer = Config.serializer(),
107+
pathMapper = { child -> TODO("Return a path for the child") }, // Optional
108+
parametersMapper = { child -> TODO("Return a Map with parameters for the child") }, // Optional
109+
childSelector = { child -> TODO("Return a WebNavigationOwner for the child") }, // Optional
110+
)
111+
112+
@Serializable
113+
private sealed interface Config {
114+
// Omitted code
115+
}
116+
}
117+
```
118+
119+
## Web Navigation with Child Pages
120+
121+
```kotlin title="Component interface"
122+
import com.arkivanov.decompose.router.pages.ChildPages
123+
import com.arkivanov.decompose.router.webhistory.WebNavigationOwner
124+
import com.arkivanov.decompose.value.Value
125+
126+
interface MyPagesComponent : WebNavigationOwner {
127+
val pages: Value<ChildPages<*, ...>>
128+
}
129+
```
130+
131+
```kotlin title="Component implementation"
132+
import com.arkivanov.decompose.ComponentContext
133+
import com.arkivanov.decompose.router.pages.ChildPages
134+
import com.arkivanov.decompose.router.pages.PagesNavigation
135+
import com.arkivanov.decompose.router.pages.childPages
136+
import com.arkivanov.decompose.router.pages.childPagesWebNavigation
137+
import com.arkivanov.decompose.router.webhistory.WebNavigation
138+
import com.arkivanov.decompose.value.Value
139+
import kotlinx.serialization.Serializable
140+
141+
class DefaultMyPagesComponent(
142+
componentContext: ComponentContext,
143+
deepLinkUrl: String?, // Or your favourite data structure, like Uri, etc.
144+
) : MyPagesComponent, ComponentContext by componentContext {
145+
146+
private val nav = PagesNavigation<Config>()
147+
148+
private val _pages: Value<ChildPages<Config, ...>> =
149+
childPages(
150+
source = nav,
151+
serializer = Config.serializer(),
152+
initialPages = { TODO("Use the deepLinkUrl parameter to initialize the navigation") },
153+
childFactory = { ... },
154+
)
155+
156+
override val pages: Value<ChildPages<*, ...>> = _pages
157+
158+
override val webNavigation: WebNavigation<*> =
159+
childPagesWebNavigation(
160+
navigator = nav,
161+
pages = _pages,
162+
serializer = Config.serializer(),
163+
pathMapper = { pages -> TODO("Return a path for the navigation state") }, // Optional
164+
parametersMapper = { pages -> TODO("Return a Map with parameters for the navigation state") }, // Optional
165+
childSelector = { child -> TODO("Return a WebNavigationOwner for the child") }, // Optional
166+
)
167+
168+
@Serializable
169+
private data class Config(...)
170+
}
171+
```
172+
173+
## Web Navigation with Child Panels
174+
175+
```kotlin title="Component interface"
176+
import com.arkivanov.decompose.router.panels.ChildPanels
177+
import com.arkivanov.decompose.router.webhistory.WebNavigationOwner
178+
import com.arkivanov.decompose.value.Value
179+
180+
interface MyPanelsComponent : WebNavigationOwner {
181+
val panels: Value<ChildPanels<...>>
182+
}
183+
```
184+
185+
```kotlin title="Component implementation"
186+
import com.arkivanov.decompose.ComponentContext
187+
import com.arkivanov.decompose.router.panels.ChildPanels
188+
import com.arkivanov.decompose.router.panels.PanelsNavigation
189+
import com.arkivanov.decompose.router.panels.childPanels
190+
import com.arkivanov.decompose.router.panels.childPanelsWebNavigation
191+
import com.arkivanov.decompose.router.webhistory.WebNavigation
192+
import com.arkivanov.decompose.value.Value
193+
import kotlinx.serialization.Serializable
194+
195+
class DefaultMyPanelsComponent(
196+
componentContext: ComponentContext,
197+
deepLinkUrl: String?, // Or your favourite data structure, like Uri, etc.
198+
) : MyPanelsComponent, ComponentContext by componentContext {
199+
200+
private val nav = PanelsNavigation<MainConfig, DetailsConfig, Nothing>()
201+
202+
private val _panels: Value<ChildPanels<...>> =
203+
childPanels(
204+
source = nav,
205+
serializers = MainConfig.serializer() to DetailsConfig.serializer(),
206+
initialPanels = { TODO("Use the deepLinkUrl parameter to initialize the navigation") },
207+
mainFactory = { ... },
208+
detailsFactory = { ... },
209+
)
210+
211+
override val panels: Value<ChildPanels<...>> = _panels
212+
213+
override val webNavigation: WebNavigation<*> =
214+
childPanelsWebNavigation(
215+
navigator = nav,
216+
panels = _panels,
217+
mainSerializer = MainConfig.serializer(),
218+
detailsSerializer = DetailsConfig.serializer(),
219+
pathMapper = { panels -> TODO("Return a path for the navigation state") }, // Optional
220+
parametersMapper = { panels -> TODO("Return a Map with parameters for the navigation state") }, // Optional
221+
childSelector = { panels -> TODO("Return a WebNavigationOwner for the navigation state") }, // Optional
222+
)
223+
224+
@Serializable
225+
private data class MainConfig(...)
226+
227+
@Serializable
228+
private data class DetailsConfig(...)
229+
}
230+
```
231+
## Example
232+
233+
Please refer to the [main sample](https://github.com/arkivanov/Decompose/tree/master/sample) for a complete example of using the Web Navigation API.

mkdocs.yml

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ nav:
4949
- Navigation: navigation/panels/navigation.md
5050
- Generic Navigation:
5151
- Overview: navigation/children/overview.md
52+
- Web Navigation: navigation/web-navigation.md
5253

5354
- Extensions:
5455
- Overview: extensions/overview.md

0 commit comments

Comments
 (0)