All notable changes to KRelay will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
- Hardened Concurrency Safety: Persisted dispatch is now fully atomic. The decision to enqueue (impl lookup + queue insertion) happens inside a single lock, eliminating the TOCTOU race where
register()completing between the check and the enqueue would leave an action stranded. Persistence I/O (save/remove) is intentionally performed outside the lock so disk latency cannot block other threads. - Compose Module (
krelay-compose):KRelayEffect<T>andrememberKRelayImpl<T>are now published as a standalonedev.brewkits:krelay-composeartifact. The module usesapi(project(":krelay"))so all core types are automatically visible to consumers. - Enhanced iOS Registration Safety:
registerFeaturehelper now validates at runtime that the provided implementation conforms to the target interface, crashing early in debug mode and logging a clear warning in release mode. Prevents silent failures from KClass mismatches. - ProGuard/R8-safe Persistence API:
registerActionFactoryanddispatchPersistednow require an explicit stablefeatureKey: String. Class simple names can be obfuscated by R8; explicit keys survive minification. Old overloads deprecated withreplaceWithguidance. - Testability via
KRelayInstanceinterface: Persistence and dispatch methods are now declared on theKRelayInstanceinterface, allowing test doubles to implement it directly without casting toKRelayInstanceImpl. - Binary Compatibility: Compiled with Kotlin 2.1.0 (Stable) for maximum consumer compatibility.
- Thread-safe
KRelayMetrics: Allrecord*,get*, andgetAllMetrics()operations are now protected by an internal lock, preventing data races in concurrent apps. getMetricsInternalstub: iOS Swift interop helper now returns real metrics fromKRelayMetricsinstead of an empty map.- Priority Eviction Logic: When the queue is full, KRelay now evicts the lowest-priority action (not just the oldest FIFO action), correctly honouring the
ActionPriorityattribute. - Compose overwrite warning noise:
register()now only logs the overwrite warning when the incoming implementation is a different class from the existing one. Same-class replacements (e.g. Activity recreated by the Compose lifecycle) are expected and are no longer logged. restorePersistedActionsI/O inside lock: Persistence I/O (loadAll/remove) is now performed outside the instance lock; all in-memory mutations happen in a singlelock.withLockblock. This removes the risk of disk latency blocking the dispatch path.- Identity-aware
unregister: Passing animpltounregister()now only removes the registration if the stored reference is the same object, preventing a recomposing Compose screen from accidentally clearing a newer registration. VoyagerDemoGC bug:VoyagerNavigationImplis now held byremember(navigator)outside theDisposableEffect, preventing the Kotlin/Native GC from reclaiming it before the first dispatch.
2.1.0 - 2026-03-16
dispatchWithPriorityonKRelayInstance: 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 ViewModelonClearedpatterns. - 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:dokkaHtmlgenerates HTML docs todocs/api/. Configured with source links to GitHub. - Persistent Dispatch (
KRelayPersistence.kt,PersistedDispatch.kt): NewdispatchPersisted<T>()API that survives process death. Uses named actions +ActionFactorypattern (serializable by design — no lambda capture). SupportsrestorePersistedActions()on app restart.KRelayPersistenceAdapterinterface for pluggable storage backendsInMemoryPersistenceAdapter(default, no-op persistence)SharedPreferencesPersistenceAdapterfor Android (SharedPreferences-backed)NSUserDefaultsPersistenceAdapterfor iOS (NSUserDefaults-backed)PersistedCommandwith length-prefix serialization format (handles all special characters unambiguously)
- Compose Multiplatform integration (
composeApp/.../KRelayCompose.kt):KRelayEffect<T>composable andrememberKRelayImpl<T>helper for lifecycle-safe registration viaDisposableEffect. - Compose Integration Guide (
docs/COMPOSE_INTEGRATION.md): Patterns forDisposableEffect,rememberKRelayImpl, Voyager, Navigation Compose, and SnackbarHostState. - SwiftUI Integration Guide (
docs/SWIFTUI_INTEGRATION.md):KRelayEffectViewModifier,@Observablepattern (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; callcancelScope(token)inonCleared()to release lambda captures without touching other pending actions for the same feature.scopedToken()utility generates a unique, human-readable token per instance. resetConfiguration()onKRelayInstanceandKRelaysingleton: RestoresmaxQueueSize,actionExpiryMs, anddebugModeto 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. SeeKRelayIosHelper.ktfor usage.
KRelayMetricsnot wired to dispatch pipeline:recordDispatch(),recordQueue(), andrecordReplay()were never called from actual dispatch/register code. All three metrics now fire correctly fromdispatchInternal,dispatchWithPriorityInternal,dispatchPersistedInternal, andregisterInternal(replay path). Zero overhead whenKRelayMetrics.enabled = false(default).KRelayMetrics.enabledflag was never respected:record*methods always recorded metrics regardless of themetricsEnabledflag. Fixed by addingif (!enabled) returnguard in each method.KRelay.metricsEnabled = truenow properly opt-in enables collection.- iOS Swift KClass bridging broken:
KotlinKClass.init()placeholder inKRelay+Extensions.swiftcreated an invalid/empty KClass, causing all iOS register/dispatch operations to silently fail. Fixed by usingKRelayIosHelperKt.getKClass(obj:)duringregister(_:)and caching the result. All iOS operations now use the correct concrete KClass. KMP pattern (Kotlin dispatches interface KClass, iOS registers) documented withregisterFeaturehelper. - iOS main thread comment misleading: Removed the "99% accurate" comment from
MainThreadExecutor.ios.kt.NSThread.isMainThreadis 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:
DiagnosticDemotests modified globalKRelay.actionExpiryMs/maxQueueSizewithout restoring defaults after each test, causing intermittent failures in unrelated test classes. Fixed with proper@BeforeTest/@AfterTestlifecycle methods.
KRelayMetricsnow exposesenabled: Booleanas a direct public property (replaces the convoluted private extension property workaround).- Code duplication reduced: Extracted
enqueueActionUnderLock()helper inKRelayInstanceImpl— shared bydispatchInternal,dispatchWithPriorityInternal, anddispatchPersistedInternal. 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
- Instance Creation: New
KRelay.create(scopeName)factory method for creating isolated instances - Builder Pattern: New
KRelay.builder(scopeName)for configuring instances with custom settings - Full Instance Isolation: Each instance has independent registry, queue, and lock
- Per-Instance Configuration: Customize
maxQueueSize,actionExpiryMs, anddebugModeper instance - DI Integration: First-class support for dependency injection frameworks (Koin, Hilt)
- Super App Support: Enables multiple independent modules without feature name conflicts
- Duplicate Scope Name Detection: Warning logged in debug mode when creating instances with duplicate names
- Input Validation: Builder parameters validated at creation time
maxQueueSizemust be > 0actionExpiryMsmust be > 0scopeNamemust not be blank
- Clear Error Messages: Validation errors include actual invalid values for easier debugging
- New migration guide for v1.x to v2.0 (
docs/MIGRATION_V2.md) - Super App architecture example with full implementation (
docs/SUPER_APP_EXAMPLE.md) - DI integration guide for Koin and Hilt (
docs/DI_INTEGRATION.md) - Comprehensive technical review document (
docs/COMPREHENSIVE_REVIEW_V2.md) - v2.0.0 improvements summary (
docs/V2_0_0_IMPROVEMENTS.md)
- 10 new instance isolation tests
- 5 new validation tests
- All tests pass: 100% (15/15 instance tests)
- Verified backward compatibility: all existing tests pass
- Refactored
KRelaysingleton to facade pattern, delegating to internaldefaultInstance - Extracted core logic into reusable
KRelayInstanceImplclass - Improved code organization with clear separation between singleton and instance APIs
KRelayInstanceinterface with extension functions for type-safe operationsregister,dispatch,unregister,isRegistered,getPendingCount,clearQueueavailable on instances- Non-reified methods (
getDebugInfo,dump,reset) available directly on interface
- Instance API overhead: <5% compared to singleton (negligible)
- Memory footprint: ~800 bytes per instance
- Lock granularity: Per-instance locks (no global contention)
- All instance operations protected by per-instance locks
- Proper lock scope to prevent deadlocks
- Verified by stress tests
- Weak references prevent memory leaks
- Bounded queues with configurable size limits
- Automatic cleanup of expired actions
✅ 100% Backward Compatible
- All existing v1.x code works without modification
- Singleton API delegates to default internal instance
- No breaking changes to public API
- Existing tests pass without changes
Migration from v1.x to v2.0 is optional and can be done incrementally:
- No Changes Required: Existing code continues to work
- Recommended for New Projects: Use instance API with DI
- Recommended for Super Apps: Migrate to instance API for module isolation
- Simple Apps: Can continue using singleton API
See docs/MIGRATION_V2.md for detailed migration guide.
None. This release is fully backward compatible with v1.x.
1.1.0 - 2025-XX-XX
- Thread safety improvements with atomic operations in stress tests
- Enhanced documentation
- Bug fixes and stability improvements
- Improved test coverage
1.0.0 - 2024-XX-XX
- Initial release
- Singleton-based API
- WeakRef registry for automatic memory management
- Sticky queue for action replay
- Thread-safe operations
- Platform-specific main thread dispatch (Android Looper, iOS GCD)
- Debug mode and metrics
- Comprehensive test suite
KRelay.register<T>(impl)- Register platform implementationsKRelay.dispatch<T> { }- Dispatch actions to platformKRelay.unregister<T>()- Unregister implementationsKRelay.isRegistered<T>()- Check registration statusKRelay.getPendingCount<T>()- Get queued action countKRelay.clearQueue<T>()- Clear pending actionsKRelay.getDebugInfo()- Get debug informationKRelay.dump()- Dump state to consoleKRelay.reset()- Clear all registrations and queues