Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@ jobs:
runs-on: macos-15
steps:
- name: Check out source
uses: actions/checkout@v4
uses: actions/checkout@v6

- name: Set up JDK 17
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 17

- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
uses: gradle/actions/setup-gradle@v6

- name: Run Android and Apple Silicon checks
run: ./gradlew clean check apiCheck koverVerify --stacktrace
Expand Down Expand Up @@ -53,16 +53,16 @@ jobs:
runs-on: macos-15-intel
steps:
- name: Check out source
uses: actions/checkout@v4
uses: actions/checkout@v6

- name: Set up JDK 17
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 17

- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
uses: gradle/actions/setup-gradle@v6

- name: Run Intel simulator checks
run: |
Expand Down
14 changes: 7 additions & 7 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@ jobs:
runs-on: macos-15-intel
steps:
- name: Check out source
uses: actions/checkout@v4
uses: actions/checkout@v6

- name: Set up JDK 17
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 17

- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
uses: gradle/actions/setup-gradle@v6

- name: Run Intel simulator checks
run: |
Expand All @@ -37,7 +37,7 @@ jobs:
runs-on: macos-15
steps:
- name: Check out source
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0

Expand All @@ -47,13 +47,13 @@ jobs:
git merge-base --is-ancestor "$GITHUB_SHA" origin/main

- name: Set up JDK 17
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 17

- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
uses: gradle/actions/setup-gradle@v6

- name: Extract version
id: version
Expand Down Expand Up @@ -98,4 +98,4 @@ jobs:
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_KEY }}
ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.SIGNING_KEY_ID }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_KEY_PASSWORD }}
run: ./gradlew publishAllPublicationsToMavenCentralRepository -PVERSION_NAME=${{ steps.version.outputs.value }} --stacktrace
run: ./gradlew publishAndReleaseToMavenCentral -PVERSION_NAME=${{ steps.version.outputs.value }} --stacktrace
50 changes: 49 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,46 @@ Gbéewá is the library name. Its repository and artifact use `gbeewa`, and its

Gbéewá delivers typed, transient results to app-wired Compose Multiplatform consumers. It does not own navigation, identify a previous destination, or select a destination. A mounted consumer can receive a result on the same screen or after any navigation change while the shared result store remains alive.

The library uses a common runtime API. It does not require KSP, code generation, or a navigation-framework compiler plugin.

Quanti Pixels authors, maintains, and publishes this library. The library originated from the result mechanism in Yétúndé The Alárìná.

Version `0.1.0` supports Android and iOS. Future platform targets are added by request.

## Why Gbéewá

- **Omni-directional delivery:** Deliver current-to-previous, current-to-next, or current-to-current. Shared store ownership, key equality, and consumption timing select the receiver.
- **Typed result lanes:** Use `ResultKey<T>` instead of shared string keys and manual payload casts.
- **Parameterized keys:** Use data-class keys to isolate concurrent requests or screen instances.
- **Pure runtime API:** Add no KSP processor or generated navigation wrapper.
- **Framework agnostic:** Use the same contract with Navigation 2, Navigation 3, Decompose, Voyager, or app-owned navigation.
- **Compose Multiplatform:** Use the same common API on Android and iOS.

Gbéewá provides transient, one-consumer result delivery. It is not a broadcast channel or a replacement for durable application state.

## Navigation direction

`Result` describes the outcome of an action. It does not define a navigation direction.

`ResultStore.put` stores one pending value under a key. It does not address a screen. The first eligible consumer that uses a `ResultConsumer` backed by the same store and an equal key consumes that value. Therefore, a publication can deliver in these directions:

- current to previous: publish, then pop;
- current to current: publish while the consumer remains mounted;
- current to current: publish while the consumer remains mounted, including between sibling composables that share the store;
- current to next: publish, then push a screen that consumes the key.

Other route relationships use the same rules. Navigation direction does not select the receiver. Shared `ResultStore` ownership, key equality, and consumption timing select it. Delivery is not a broadcast. If more than one consumer can observe an equal key, only the first consumer receives the pending value. A mounted consumer can consume it before a later screen enters composition. Use request-specific or parameterized keys when the receiver must be unambiguous.

```text
Producer A ── put(ResultKey<T>, value) ──► ResultStore
equal key + first eligible consumer
Consumer B or a later destination
```

The producer and consumer can be on the same screen, on different mounted screens, or on screens separated by a navigation change. The diagram shows one-result consumption. It does not show a broadcast channel.

## Samples

The runnable Android and iOS showcase uses two flows derived from Yétúndé:
Expand Down Expand Up @@ -221,6 +245,30 @@ class AppResultViewModel : ViewModel() {

Provide `resultConsumer` through `LocalResultConsumer`. Capture `resultStore` in route callbacks, or pass it to producers when the application chooses direct exposure. A `ViewModel` preserves this in-memory owner across Android configuration changes. It does not restore pending results after process death.

Apps that use Compose Runtime 1.10.0 or later can instead keep a composition-owned store with [`retain`](https://developer.android.com/reference/kotlin/androidx/compose/runtime/retain/retain.composable). Add the `androidx.compose.runtime:runtime-retain` artifact to the app, then retain the store at the application or navigation root:

```kotlin
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.runtime.retain.retain
import com.quantipixels.gbeewa.LocalResultConsumer
import com.quantipixels.gbeewa.ResultConsumer
import com.quantipixels.gbeewa.ResultStore

@Composable
fun App() {
val resultStore = retain { ResultStore() }
val resultConsumer = remember(resultStore) { ResultConsumer(resultStore) }

CompositionLocalProvider(LocalResultConsumer provides resultConsumer) {
AppNavigation(resultStore)
}
}
```

On Android, Compose installs a lifecycle-aware retained-values store at the composition root. It keeps `resultStore` in memory when an activity is recreated for a configuration change. Use a `ViewModel` when the store belongs to an app-owned lifecycle outside composition. Neither option restores pending results after process death.

## Receive a result

The application also chooses where the receive API appears. Install `ResultEffect` in a route when screen content must remain unaware of Gbéewá. Install it directly in a screen when that coupling is useful:
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ kover = "0.9.8"
navigation2 = "2.9.0-beta03"
voyager = "1.1.0-beta03"
activity-compose = "1.10.1"
vanniktech-maven-publish = "0.34.0"
vanniktech-maven-publish = "0.35.0"

[libraries]
decompose = { module = "com.arkivanov.decompose:decompose", version.ref = "decompose" }
Expand Down
Binary file modified gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
3 changes: 1 addition & 2 deletions gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#Mon Apr 07 21:32:33 IST 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down
8 changes: 5 additions & 3 deletions gradlew

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 12 additions & 10 deletions gradlew.bat

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.