Skip to content

Commit

Permalink
Merge pull request #488 from arkivanov/updated-docs
Browse files Browse the repository at this point in the history
Updated docs with kotlinx-serialization
  • Loading branch information
arkivanov authored Sep 24, 2023
2 parents 4b7faa7 + f39bb21 commit 8988b84
Show file tree
Hide file tree
Showing 6 changed files with 431 additions and 182 deletions.
63 changes: 44 additions & 19 deletions docs/component/state-preservation.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,52 @@ Sometimes it might be necessary to preserve state or data in a component when it

The `decompose` module adds Essenty's `state-keeper` module as `api` dependency, so you don't need to explicitly add it to your project. Please familiarise yourself with Essenty library, especially with the `StateKeeper`.

## Usage examples

```kotlin title="Saving state in a component"
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.essenty.parcelable.Parcelable
import com.arkivanov.essenty.parcelable.Parcelize
import com.arkivanov.essenty.statekeeper.consume

class SomeComponent(
componentContext: ComponentContext
) : ComponentContext by componentContext {
Since Decompose `v2.2.0-alpha01` the recommended way is to use [kotlinx-serialization](https://github.com/Kotlin/kotlinx.serialization) library. Most of the Parcelable/Parcelize APIs are now deprecated.

private var state: State = stateKeeper.consume(key = "SAVED_STATE") ?: State()
## Usage examples

init {
stateKeeper.register(key = "SAVED_STATE") { state }
=== "Before v1.2.0-alpha01"

```kotlin title="Saving state in a component"
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.essenty.parcelable.Parcelable
import com.arkivanov.essenty.parcelable.Parcelize
import com.arkivanov.essenty.statekeeper.consume

class SomeComponent(
componentContext: ComponentContext
) : ComponentContext by componentContext {

private var state: State = stateKeeper.consume(key = "SAVED_STATE") ?: State()

init {
stateKeeper.register(key = "SAVED_STATE") { state }
}

@Parcelize
private class State(val someValue: Int = 0) : Parcelable
}

@Parcelize
private class State(val someValue: Int = 0) : Parcelable
}
```
```

=== "After v1.2.0-alpha01"

```kotlin title="Saving state in a component"
import kotlinx.serialization.Serializable

class SomeComponent(
componentContext: ComponentContext
) : ComponentContext by componentContext {

private var state: State = stateKeeper.consume(key = "SAVED_STATE", strategy = State.serializer()) ?: State()

init {
stateKeeper.register(key = "SAVED_STATE", strategy = State.serializer()) { state }
}

@Serializable
private class State(val someValue: Int = 0)
}
```

```kotlin title="Saving state of a retained instance"
import com.arkivanov.essenty.instancekeeper.InstanceKeeper
Expand All @@ -51,6 +75,7 @@ class SomeComponent(
}

class SomeStatefulEntity(
// There is no any `kotlinx-serialization` replacement for `ParcelableContainer` currently, please continue using it until v3.0
savedState: ParcelableContainer?,
) : InstanceKeeper.Instance {

Expand Down
174 changes: 123 additions & 51 deletions docs/getting-started/quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,70 +138,142 @@ Child component configurations is another important concepts of Decompose. It al

Each child component is represented by a persistent configuration class. A configuration class denotes which child component should be instantiated, and holds persistent arguments required for instantiation. A configuration class must be defined for every child component.

!!!warning
Before `v1.2.0-alpha01`, configuration classes must implement `Parcelable` interface and be annotated with [@Parcelize](https://developer.android.com/kotlin/parcelize) annotation. Starting with `v1.2.0-alpha01`, Parcelable/Parcelize support is deprecated and the recommended way is to annotate configuration classes with [@Serializable](https://github.com/Kotlin/kotlinx.serialization) annotation.

### Using the Child Stack

```kotlin
interface RootComponent {

val stack: Value<ChildStack<*, Child>>
=== "Before 1.2.0-alpha01"

// It's possible to pop multiple screens at a time on iOS
fun onBackClicked(toIndex: Int)
```kotlin
interface RootComponent {

val stack: Value<ChildStack<*, Child>>

// It's possible to pop multiple screens at a time on iOS
fun onBackClicked(toIndex: Int)

// Defines all possible child components
sealed class Child {
class ListChild(val component: ListComponent) : Child()
class DetailsChild(val component: DetailsComponent) : Child()
// Defines all possible child components
sealed class Child {
class ListChild(val component: ListComponent) : Child()
class DetailsChild(val component: DetailsComponent) : Child()
}
}
}

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

private val navigation = StackNavigation<Config>()

override val stack: Value<ChildStack<*, RootComponent.Child>> =
childStack(
source = navigation,
initialConfiguration = Config.List, // The initial child component is List
handleBackButton = true, // Automatically pop from the stack on back button presses
childFactory = ::child,
)

private fun child(config: Config, componentContext: ComponentContext): RootComponent.Child =
when (config) {
is Config.List -> ListChild(listComponent(componentContext))
is Config.Details -> DetailsChild(detailsComponent(componentContext, config))

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

private val navigation = StackNavigation<Config>()

override val stack: Value<ChildStack<*, RootComponent.Child>> =
childStack(
source = navigation,
initialConfiguration = Config.List, // The initial child component is List
handleBackButton = true, // Automatically pop from the stack on back button presses
childFactory = ::child,
)

private fun child(config: Config, componentContext: ComponentContext): RootComponent.Child =
when (config) {
is Config.List -> ListChild(listComponent(componentContext))
is Config.Details -> DetailsChild(detailsComponent(componentContext, config))
}

private fun listComponent(componentContext: ComponentContext): ListComponent =
DefaultListComponent(
componentContext = componentContext,
onItemSelected = { item: String -> // Supply dependencies and callbacks
navigation.push(Config.Details(item = item)) // Push the details component
},
)

private fun detailsComponent(componentContext: ComponentContext, config: Config.Details): DetailsComponent =
DefaultDetailsComponent(
componentContext = componentContext,
item = config.item, // Supply arguments from the configuration
onFinished = navigation::pop, // Pop the details component
)
override fun onBackClicked(toIndex: Int) {
navigation.popTo(index = toIndex)
}

@Parcelize // kotlin-parcelize plugin must be applied if you are targeting Android
private sealed interface Config : Parcelable {
data object List : Config
data class Details(val item: String) : Config
}
}
```

private fun listComponent(componentContext: ComponentContext): ListComponent =
DefaultListComponent(
componentContext = componentContext,
onItemSelected = { item: String -> // Supply dependencies and callbacks
navigation.push(Config.Details(item = item)) // Push the details component
},
)
=== "Since 1.2.0-alpha01"

private fun detailsComponent(componentContext: ComponentContext, config: Config.Details): DetailsComponent =
DefaultDetailsComponent(
componentContext = componentContext,
item = config.item, // Supply arguments from the configuration
onFinished = navigation::pop, // Pop the details component
)
```kotlin
interface RootComponent {

val stack: Value<ChildStack<*, Child>>

override fun onBackClicked(toIndex: Int) {
navigation.popTo(index = toIndex)
// It's possible to pop multiple screens at a time on iOS
fun onBackClicked(toIndex: Int)

// Defines all possible child components
sealed class Child {
class ListChild(val component: ListComponent) : Child()
class DetailsChild(val component: DetailsComponent) : Child()
}
}

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

private val navigation = StackNavigation<Config>()

override val stack: Value<ChildStack<*, RootComponent.Child>> =
childStack(
source = navigation,
serializer = Config.serializer(),
initialConfiguration = Config.List, // The initial child component is List
handleBackButton = true, // Automatically pop from the stack on back button presses
childFactory = ::child,
)

private fun child(config: Config, componentContext: ComponentContext): RootComponent.Child =
when (config) {
is Config.List -> ListChild(listComponent(componentContext))
is Config.Details -> DetailsChild(detailsComponent(componentContext, config))
}

private fun listComponent(componentContext: ComponentContext): ListComponent =
DefaultListComponent(
componentContext = componentContext,
onItemSelected = { item: String -> // Supply dependencies and callbacks
navigation.push(Config.Details(item = item)) // Push the details component
},
)

private fun detailsComponent(componentContext: ComponentContext, config: Config.Details): DetailsComponent =
DefaultDetailsComponent(
componentContext = componentContext,
item = config.item, // Supply arguments from the configuration
onFinished = navigation::pop, // Pop the details component
)
override fun onBackClicked(toIndex: Int) {
navigation.popTo(index = toIndex)
}

@Serializable // kotlinx-serialization plugin must be applied
private sealed interface Config {
@Serializable
data object List : Config

@Parcelize // The `kotlin-parcelize` plugin must be applied if you are targeting Android
private sealed interface Config : Parcelable {
data object List : Config
data class Details(val item: String) : Config
@Serializable
data class Details(val item: String) : Config
}
}
}
```
```

### Child Stack with Jetpack Compose

Expand Down
15 changes: 12 additions & 3 deletions docs/navigation/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,26 @@ 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
3. Implement `Parcelable` interface (or be `@Serializable` starting with `v1.3.0-alpha01`)

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 are Parcelable (or @Serializable)

`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.

#### Android target
Starting with `v1.3.0-alpha01`, the recommended way is to use [kotlinx-serialization](https://github.com/Kotlin/kotlinx.serialization) library. The Essenty library is still used.

#### All targets (using kotlinx-serialization since v1.3.0-alpha01)

Please make sure you [setup](https://github.com/Kotlin/kotlinx.serialization#setup) `kotlinx-serialization` correctly and applied the plugin.

!!!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.

#### Android target (using Parcelable/Parcelize, deprecated since v1.3.0-alpha01)

If you support the `android` target, make sure you have applied [kotlin-parcelize](https://developer.android.com/kotlin/parcelize) Gradle plugin. Otherwise, your code won't compile for Android.

Expand Down
Loading

0 comments on commit 8988b84

Please sign in to comment.