Skip to content

Commit

Permalink
Merge pull request #707 from arkivanov/update-docs
Browse files Browse the repository at this point in the history
Updated docs
  • Loading branch information
arkivanov committed Apr 29, 2024
2 parents e8dece3 + c8f087f commit db08725
Show file tree
Hide file tree
Showing 20 changed files with 430 additions and 1,331 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ import androidx.savedstate.SavedStateRegistryOwner
* This function must be called from [Activity.onCreate] method.
*
* It is strongly recommended to always use the `standard` (default)
* `launchMode` for the [Activity] when handling deep links. This function takes
* care of restarting the [Activity] task and finishing this [Activity] if needed,
* [launchMode](https://developer.android.com/guide/components/activities/tasks-and-back-stack#TaskLaunchModes)
* for the [Activity] when handling deep links. This function takes care of
* restarting the [Activity] task and finishing this [Activity] if needed,
* in which case the returned value is `null`.
*
* Example of creating a root component with deep link support.
Expand Down
2 changes: 1 addition & 1 deletion docs/component/back-button.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class SomeComponent(

By default, registered callbacks are checked in reverse order, the last registered enabled callback is called first. Various navigation models may also register back button callbacks, e.g. `Child Stack` uses `BackHandler` to automatically pop the stack on back button press. If you want your callback to be called first, make sure to register it as later as possible. Similarly, if you want your callback to be called last, make sure to register it as early as possible.

Since Essenty version `1.2.0-alpha`, it is also possible to specify a priority for your back callback.
It is also possible to specify a priority for your back callback.

```kotlin
// This will make sure your callback is always called first
Expand Down
102 changes: 29 additions & 73 deletions docs/component/custom-component-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,79 +4,35 @@ If you need `ComponentContext` to have extra functionality that is not already p

## Create and implement custom ComponentContext

=== "Before version 3.0.0-alpha09"

To define a custom component context, create an interface that extends the `ComponentContext` interface, then implement it by delegating to the existing `ComponentContext`.

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

interface AppComponentContext : ComponentContext {

val logger: Logger // Additional property
}

class DefaultAppComponentContext(
componentContext: ComponentContext,
override val logger: Logger,
) : AppComponentContext, ComponentContext by componentContext
```

=== "Since version 3.0.0-alpha09"

To define a custom component context, create an interface that extends the `GenericComponentContext` interface, then implement it by delegating parts to the existing `ComponentContext`. Also, implement the `componentContextFactory` property to allow Decompose creating new instances of the custom component context type.

```kotlin
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.ComponentContextFactory
import com.arkivanov.decompose.GenericComponentContext
import com.arkivanov.essenty.backhandler.BackHandlerOwner
import com.arkivanov.essenty.instancekeeper.InstanceKeeperOwner
import com.arkivanov.essenty.lifecycle.LifecycleOwner
import com.arkivanov.essenty.statekeeper.StateKeeperOwner

interface AppComponentContext : GenericComponentContext<AppComponentContext> {

val logger: Logger // Additional property
}

class DefaultAppComponentContext(
componentContext: ComponentContext,
override val logger: Logger,
) : AppComponentContext,
LifecycleOwner by componentContext,
StateKeeperOwner by componentContext,
InstanceKeeperOwner by componentContext,
BackHandlerOwner by componentContext {

override val componentContextFactory: ComponentContextFactory<AppComponentContext> =
ComponentContextFactory { lifecycle, stateKeeper, instanceKeeper, backHandler ->
val ctx = componentContext.componentContextFactory(lifecycle, stateKeeper, instanceKeeper, backHandler)
DefaultAppComponentContext(ctx, logger)
}
}
```

## Custom child ComponentContext (before v3.0.0-alpha09)

!!!info "Not required since version 3.0.0-alpha09"

This section is only relevant for Decompose versions before `3.0.0-alpha09`. Since that version, the custom component context can be created the usual way - using the `childContext` extension function.

The default [ComponentContext#childContext](child-components.md#adding-a-child-component-manually) extension function returns the default `ComponentContext`. In order to create custom child `ComponentContext`, a special extension function is required.
To define a custom component context, create an interface that extends the `GenericComponentContext` interface, then implement it by delegating parts to the existing `ComponentContext`. Also, implement the `componentContextFactory` property to allow Decompose creating new instances of the custom component context type.

```kotlin
import com.arkivanov.decompose.childContext
import com.arkivanov.essenty.lifecycle.Lifecycle

fun AppComponentContext.childAppContext(key: String, lifecycle: Lifecycle? = null): AppComponentContext =
DefaultAppComponentContext(
componentContext = childContext(key = key, lifecycle = lifecycle),
// Supply additional dependencies here
)
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.decompose.ComponentContextFactory
import com.arkivanov.decompose.GenericComponentContext
import com.arkivanov.essenty.backhandler.BackHandlerOwner
import com.arkivanov.essenty.instancekeeper.InstanceKeeperOwner
import com.arkivanov.essenty.lifecycle.LifecycleOwner
import com.arkivanov.essenty.statekeeper.StateKeeperOwner

interface AppComponentContext : GenericComponentContext<AppComponentContext> {

val logger: Logger // Additional property
}

class DefaultAppComponentContext(
componentContext: ComponentContext,
override val logger: Logger,
) : AppComponentContext,
LifecycleOwner by componentContext,
StateKeeperOwner by componentContext,
InstanceKeeperOwner by componentContext,
BackHandlerOwner by componentContext {

override val componentContextFactory: ComponentContextFactory<AppComponentContext> =
ComponentContextFactory { lifecycle, stateKeeper, instanceKeeper, backHandler ->
val ctx = componentContext.componentContextFactory(lifecycle, stateKeeper, instanceKeeper, backHandler)
DefaultAppComponentContext(ctx, logger)
}
}
```

## Navigation with custom ComponentContext

- [Using Child Stack](../navigation/stack/component-context.md)
- [Using Child Slot](../navigation/slot/component-context.md)
2 changes: 1 addition & 1 deletion docs/component/instance-retaining.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class SomeComponent(
}
```

## Retained components (since v2.1.0-alpha-03)
## Retained components

Although discouraged, it is still possible to have all components retained over configuration changes on Android. On the one hand, this makes `InstanceKeeper` no longer required. But on the other hand, this prevents from supplying dependencies that capture the hosting `Activity` or `Fragment`.

Expand Down
222 changes: 88 additions & 134 deletions docs/component/scopes.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,77 +4,53 @@ Due to the fact that components are lifecycle-aware, it is very easy to manage c

## Creating a CoroutineScope in a component

!!! note "Extensions for CoroutineScope and Lifecycle"

=== "Before Essenty 2.0.0-alpha01"

```kotlin
import com.arkivanov.essenty.lifecycle.Lifecycle
import com.arkivanov.essenty.lifecycle.LifecycleOwner
import com.arkivanov.essenty.lifecycle.doOnDestroy
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
import kotlin.coroutines.CoroutineContext
fun CoroutineScope(context: CoroutineContext, lifecycle: Lifecycle): CoroutineScope {
val scope = CoroutineScope(context)
lifecycle.doOnDestroy(scope::cancel)
return scope
}
fun LifecycleOwner.coroutineScope(context: CoroutineContext): CoroutineScope =
CoroutineScope(context, lifecycle)
```

=== "Since Essenty 2.0.0-alpha01"

Since Essenty version `2.0.0-alpha01` extensions for `CoroutineScope` and `Lifecycle` are [provided](https://github.com/arkivanov/Essenty?tab=readme-ov-file#coroutines-extensions) by Essenty.

Add the following dependency to your build.gradle file.

=== "Groovy"
``` groovy
implementation "com.arkivanov.essenty:lifecycle-coroutines:<version>"
```
=== "Kotlin"
``` kotlin
implementation("com.arkivanov.essenty:lifecycle-coroutines:<version>")
```

Use the Essenty extensions to create the `CoroutineScope`.

```kotlin
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.essenty.lifecycle.coroutines.coroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.coroutines.CoroutineContext
class SomeComponent(
componentContext: ComponentContext,
mainContext: CoroutineContext,
private val ioContext: CoroutineContext,
) : ComponentContext by componentContext {
// The scope is automatically cancelled when the component is destroyed
private val scope = coroutineScope(mainContext + SupervisorJob())
fun foo() {
scope.launch {
val result =
There are extensions for `CoroutineScope` and `Lifecycle` [provided](https://github.com/arkivanov/Essenty?tab=readme-ov-file#coroutines-extensions) by Essenty.

Add the following dependency to your build.gradle file.

=== "Groovy"

``` groovy
implementation "com.arkivanov.essenty:lifecycle-coroutines:<version>"
```

=== "Kotlin"

``` kotlin
implementation("com.arkivanov.essenty:lifecycle-coroutines:<version>")
```

Use the Essenty extensions to create the `CoroutineScope`.

```kotlin
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.essenty.lifecycle.coroutines.coroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.coroutines.CoroutineContext

class SomeComponent(
componentContext: ComponentContext,
mainContext: CoroutineContext,
private val ioContext: CoroutineContext,
) : ComponentContext by componentContext {

// The scope is automatically cancelled when the component is destroyed
private val scope = coroutineScope(mainContext + SupervisorJob())

fun foo() {
scope.launch {
val result =
withContext(ioContext) {
"Result" // Result from background thread
"Result" // Result from background thread
}
println(result) // Handle the result on main thread
}
}

println(result) // Handle the result on main thread
}
```
}
}
```

## Creating a CoroutineScope that survives Android configuration changes

Expand Down Expand Up @@ -114,74 +90,52 @@ class SomeComponent(

## Creating a Reaktive DisposableScope in a component

!!! note "Extensions for DisposableScope and Lifecycle"

=== "Before Essenty 2.0.0-alpha01"

```kotlin
import com.arkivanov.essenty.lifecycle.Lifecycle
import com.arkivanov.essenty.lifecycle.LifecycleOwner
import com.arkivanov.essenty.lifecycle.doOnDestroy
import com.badoo.reaktive.disposable.scope.DisposableScope
fun DisposableScope(lifecycle: Lifecycle): DisposableScope {
val scope = DisposableScope()
lifecycle.doOnDestroy(scope::dispose)
return scope
There are extensions for `DisposableScope` and `Lifecycle` [provided](https://github.com/arkivanov/Essenty?tab=readme-ov-file#reaktive-extensions) by Essenty.

Add the following dependency to your build.gradle file.

=== "Groovy"

``` groovy
implementation "com.arkivanov.essenty:lifecycle-reaktive:<version>"
```

=== "Kotlin"

``` kotlin
implementation("com.arkivanov.essenty:lifecycle-reaktive:<version>")
```

Use the Essenty extensions to create the `DisposableScope`.

```kotlin
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.sample.shared.multipane.utils.disposableScope
import com.badoo.reaktive.disposable.scope.DisposableScope
import com.badoo.reaktive.scheduler.ioScheduler
import com.badoo.reaktive.scheduler.mainScheduler
import com.badoo.reaktive.single.observeOn
import com.badoo.reaktive.single.singleFromFunction
import com.badoo.reaktive.single.subscribeOn

class SomeComponent(
componentContext: ComponentContext,
) : ComponentContext by componentContext,
// The scope is automatically disposed when the component is destroyed
DisposableScope by componentContext.disposableScope() {

fun foo() {
singleFromFunction {
"Result" // Result from background thread
}
fun LifecycleOwner.disposableScope(): DisposableScope =
DisposableScope(lifecycle)
```

=== "Since Essenty 2.0.0-alpha01"

Since Essenty version `2.0.0-alpha01` extensions for `DisposableScope` and `Lifecycle` are [provided](https://github.com/arkivanov/Essenty?tab=readme-ov-file#reaktive-extensions) by Essenty.

Add the following dependency to your build.gradle file.

=== "Groovy"
``` groovy
implementation "com.arkivanov.essenty:lifecycle-reaktive:<version>"
```
=== "Kotlin"
``` kotlin
implementation("com.arkivanov.essenty:lifecycle-reaktive:<version>")
```

Use the Essenty extensions to create the `DisposableScope`.

```kotlin
import com.arkivanov.decompose.ComponentContext
import com.arkivanov.sample.shared.multipane.utils.disposableScope
import com.badoo.reaktive.disposable.scope.DisposableScope
import com.badoo.reaktive.scheduler.ioScheduler
import com.badoo.reaktive.scheduler.mainScheduler
import com.badoo.reaktive.single.observeOn
import com.badoo.reaktive.single.singleFromFunction
import com.badoo.reaktive.single.subscribeOn
class SomeComponent(
componentContext: ComponentContext,
) : ComponentContext by componentContext,
// The scope is automatically disposed when the component is destroyed
DisposableScope by componentContext.disposableScope() {
fun foo() {
singleFromFunction {
"Result" // Result from background thread
}
.subscribeOn(ioScheduler)
.observeOn(mainScheduler)
.subscribeScoped { // Subscribe using the DisposableScope
println(it) // Handle the result on main thread
}
.subscribeOn(ioScheduler)
.observeOn(mainScheduler)
.subscribeScoped { // Subscribe using the DisposableScope
println(it) // Handle the result on main thread
}
}
```
}
}
```

## Creating a Reaktive DisposableScope that survives Android configuration changes

Expand Down

0 comments on commit db08725

Please sign in to comment.