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
Binary file added .github/assets/krelay-cover.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
127 changes: 127 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
name: CI

on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]

jobs:
test-android:
name: Test (Android/JVM)
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

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

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
with:
gradle-version: wrapper

- name: Run Android unit tests
run: ./gradlew :krelay:testDebugUnitTest --no-daemon

- name: Run common tests (JVM)
run: ./gradlew :krelay:jvmTest --no-daemon 2>/dev/null || ./gradlew :krelay:testReleaseUnitTest --no-daemon

- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: android-test-results
path: krelay/build/reports/tests/

test-ios:
name: Test (iOS)
runs-on: macos-14

steps:
- uses: actions/checkout@v4

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

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
with:
gradle-version: wrapper

- name: Run iOS simulator tests
run: ./gradlew :krelay:iosSimulatorArm64Test --no-daemon

- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: ios-test-results
path: krelay/build/reports/

build:
name: Build Library
runs-on: macos-14
needs: [ test-android ]

steps:
- uses: actions/checkout@v4

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

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
with:
gradle-version: wrapper

- name: Build all targets
run: ./gradlew :krelay:assemble --no-daemon

- name: Generate Dokka docs
run: ./gradlew :krelay:dokkaHtml --no-daemon

- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: krelay-artifacts
path: krelay/build/outputs/

publish-snapshot:
name: Publish Snapshot
runs-on: macos-14
needs: [ build ]
if: github.ref == 'refs/heads/main' && github.event_name == 'push'

steps:
- uses: actions/checkout@v4

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

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
with:
gradle-version: wrapper

- name: Publish to Maven Central Snapshots
run: ./gradlew :krelay:publishAllPublicationsToOSSRHRepository --no-daemon
env:
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
continue-on-error: true
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ gradle-app.setting
!gradle/wrapper/gradle-wrapper.jar
!gradle/wrapper/gradle-wrapper.properties
.claude
.github
# Android Studio
.idea/
*.iml
Expand Down
42 changes: 42 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,46 @@ All notable changes to KRelay will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

---

## [2.1.0] - 2026-03-16

### Added
- **`dispatchWithPriority` on `KRelayInstance`**: Priority dispatch is now available on all instances (previously singleton-only). Fixes API inconsistency between singleton and instance APIs.
- **Lifecycle Integration Guide** (`docs/LIFECYCLE.md`): Comprehensive best practices for Android (Activity, Fragment, Compose) and iOS (UIViewController, SwiftUI), including screen rotation behavior and ViewModel `onCleared` patterns.
- **Flow/Coroutines Adapter** (`samples/KRelayFlowAdapter.kt`): Documentation and patterns for integrating KRelay with Kotlin coroutines and Flow.
- **CI/CD via GitHub Actions** (`.github/workflows/ci.yml`): Automated build, test (Android JVM + iOS Simulator), and snapshot publishing pipeline.
- **Version compatibility matrix** in README: Clear table of KRelay versions vs Kotlin, KMP, AGP, and platform support.
- **Dokka API documentation**: `./gradlew :krelay:dokkaHtml` generates HTML docs to `docs/api/`. Configured with source links to GitHub.
- **Persistent Dispatch** (`KRelayPersistence.kt`, `PersistedDispatch.kt`): New `dispatchPersisted<T>()` API that survives process death. Uses named actions + `ActionFactory` pattern (serializable by design — no lambda capture). Supports `restorePersistedActions()` on app restart.
- `KRelayPersistenceAdapter` interface for pluggable storage backends
- `InMemoryPersistenceAdapter` (default, no-op persistence)
- `SharedPreferencesPersistenceAdapter` for Android (`SharedPreferences`-backed)
- `NSUserDefaultsPersistenceAdapter` for iOS (`NSUserDefaults`-backed)
- `PersistedCommand` with length-prefix serialization format (handles all special characters unambiguously)
- **Compose Multiplatform integration** (`composeApp/.../KRelayCompose.kt`): `KRelayEffect<T>` composable and `rememberKRelayImpl<T>` helper for lifecycle-safe registration via `DisposableEffect`.
- **Compose Integration Guide** (`docs/COMPOSE_INTEGRATION.md`): Patterns for `DisposableEffect`, `rememberKRelayImpl`, Voyager, Navigation Compose, and SnackbarHostState.
- **SwiftUI Integration Guide** (`docs/SWIFTUI_INTEGRATION.md`): `KRelayEffect` ViewModifier, `@Observable` pattern (iOS 17+), NavigationStack, Sheet/Modal, Permissions, and XCTest patterns.
- **Scope Token API** (`scopeToken`, `cancelScope`, `dispatch(scopeToken, block)`): Selective queue cleanup by caller identity. Tag queued actions from a ViewModel with a token; call `cancelScope(token)` in `onCleared()` to release lambda captures without touching other pending actions for the same feature. `scopedToken()` utility generates a unique, human-readable token per instance.
- **`resetConfiguration()`** on `KRelayInstance` and `KRelay` singleton: Restores `maxQueueSize`, `actionExpiryMs`, and `debugMode` to defaults without touching the registry or pending queue. Useful for isolated test setup.
- **`KRelayIosHelperKt.registerFeature(instance:kClass:impl:)`**: Public Kotlin helper for KMP apps where Kotlin dispatches under the interface KClass and iOS needs to register under the same key. See `KRelayIosHelper.kt` for usage.

### Fixed
- **`KRelayMetrics` not wired to dispatch pipeline**: `recordDispatch()`, `recordQueue()`, and `recordReplay()` were never called from actual dispatch/register code. All three metrics now fire correctly from `dispatchInternal`, `dispatchWithPriorityInternal`, `dispatchPersistedInternal`, and `registerInternal` (replay path). Zero overhead when `KRelayMetrics.enabled = false` (default).
- **`KRelayMetrics.enabled` flag was never respected**: `record*` methods always recorded metrics regardless of the `metricsEnabled` flag. Fixed by adding `if (!enabled) return` guard in each method. `KRelay.metricsEnabled = true` now properly opt-in enables collection.
- **iOS Swift KClass bridging broken**: `KotlinKClass.init()` placeholder in `KRelay+Extensions.swift` created an invalid/empty KClass, causing all iOS register/dispatch operations to silently fail. Fixed by using `KRelayIosHelperKt.getKClass(obj:)` during `register(_:)` and caching the result. All iOS operations now use the correct concrete KClass. KMP pattern (Kotlin dispatches interface KClass, iOS registers) documented with `registerFeature` helper.
- **iOS main thread comment misleading**: Removed the "99% accurate" comment from `MainThreadExecutor.ios.kt`. `NSThread.isMainThread` is the correct and reliable check for all standard iOS use cases.
- **Duplicate registration warning**: Added debug log when `register<T>()` overwrites an existing alive implementation for the same feature type. Helps detect accidental double-registration.
- **Test config pollution**: `DiagnosticDemo` tests modified global `KRelay.actionExpiryMs`/`maxQueueSize` without restoring defaults after each test, causing intermittent failures in unrelated test classes. Fixed with proper `@BeforeTest`/`@AfterTest` lifecycle methods.

### Changed
- `KRelayMetrics` now exposes `enabled: Boolean` as a direct public property (replaces the convoluted private extension property workaround).
- **Code duplication reduced**: Extracted `enqueueActionUnderLock()` helper in `KRelayInstanceImpl` — shared by `dispatchInternal`, `dispatchWithPriorityInternal`, and `dispatchPersistedInternal`. Eliminates ~50 lines of duplicated queue management logic across the three dispatch paths. `KRelay.dispatchWithPriorityInternal` (singleton) now delegates to the same helper.

---

## [2.0.0] - 2026-02-04

### Added
Expand Down Expand Up @@ -130,6 +170,8 @@ None. This release is fully backward compatible with v1.x.

---

[Unreleased]: https://github.com/brewkits/KRelay/compare/v2.1.0...HEAD
[2.1.0]: https://github.com/brewkits/KRelay/compare/v2.0.0...v2.1.0
[2.0.0]: https://github.com/brewkits/KRelay/releases/tag/v2.0.0
[1.1.0]: https://github.com/brewkits/KRelay/releases/tag/v1.1.0
[1.0.0]: https://github.com/brewkits/KRelay/releases/tag/v1.0.0
Loading
Loading