Skip to content

Commit

Permalink
Update documentation about Essenty
Browse files Browse the repository at this point in the history
  • Loading branch information
arkivanov committed Jul 21, 2021
1 parent 745f7ab commit 4b905e7
Show file tree
Hide file tree
Showing 15 changed files with 159 additions and 115 deletions.
32 changes: 32 additions & 0 deletions docs/component/back-button.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Back button handling

Some devices (e.g. Android) have hardware back buttons. A very common use case is to close the current screen, or the app if there is only one screen in the stack. Another possible use case is to show a confirmation dialog before closing the app.

## Navigation with back button

The `Router` can automatically navigate back when the back button is pressed. All you need to do is to supply the `handleBackButton=true` argument when you create the `Router`. Please see the related [documentation page](https://arkivanov.github.io/Decompose/router/overview/) for more information.

## Manual back button handling

The back button can be handled manually using `BackPressedDispatcher` (comes from [Essenty](https://github.com/arkivanov/Essenty) library), which is provided by `ComponentContext`. The `decompose` module adds Essenty's `back-pressed` 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 `BackPressedDispatcher`.

### Usage example

```kotlin
import com.arkivanov.decompose.ComponentContext

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

init {
backPressedDispatcher.register(::onBackPressed)
}

private fun onBackPressed(): Boolean {
// Handle the back button.
// Return true to consume the event, or false to allow other consumers.
return false
}
}
```
2 changes: 1 addition & 1 deletion docs/component/custom-component-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Finally, in your components you can create a new router that will utilize the ne

```kotlin
class MyComponent(componentContext: AppComponentContext): AppComponentContext by componentContext {

private val router = appRouter(
initialConfiguration = { Configuration.Home },
childFactory = { configuration, appComponentContext ->
Expand Down
17 changes: 0 additions & 17 deletions docs/component/instance-keeper.md

This file was deleted.

30 changes: 30 additions & 0 deletions docs/component/instance-retaining.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Instance retaining

Sometimes it might be necessary to preserve state or data in a component when it gets destroyed. This commonly used in Android when configuration changes occur. The `ComponentContext` interface extends the `InstanceKeeperOwner` interface, which provides the `InstanceKeeper` - a multiplatform abstraction for instances retaining. It is provided by [Essenty](https://github.com/arkivanov/Essenty) library (from the same author).

The `decompose` module adds Essenty's `instance-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 `InstanceKeeper`.

## Usage example

```kotlin
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.essenty.instancekeeper.InstanceKeeper
import com.arkivanov.essenty.instancekeeper.getOrCreate

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

private val someLogic = instanceKeeper.getOrCreate(::SomeLogic)

/*
* Instances of this class will be retained.
* ⚠️ Pay attention to not leak any dependencies.
*/
private class SomeLogic : InstanceKeeper.Instance {
override fun onDestroy() {
// Clean-up
}
}
}
```
64 changes: 30 additions & 34 deletions docs/component/lifecycle.md
Original file line number Diff line number Diff line change
@@ -1,47 +1,43 @@
# Lifecycle

The component lifecycle is very similar to the [Android Activity lifecycle](https://developer.android.com/guide/components/activities/activity-lifecycle). There is the [Lifecycle](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/lifecycle/Lifecycle.kt) interface, each instance of the `ComponentContext` has its own instance of the `Lifecycle`.
The component lifecycle is very similar to the [Android Activity lifecycle](https://developer.android.com/guide/components/activities/activity-lifecycle). The `ComponentContext` interface extends the `LifecycleOwner` interface, which provides the `Lifecycle` - a multiplatform abstraction for lifecycle states and events. It is provided by [Essenty](https://github.com/arkivanov/Essenty) library (from the same author).

There is also [LifecycleRegistry](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/lifecycle/LifecycleRegistry.kt) which is the `Lifecycle` and the `Lifecycle.Callbacks` at the same time. `LifecycleRegistry` can be used to manually control the lifecycle. It can be passed to a root component, or can be used in tests.
The `decompose` module adds Essenty's `lifecycle` 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 `Lifecycle`.

Each component has its own lifecycle. The lifecycle of a child component can not be longer than its parent's lifecycle.

<img src="https://raw.githubusercontent.com/arkivanov/Decompose/master/docs/media/LifecycleStates.png" width="512">

The lifecycle can be observed using its `subscribe`/`unsubscribe` methods:
## Usage example

```kotlin
lifecycle.subscribe(
object : DefaultLifecycleCallbacks {
override fun onCreate() {
// Handle lifecycle created
}

// onStart, onResume, onPause, onStop are also available

override fun onDestroy() {
// Handle lifecycle destroyed
}
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.essenty.lifecycle.Lifecycle
import com.arkivanov.essenty.lifecycle.doOnCreate
import com.arkivanov.essenty.lifecycle.subscribe

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

init {
lifecycle.subscribe(
object : Lifecycle.Callbacks {
override fun onCreate() {
/* Component created */
}

// onStart, onResume, onPause, onStop, onDestroy
}
)

lifecycle.subscribe(
onCreate = { /* Component created */ },
// onStart, onResume, onPause, onStop, onDestroy
)

lifecycle.doOnCreate { /* Component created */ }
// doOnStart, doOnResume, doOnPause, doOnStop, doOnDestroy
}
)
```

Or using the extension functions:

```kotlin
lifecycle.subscribe(
onCreate = { /* Handle lifecycle created */ },
// onStart, onResume, onPause, onStop are also available
onDestroy = { /* Handle lifecycle destroyed */ }
)

lifecycle.doOnCreate {
// Handle lifecycle created
}

// doOnStart, doOnResume, doOnPause, doOnStop are also available

lifecycle.doOnDestroy {
// Handle lifecycle destroyed
}
```
15 changes: 8 additions & 7 deletions docs/component/overview.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

# Component Overview

A component is just a normal class that encapsulates some logic and possibly another (child) components. Every component has its own lifecycle, which is automatically managed by Decompose. So everything encapsulated by a component is scoped. Please head to the [Lifecycle documentation page](https://arkivanov.github.io/Decompose/router/lifecycle/) for more information.
Expand All @@ -17,15 +16,15 @@ UI is optional and is pluggable from outside of components. Components do not de

<img src="https://raw.githubusercontent.com/arkivanov/Decompose/master/docs/media/ComponentStructure.png" width="512">

## `ComponentContext`
## ComponentContext

Each component has an associated [`ComponentContext`](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/ComponentContext.kt) which implements the following interfaces:

* [RouterFactory](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/RouterFactory.kt), so you can create nested `Routers` in your `Componenets`
* [StateKeeperOwner](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/statekeeper/StateKeeperOwner.kt), so you can preserve any state during configuration changes and/or process death
* [InstanceKeeperOwner](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/instancekeeper/InstanceKeeperOwner.kt), so you can retain instances in your components (like with AndroidX ViewModels)
* [LifecycleOwner](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/lifecycle/LifecycleOwner.kt), so each component has its own lifecycle
* [BackPressedDispatcherOwner](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/backpressed/BackPressedDispatcherOwner.kt), so each component can handle back button events
* `LifecycleOwner`, provided by Essenty library, so each component has its own lifecycle
* `StateKeeperOwner`, provided by Essenty library, so you can preserve any state during configuration changes and/or process death
* `InstanceKeeperOwner`, provided by Essenty library, so you can retain arbitrary object instances in your components (like with AndroidX ViewModels)
* `BackPressedDispatcherOwner`, provided by Essenty library, so each component can handle back button events

So if a component requires any of the above features, just pass the `ComponentContext` via the component's constructor. You can use the delegation pattern to add the `ComponentContext` to `this` scope:

Expand Down Expand Up @@ -100,7 +99,9 @@ fun CounterUi(counter: Counter) {
}
```

If you are using only Jetpack/JetBrains Compose UI, then most likely you can use its `State` and `MutableState` directly, without intermediate `Value`/`MutableValue` from Decompose.
[Value](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/value/Value.kt) - is a multiplatform way to expose streams of states. It contains the `value` property, which always returns the current state. It also provides ability to observe state changes via `subscribe`/`unsubscribe` methods. There is [MutableValue](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/value/MutableValueBuilder.kt) which is a mutable variant of `Value`.

If you are using only Jetpack/JetBrains Compose UI, then most likely you can use its `State` and `MutableState` directly, without intermediate `Value`/`MutableValue` from Decompose. You can convert between `State` and `Value` using one of the Compose [extension modules](https://arkivanov.github.io/Decompose/extensions/compose/).

### SwiftUI Example

Expand Down
20 changes: 0 additions & 20 deletions docs/component/state-keeper.md

This file was deleted.

28 changes: 28 additions & 0 deletions docs/component/state-preservation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# State preservation

Sometimes it might be necessary to preserve state or data in a component when it gets destroyed. A very common use case is Android Activity recreation due to configuration changes, or process death. The `ComponentContext` interface extends the `StateKeeperOwner` interface, which provides the `StateKeeper` - a multiplatform abstraction for state preservation. It is provided by [Essenty](https://github.com/arkivanov/Essenty) library (from the same author).

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 example

```kotlin
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
}
```
File renamed without changes.
20 changes: 10 additions & 10 deletions docs/getting-started.md → docs/getting-started/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,17 @@ Decompose provides a number of modules, they all published to Maven Central Repo

## The main Decompose module

The main functionality is provided by the `decompose` module. It includes (but is not limited to) the following:
The main functionality is provided by the `decompose` module. It contains the core functionality, like [Router](https://arkivanov.github.io/Decompose/router/overview/), [ComponentContext](https://arkivanov.github.io/Decompose/component/overview/#componentcontext), etc.

- The [Router](https://arkivanov.github.io/Decompose/router/overview/)
- The [ComponentContext](https://arkivanov.github.io/Decompose/component/overview/#componentcontext)
- The [Lifecycle](https://arkivanov.github.io/Decompose/component/lifecycle/)
- The [StateKeeper](https://arkivanov.github.io/Decompose/component/state-keeper/)
- The [InstanceKeeper](https://arkivanov.github.io/Decompose/component/instance-keeper/)
This module supports the following Kotlin Multiplatform targets:

This module supports the following Kotlin Multiplatform targets:
- `android`,
- `jvm`
- `iosX64`, `iosArm64`
- `js` (both `IR` and `Legacy` modes)
- `macosX64`
- `iosX64`, `iosArm64`
- `tvosArm64`, `tvosX64`
- `watchosArm32`, `watchosArm64`, `watchosX86`, `watchosX64`
- `watchosArm32`, `watchosArm64`, `watchosX64`
- `macosX64`

### Gradle setup

Expand All @@ -33,6 +28,10 @@ This module supports the following Kotlin Multiplatform targets:
implementation("com.arkivanov.decompose:decompose:<version>")
```

### Dependency on Essenty library

Some functionality is actually provided by [Essenty](https://github.com/arkivanov/Essenty) library. Essenty is implemented by the same author and provides very basic things like `Lifecycle`, `StateKeeper`, etc. Most important Essenty modules are added to the `decompose` module as `api` dependency, so you don't have to add them manually to your project. Please familiarise yourself with Essenty library.

## Extensions for Jetpack/JetBrains Compose

The Compose UI is currently published in two separate variants:
Expand All @@ -41,6 +40,7 @@ The Compose UI is currently published in two separate variants:
- The Kotlin Multiplatform variant of Jetpack Compose maintained by both JetBrains and Google, we call it [JetBrains Compose](https://github.com/JetBrains/compose-jb)

Due to this fragmentation Decompose provides two separate extension modules for Compose UI:

- `extensions-compose-jetpack` - Android library for Jetpack Compose
- `extensions-compose-jetbrains` - Kotlin Multiplatform library for JetBrains Compose, supports `android` and `jvm` targets

Expand Down
File renamed without changes.
File renamed without changes.
18 changes: 9 additions & 9 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
# Overview
# Overview

## What is Decompose?

Decompose is a Kotlin Multiplatform lifecycle-aware business logic components (aka BLoCs) with routing functionality and pluggable UI (Android Views, Jetpack Compose, SwiftUI, JS React, etc.) This project is inspired by [Badoos RIBs](https://github.com/badoo/RIBs) fork of the [Uber RIBs](https://github.com/uber/RIBs) framework.

### Supported targets

* Android
* JVM
* iosX64, iosArm64
* macosX64
* tvosX64, tvosArm64
* watchosArm32, watchosArm64, watchosX86, watchosX64
* JavaScript
- `android`,
- `jvm`
- `js` (both `IR` and `Legacy` modes)
- `iosX64`, `iosArm64`
- `tvosArm64`, `tvosX64`
- `watchosArm32`, `watchosArm64`, `watchosX64`
- `macosX64`

## Why Decompose?
## Why Decompose?

- Decompose draws clear boundaries between UI and non-UI code, which gives the following benefits:
- Better separation of concerns
Expand Down
15 changes: 4 additions & 11 deletions docs/router/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,11 @@
## 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`.
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 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.

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

Expand Down Expand Up @@ -91,7 +84,7 @@ class RootComponent(
private val router =
router<Config, Root.Child>(
initialConfiguration = Config.List,
handleBackButton = true,
handleBackButton = true, // Pop the back stack on back button press
childFactory = ::createChild
)

Expand Down
Loading

0 comments on commit 4b905e7

Please sign in to comment.