Skip to content

Commit

Permalink
Merge branch 'master' into compose-experimental
Browse files Browse the repository at this point in the history
  • Loading branch information
arkivanov committed Dec 23, 2023
2 parents 2bc0951 + 8b16a1f commit 775733a
Show file tree
Hide file tree
Showing 9 changed files with 407 additions and 229 deletions.
20 changes: 4 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,6 @@
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0)
[![Twitter URL](https://img.shields.io/badge/Twitter-@arkann1985-blue.svg?style=social&logo=twitter)](https://twitter.com/arkann1985)

![badge][badge-android]
![badge][badge-ios]
![badge][badge-js]
![badge][badge-jvm]
![badge][badge-mac]
![badge][badge-tvos]
![badge][badge-watchos]

---

Decompose is a Kotlin Multiplatform library for breaking down your code into tree-structured lifecycle-aware business logic components (aka BLoC), with routing functionality and pluggable UI (Jetpack/Multiplatform Compose, Android Views, SwiftUI, Kotlin/React, etc.).
Expand Down Expand Up @@ -66,6 +58,10 @@ Additional resources:

Please check the [Installation](https://arkivanov.github.io/Decompose/getting-started/installation/) section of the documentation.

### Supported platforms

In general, Decompose supports the following targets: `android`, `jvm`, `ios`, `watchos`, `tvos`, `macos`, `wasmJs`, `js`. However, some modules do not support all targets or the support depends on the Decompose version. Please see the Installation docs for details.

## Overview

Here are some key concepts of the library, more details can be found in the documentation.
Expand Down Expand Up @@ -120,11 +116,3 @@ Check out the [template repository](https://github.com/arkivanov/decompose-multi
Twitter: [@arkann1985](https://twitter.com/arkann1985)

If you like this project you can always <a href="https://www.buymeacoffee.com/arkivanov" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-blue.png" alt="Buy Me A Coffee" height=32></a> ;-)

[badge-android]: http://img.shields.io/badge/platform-android-6EDB8D.svg?style=flat
[badge-ios]: http://img.shields.io/badge/platform-ios-CDCDCD.svg?style=flat
[badge-js]: http://img.shields.io/badge/platform-js-F8DB5D.svg?style=flat
[badge-jvm]: http://img.shields.io/badge/platform-jvm-DB413D.svg?style=flat
[badge-mac]: http://img.shields.io/badge/platform-macos-111111.svg?style=flat
[badge-tvos]: http://img.shields.io/badge/platform-tvos-808080.svg?style=flat
[badge-watchos]: http://img.shields.io/badge/platform-watchos-C0C0C0.svg?style=flat
124 changes: 79 additions & 45 deletions docs/component/state-preservation.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,6 @@ Since Decompose `v2.2.0-alpha01` the recommended way is to use [kotlinx-serializ
=== "Before v2.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 {
Expand All @@ -34,8 +29,6 @@ Since Decompose `v2.2.0-alpha01` the recommended way is to use [kotlinx-serializ
=== "After v2.2.0-alpha01"

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

class SomeComponent(
componentContext: ComponentContext
) : ComponentContext by componentContext {
Expand All @@ -51,59 +44,100 @@ Since Decompose `v2.2.0-alpha01` the recommended way is to use [kotlinx-serializ
}
```

```kotlin title="Saving state of a retained instance"
import com.arkivanov.essenty.instancekeeper.InstanceKeeper
import com.arkivanov.essenty.instancekeeper.getOrCreate
import com.arkivanov.essenty.parcelable.Parcelable
import com.arkivanov.essenty.parcelable.ParcelableContainer
import com.arkivanov.essenty.parcelable.Parcelize
import com.arkivanov.essenty.parcelable.consume
import com.arkivanov.essenty.statekeeper.consume

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

private val statefulEntity =
instanceKeeper.getOrCreate {
SomeStatefulEntity(savedState = stateKeeper.consume(key = "SAVED_STATE"))
}
=== "Before v3.0.0-alpha01"

init {
stateKeeper.register(key = "SAVED_STATE", supplier = statefulEntity::saveState)
```kotlin title="Saving state of a retained instance"
class SomeComponent(
componentContext: ComponentContext
) : ComponentContext by componentContext {

private val statefulEntity =
instanceKeeper.getOrCreate {
SomeStatefulEntity(savedState = stateKeeper.consume(key = "SAVED_STATE"))
}

init {
stateKeeper.register(key = "SAVED_STATE", supplier = statefulEntity::saveState)
}
}
}

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

var state: State = savedState?.consume() ?: State()
private set

fun saveState(): ParcelableContainer =
ParcelableContainer(state)

class SomeStatefulEntity(
savedState: ParcelableContainer?,
) : InstanceKeeper.Instance {

var state: State = savedState?.consume() ?: State()
private set

fun saveState(): ParcelableContainer =
ParcelableContainer(state)

override fun onDestroy() {}

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

override fun onDestroy() {}
=== "Since v3.0.0-alpha01"

@Parcelize
data class State(val someValue: Int = 0) : Parcelable
}
```
```kotlin title="Saving state of a retained instance"
class SomeComponent(
componentContext: ComponentContext
) : ComponentContext by componentContext {

private val statefulEntity =
instanceKeeper.getOrCreate {
SomeStatefulEntity(savedState = stateKeeper.consume(key = "SAVED_STATE", strategy = SerializableContainer.serializer()))
}

init {
stateKeeper.register(
key = "SAVED_STATE",
strategy = SerializableContainer.serializer(),
supplier = statefulEntity::saveState,
)
}
}


class SomeStatefulEntity(
savedState: SerializableContainer?,
) : InstanceKeeper.Instance {

var state: State = savedState?.consume(strategy = State.serializer()) ?: State()
private set

fun saveState(): SerializableContainer =
SerializableContainer(value = state, strategy = State.serializer())

override fun onDestroy() {}

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

## Darwin (Apple) targets support
## Darwin (Apple) targets support (before v3.0.0)

Decompose provides an experimental support of state preservation for all Darwin (Apple) targets. It works via `Essenty` library and [parcelize-darwin](https://github.com/arkivanov/parcelize-darwin) compiler plugin (from the same author). Please read the documentation of both before using state preservation on Darwin targets.

This only affects your project if you explicitly enable the `parcelize-darwin` compiler plugin in your project. Otherwise, it's just no-op.

Please refer to the [sample iOS app](https://github.com/arkivanov/Decompose/blob/master/sample/app-ios/app-ios/app_iosApp.swift) where you can find an example of preserving the state.

## JVM/desktop support
## Darwin (Apple) targets support (since v3.0.0)

Decompose fully supports `kotlinx-serialization` since version `3.0.0-alpha01`, so the state can be serialized as JSON and
encoded via `NSCoder` as usual.

## JVM/desktop support (before v3.0.0)

Decompose also provides an experimental support of state preservation for JVM/desktop. Similarly to other targets, it works via `Essenty` library. Currently, there is no `Parcelize` compiler plugin, `Parcelable` interface just extends `java.io.Serializable`.

Due to [KT-40218](https://youtrack.jetbrains.com/issue/KT-40218), deserialized `object` classes are not equal to their original instances. This prevents the navigation state from being restored correctly when a configuration is an `object`. This will be fixed with the introduction of `data object` in Kotlin 1.9 (see [KT-4107](https://youtrack.jetbrains.com/issue/KT-4107)). A workaround for now is to add a [readResolve](https://docs.oracle.com/javase/7/docs/platform/serialization/spec/input.html#5903) method to every `object` configuration (see an [example](https://github.com/arkivanov/Decompose/blob/master/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/root/DefaultRootComponent.kt)).

Please refer to the [sample JVM/desktop app](https://github.com/arkivanov/Decompose/blob/master/sample/app-desktop/src/jvmMain/kotlin/com/arkivanov/sample/app/Main.kt) where you can find an example of preserving the state.

## JVM/desktop support (since v3.0.0)

Decompose fully supports `kotlinx-serialization` since version `3.0.0-alpha01`, so the state can be serialized as JSON into a file as usual.
47 changes: 24 additions & 23 deletions docs/extensions/compose.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Extensions for Jetpack/JetBrains Compose

Extensions and utilities for easier integration of Decompose with Jetpack/JetBrains Compose.
Extensions and utilities for easier integration of Decompose with Jetpack and Multiplatform Compose.

## Setup

Expand Down Expand Up @@ -276,6 +276,24 @@ fun RootContent(component: RootComponent) {
}
```

### Default stack animations

By default, the `Children` function (and all other functions with stack animations) does not animate stack changes, the change is performed instantly. The default stack animation is configurable, so that it's possible to avoid specifying the same animation multiple times.

```kotlin
@Composable
fun App() {
CompositionLocalProvider(LocalStackAnimationProvider provides DefaultStackAnimationProvider) {
// The rest of the code
}
}

private object DefaultStackAnimationProvider : StackAnimationProvider {
override fun <C : Any, T : Any> provide(): StackAnimation<C, T> =
stackAnimation(slide() + scale())
}
```

### Custom animations

It is also possible to define custom animations.
Expand Down Expand Up @@ -397,6 +415,9 @@ fun RootContent(component: RootComponent) {
}
```

!!! note
Since Decompose version `3.0.0-alpha01` the `animation` argument is renamed to `fallbackAnimation`.

### Predictive Back Gesture on Android

On Android, the predictive back gesture only works starting with Android T. On Android T, it works only between Activities, if enabled in the system settings. Starting with Android U, the predictive back gesture can be enabled between `Child Stack` screens inside a single Activity.
Expand Down Expand Up @@ -525,26 +546,6 @@ private fun Modifier.offsetXFactor(factor: Float): Modifier =

<video width="192" autoplay loop muted><source src="/Decompose/media/BackGestureIos.mp4" type="video/mp4"></video>

## Compose for iOS, macOS and Web (Canvas)

Compose for iOS, macOS and Web (Canvas) is still work in progress and was not officially announced. However, Decompose already supports it. The support is also **experimental** and is not part of the main branch - see [#74](https://github.com/arkivanov/Decompose/issues/74) for more information.

If you want to use Decompose with Compose for iOS/macOS/Web, you have to use special versions of both `decompose` and `extensions-compose-jetbrains` modules.

=== "Groovy"

``` groovy
implementation "com.arkivanov.decompose:decompose:<version>-compose-experimental"
implementation "com.arkivanov.decompose:extensions-compose-jetbrains:<version>-compose-experimental"
```

=== "Kotlin"

``` kotlin
implementation("com.arkivanov.decompose:decompose:<version>-compose-experimental")
implementation("com.arkivanov.decompose:extensions-compose-jetbrains:<version>-compose-experimental")
```

### Samples
## Samples for Compose for iOS and Web (JS/Canvas, not Wasm)

You can find samples in a separate branch - [compose-darwin/sample/app-darwin-compose](https://github.com/arkivanov/Decompose/tree/compose-experimental/sample).
You can find samples in a separate branch - [compose-darwin/sample](https://github.com/arkivanov/Decompose/tree/compose-experimental/sample).
102 changes: 67 additions & 35 deletions docs/getting-started/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,60 +27,92 @@ Some functionality is actually provided by [Essenty](https://github.com/arkivano
!!! note
If you are targetting Android, make sure you applied the [kotlin-parcelize](https://developer.android.com/kotlin/parcelize) Gradle plugin.

## Extensions for Jetpack/JetBrains Compose
## Extensions for Jetpack and Multiplatform Compose

The Compose UI is currently published in two separate variants:
Compose is currently published in two separate variants:

- The one developed and maintained by Google is Android only, called [Jetpack Compose](https://developer.android.com/jetpack/compose).
- The Kotlin Multiplatform variant of Jetpack Compose maintained by both JetBrains and Google, called it [Multiplatform Compose](https://github.com/JetBrains/compose-multiplatform).

- The one developed and maintained by Google is Android only, called [Jetpack Compose](https://developer.android.com/jetpack/compose)
- The Kotlin Multiplatform variant of Jetpack Compose maintained by both JetBrains and Google, we call it [JetBrains Compose](https://github.com/JetBrains/compose-jb)
=== "Before v3.0.0-alpha01"

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

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

Both modules are used to connect Compose with Decompose components. Please see the corresponding [documentation page](/Decompose/extensions/compose/).

- `extensions-compose-jetpack` - Android library for Jetpack Compose
- `extensions-compose-jetbrains` - Kotlin Multiplatform library for JetBrains Compose, supports `android` and `jvm` targets
=== "Since v3.0.0-alpha01"

Both modules are used to connect Compose UI to Decompose components. Please see the corresponding [documentation page](/Decompose/extensions/compose/).
Since Decompose version v3.0.0-alpha01 there is just one extension module for Compose:

### Gradle setup
- `extensions-compose` - compatible with both Jetpack Compose and Multiplatform Compose.

Typically only one module should be selected, depending on the Compose UI variant being used.
The module is used to connect Compose with Decompose components. Please see the corresponding [documentation page](/Decompose/extensions/compose/).

=== "Groovy"
### Gradle setup

``` groovy
implementation "com.arkivanov.decompose:extensions-compose-jetpack:<version>"
// or
implementation "com.arkivanov.decompose:extensions-compose-jetbrains:<version>"
```
=== "Before v3.0.0-alpha01"

=== "Kotlin"
Typically only one module should be selected, depending on the Compose variant being used.

=== "Groovy"

``` groovy
implementation "com.arkivanov.decompose:extensions-compose-jetpack:<version>"
// or
implementation "com.arkivanov.decompose:extensions-compose-jetbrains:<version>"
```

=== "Kotlin"

``` kotlin
implementation("com.arkivanov.decompose:extensions-compose-jetpack:<version>")
// or
implementation("com.arkivanov.decompose:extensions-compose-jetbrains:<version>")
```

``` kotlin
implementation("com.arkivanov.decompose:extensions-compose-jetpack:<version>")
// or
implementation("com.arkivanov.decompose:extensions-compose-jetbrains:<version>")
```
=== "Since v3.0.0-alpha01"

#### Support for Compose for iOS and Web (JS Canvas, not WASM)
=== "Groovy"

``` groovy
implementation "com.arkivanov.decompose:extensions-compose:<version>"
```

=== "Kotlin"

``` kotlin
implementation("com.arkivanov.decompose:extensions-compose:<version>")
```

Compose for iOS and Web (JS Canvas, not WASM) are supported and published from a separate branch: `compose-experimental`. This means that a special version suffix for all Decompose modules is required when configuring dependencies.
#### Support for Compose for iOS and Web (JS/Canvas and Wasm)

=== "Groovy"
=== "Before v3.0.0-alpha01"

``` groovy
implementation "com.arkivanov.decompose:decompose:<version>-compose-experimental"
implementation "com.arkivanov.decompose:extensions-compose-jetbrains:<version>-compose-experimental"
```
Compose for iOS and Web (JS/Canvas, not Wasm) are supported and published from a separate branch: `compose-experimental`. This means that a special version suffix for all Decompose modules is required when configuring dependencies.

=== "Groovy"

``` groovy
implementation "com.arkivanov.decompose:decompose:<version>-compose-experimental"
implementation "com.arkivanov.decompose:extensions-compose-jetbrains:<version>-compose-experimental"
```

=== "Kotlin"

``` kotlin
implementation("com.arkivanov.decompose:decompose:<version>-compose-experimental")
implementation("com.arkivanov.decompose:extensions-compose-jetbrains:<version>-compose-experimental")
```

=== "Kotlin"
=== "Since v3.0.0-alpha01"

``` kotlin
implementation("com.arkivanov.decompose:decompose:<version>-compose-experimental")
implementation("com.arkivanov.decompose:extensions-compose-jetbrains:<version>-compose-experimental")
```
All Compose variants (Android, JVM/Desktop, Native/iOS, Native/macOS, JS/Canvas, Wasm) are published from the main branch, please add dependencies as usual (no version suffixes required).

!!!warning
WASM target is not yet supported. Please follow [issue #74](https://github.com/arkivanov/Decompose/issues/74) for more information and updates.
Wasm target is not yet supported. Please follow [issue #74](https://github.com/arkivanov/Decompose/issues/74) for more information and updates.

## Extensions for Android views

Expand Down
Loading

0 comments on commit 775733a

Please sign in to comment.