-
Notifications
You must be signed in to change notification settings - Fork 3
Refactor to context-params #128
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
…update imports/dependencies accordingly
Refactor lifecycle-aware use case operation into shared `executeWithLifecycle` functions for streamlined component integration. Remove duplicate implementations and apply adjustments in affected areas.
Walkthrough
Sequence Diagram(s)sequenceDiagram
autonumber
actor UI
participant FC as FirstComponent (CoroutineScopeOwner)
participant L as LifecycleOwner
participant TS as TimeStampUseCase (FlowUseCase)
participant C as FlowUseCaseConfig
participant W as Worker Dispatcher
participant H as UseCaseErrorHandler
UI->>FC: doOnCreate()
FC->>FC: runTimestamp()
FC->>C: Build FlowUseCaseConfig { onStart/onNext/onError/onComplete }
FC->>L: Observe lifecycle(minState=RESUMED)
L-->>FC: Emits active/inactive states
FC->>TS: build(Unit)
TS-->>FC: Flow<Instant>
FC->>W: flowOn(worker)
FC->>FC: flatMapLatest(active? flow : emptyFlow)
FC->>FC: onStart -> update UI
FC->>FC: onEach(onNext) -> updateCreatedAtTimestamp(Instant)
FC->>FC: onCompletion
alt Cancellation
FC-->>FC: Ignore
else Error
FC->>H: globalOnErrorLogger(error)
FC->>FC: onError(error)
else Success
FC-->>FC: onComplete()
end
sequenceDiagram
autonumber
actor UI
participant VM as CoroutineScopeOwner
participant U as UseCase<ARGS,T>
participant C as UseCaseConfig
participant W as Worker Dispatcher
participant H as UseCaseErrorHandler
UI->>VM: Execute use case
VM->>C: Build UseCaseConfig { onStart/onSuccess/onError/disposePrevious }
VM->>VM: Cancel previous if configured
VM->>VM: onStart()
VM->>W: async(start=LAZY) { U.build(args) }
W-->>VM: result or error
alt Success
VM-->>VM: onSuccess(value)
else Cancellation
VM-->>VM: propagate CancellationException
else Error
VM->>H: globalOnErrorLogger(error)
VM-->>VM: onError(error)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🧹 Nitpick comments (8)
shared/arkitekt-cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/scope/FlowUseCaseConfig.kt (1)
20-27: Consider improving type safety for the mapping transformation.The current design allows
Builder<T, M>to be instantiated without ensuring thatonMapis provided whenT != M. This could lead to runtime issues when a transformation is expected but not configured.Consider one of these approaches:
Option 1: Separate builders for mapped vs unmapped scenarios
+class Builder<T> { + // For T -> T scenarios (no mapping) + fun build(): FlowUseCaseConfig<T, T> +} + +class MappingBuilder<T, M> { + // For T -> M scenarios (requires mapping) + fun onMap(onMap: (T) -> M): MappingBuilder<T, M> + fun build(): FlowUseCaseConfig<T, M> +}Option 2: Sealed interface with type constraints
+sealed interface ConfigBuilder<T, M> { + class Direct<T> : ConfigBuilder<T, T> + class Mapped<T, M>(val onMap: (T) -> M) : ConfigBuilder<T, M> +}shared/arkitekt-cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/scope/CoroutineScopeOwner.kt (2)
33-47: Consider documenting the rethrow behavior in defaultErrorHandlerThe
launchWithHandlermethod provides useful error handling, but the default behavior of rethrowing exceptions indefaultErrorHandler(line 53) could cause crashes if not overridden. Consider documenting this behavior more prominently in the method's KDoc.Add a warning to the KDoc:
/** * Launch suspend [block] in [viewModelScope]. * * Encapsulates this call with try catch block and when an exception is thrown * then it is logged in [UseCaseErrorHandler.globalOnErrorLogger] and handled by [defaultErrorHandler]. * * If exception is [CancellationException] then [defaultErrorHandler] is not called and * [UseCaseErrorHandler.globalOnErrorLogger] is called only if the root cause of this exception is not * [CancellationException] (e.g. when [Result.getOrCancel] is used). + * + * @warning The default implementation of [defaultErrorHandler] rethrows the exception, + * which will crash the application. Override [defaultErrorHandler] to handle errors gracefully. */
56-56: Add cause parameter to CancellationException for better debuggingThe
getOrCancelextension throws aCancellationExceptionwithout preserving the original error as a cause, which makes debugging harder.-fun <T> Result<T>.getOrCancel(): T = this.getOrElse { throw CancellationException("Result was not Success") } +fun <T> Result<T>.getOrCancel(): T = this.getOrElse { throw CancellationException("Result was not Success", it) }shared/arkitekt-cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/scope/UseCaseConfig.kt (1)
71-71: Remove commented closing braceThere's a commented closing brace that appears to be leftover from development.
-//}shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/ui/firstScreen/FirstComponent.kt (1)
80-94: Consider extracting the interval constantThe hardcoded interval value of 1000ms could be extracted to a constant for better maintainability, similar to
COUNTER_ALERT_AT_SECONDS.companion object { private const val COUNTER_ALERT_AT_SECONDS = 30L + private const val COUNTER_INTERVAL_MS = 1000L } private fun observeCounter() { - counterUseCase.execute(CounterUseCaseArgs(interval = 1000.milliseconds)) { + counterUseCase.execute(CounterUseCaseArgs(interval = COUNTER_INTERVAL_MS.milliseconds)) {shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/tools/LifecycleExecution.kt (2)
46-78: Consider simplifying the lifecycle state management logicThe current implementation has repetitive code for managing
enableExecutionstate across different lifecycle callbacks. This could be simplified using a helper function.+ fun updateExecutionState(currentState: Lifecycle.State) { + enableExecution.value = currentState.ordinal >= minActiveState.ordinal + } + componentContext.lifecycle.subscribe( onCreate = { - if (minActiveState == Lifecycle.State.CREATED) { - enableExecution.value = true - } + if (minActiveState == Lifecycle.State.CREATED) updateExecutionState(Lifecycle.State.CREATED) }, onDestroy = { - if (minActiveState == Lifecycle.State.CREATED) { - enableExecution.value = false - } + if (minActiveState == Lifecycle.State.CREATED) updateExecutionState(Lifecycle.State.DESTROYED) }, onStart = { - if (minActiveState == Lifecycle.State.STARTED) { - enableExecution.value = true - } + if (minActiveState == Lifecycle.State.STARTED) updateExecutionState(Lifecycle.State.STARTED) }, onStop = { - if (minActiveState == Lifecycle.State.STARTED) { - enableExecution.value = false - } + if (minActiveState == Lifecycle.State.STARTED) updateExecutionState(Lifecycle.State.CREATED) }, onResume = { - if (minActiveState == Lifecycle.State.RESUMED) { - enableExecution.value = true - } + if (minActiveState == Lifecycle.State.RESUMED) updateExecutionState(Lifecycle.State.RESUMED) }, onPause = { - if (minActiveState == Lifecycle.State.RESUMED) { - enableExecution.value = false - } + if (minActiveState == Lifecycle.State.RESUMED) updateExecutionState(Lifecycle.State.STARTED) }, )
102-102: Consider adding a comment explaining the empty catch blockWhile the comment indicates errors are handled in
onCompletion, it would be clearer to explain why we need the catch block at all.- .catch { /* handled in onCompletion */ } + .catch { /* Suppress exceptions as they're already handled in onCompletion */ }shared/arkitekt-cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/UseCase.kt (1)
85-119: Verify consistent parameter naming across execute methods.The suspend version uses
cancelPreviouswhile the async version usesdisposePrevious(via config). Consider aligning the naming for consistency.Apply this diff to align the parameter naming:
- cancelPrevious: Boolean = true, + disposePrevious: Boolean = true, ): Result<T> { - if (cancelPrevious) { + if (disposePrevious) {
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (21)
androidApp/build.gradle.kts(3 hunks)gradle.properties(1 hunks)gradle/libs.versions.toml(1 hunks)settings.gradle.kts(1 hunks)shared/app/build.gradle.kts(2 hunks)shared/arkitekt-cr-usecases/build.gradle.kts(1 hunks)shared/arkitekt-cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/FlowUseCase.kt(2 hunks)shared/arkitekt-cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/UseCase.kt(2 hunks)shared/arkitekt-cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/scope/CoroutineScopeOwner.kt(2 hunks)shared/arkitekt-cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/scope/FlowUseCaseConfig.kt(1 hunks)shared/arkitekt-cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/scope/FlowUseCaseExecutionScope.kt(0 hunks)shared/arkitekt-cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/scope/SingleUseCaseExecutionScope.kt(0 hunks)shared/arkitekt-cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/scope/UseCaseConfig.kt(1 hunks)shared/arkitekt-cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/scope/UseCaseExecutionScope.kt(0 hunks)shared/arkitekt-cr-usecases/src/commonTest/kotlin/app/futured/arkitekt/crusecases/base/BaseUseCaseExecutionScopeTest.kt(2 hunks)shared/arkitekt-decompose/architecture/src/commonMain/kotlin/app/futured/arkitekt/decompose/ext/NavigationExt.kt(0 hunks)shared/arkitekt-decompose/architecture/src/commonMain/kotlin/app/futured/arkitekt/decompose/presentation/BaseComponent.kt(2 hunks)shared/feature/build.gradle.kts(2 hunks)shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/domain/TimeStampUseCase.kt(1 hunks)shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/tools/LifecycleExecution.kt(1 hunks)shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/ui/firstScreen/FirstComponent.kt(4 hunks)
💤 Files with no reviewable changes (4)
- shared/arkitekt-decompose/architecture/src/commonMain/kotlin/app/futured/arkitekt/decompose/ext/NavigationExt.kt
- shared/arkitekt-cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/scope/UseCaseExecutionScope.kt
- shared/arkitekt-cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/scope/SingleUseCaseExecutionScope.kt
- shared/arkitekt-cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/scope/FlowUseCaseExecutionScope.kt
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-06-20T08:58:15.363Z
Learnt from: matejsemancik
PR: futuredapp/kmp-futured-template#125
File: shared/arkitekt-cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/scope/FlowUseCaseExecutionScope.kt:17-21
Timestamp: 2025-06-20T08:58:15.363Z
Learning: The useCaseJobPool map in FlowUseCaseExecutionScope overwrites job references for each UseCase instance rather than accumulating them, so cancelled jobs don't cause memory leaks as they become eligible for garbage collection when their references are replaced.
Applied to files:
shared/arkitekt-cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/scope/FlowUseCaseConfig.ktshared/arkitekt-cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/FlowUseCase.kt
🧬 Code Graph Analysis (5)
shared/arkitekt-cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/scope/CoroutineScopeOwner.kt (1)
shared/arkitekt-cr-usecases/src/commonTest/kotlin/app/futured/arkitekt/crusecases/UseCaseExecutionScopeTest.kt (3)
defaultErrorHandler(203-205)defaultErrorHandler(224-226)defaultErrorHandler(245-247)
shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/tools/LifecycleExecution.kt (1)
shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/domain/TimeStampUseCase.kt (2)
build(11-19)build(13-18)
shared/arkitekt-cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/UseCase.kt (2)
shared/arkitekt-cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/FlowUseCase.kt (3)
execute(48-81)execute(84-85)build(31-31)shared/arkitekt-cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/scope/UseCaseConfig.kt (1)
build(63-68)
shared/arkitekt-cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/FlowUseCase.kt (9)
shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/domain/TimeStampUseCase.kt (2)
build(11-19)build(13-18)shared/arkitekt-cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/scope/FlowUseCaseConfig.kt (1)
build(82-89)shared/arkitekt-cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/UseCase.kt (4)
build(26-26)execute(38-39)execute(54-83)execute(100-119)shared/arkitekt-cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/scope/UseCaseConfig.kt (1)
build(63-68)shared/arkitekt-cr-usecases/src/commonTest/kotlin/app/futured/arkitekt/crusecases/usecases/TestFailureUseCase.kt (2)
build(5-8)build(7-7)shared/arkitekt-cr-usecases/src/commonTest/kotlin/app/futured/arkitekt/crusecases/usecases/TestFlowUseCase.kt (1)
build(13-14)shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/domain/CounterUseCase.kt (2)
build(12-22)build(15-21)shared/arkitekt-cr-usecases/src/commonTest/kotlin/app/futured/arkitekt/crusecases/usecases/TestFailureFlowUseCase.kt (2)
build(7-12)build(9-11)shared/arkitekt-cr-usecases/src/commonTest/kotlin/app/futured/arkitekt/crusecases/usecases/TestUseCase.kt (2)
build(6-12)build(8-11)
shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/ui/firstScreen/FirstComponent.kt (2)
shared/arkitekt-cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/scope/FlowUseCaseConfig.kt (2)
onNext(41-43)onError(58-60)shared/arkitekt-cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/scope/UseCaseConfig.kt (2)
onError(48-50)onSuccess(39-41)
🪛 detekt (1.23.8)
shared/arkitekt-cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/UseCase.kt
[warning] 75-75: The caught exception is swallowed. The original exception could be lost.
(detekt.exceptions.SwallowedException)
🔇 Additional comments (27)
shared/arkitekt-cr-usecases/build.gradle.kts (1)
24-26: LGTM! Context parameters support added correctly.The
-Xcontext-parameterscompiler argument has been properly added to enable Kotlin context parameters feature, which aligns with the PR's objective to refactor from extension methods to context parameters for increased flexibility.shared/arkitekt-cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/scope/FlowUseCaseConfig.kt (1)
8-15: LGTM! Clean data class design with proper encapsulation.The private constructor ensures controlled instantiation through the builder pattern, and the nullable
onMapparameter provides flexibility for optional transformations.gradle/libs.versions.toml (2)
12-13: Confirm Koin 4.1.0 Upgrade with KoinAnnotationsOur scan didn’t find any direct Koin DSL usage (
module { … }orstartKoin { … })—the project relies entirely on KoinAnnotations (@Module,@Single,@Factory) via KSP. Before merging this bump:
- Verify that koin-annotations 2.1.0 supports Koin core 4.1.0
- Ensure the KSP plugin version in your build scripts aligns with koin-annotations 2.1.0
- Review a few generated modules to confirm they import and invoke the correct Koin 4.1.0 APIs (e.g.
single { … },factory { … })
21-23: Compatibility Verification: Apollo 4.3.1, Ktorfit 2.6.1 & Ktor 3.2.1Please run through this checklist to ensure there are no hidden incompatibilities in your Kotlin Multiplatform setup:
• Align Gradle & Kotlin toolchain
– Gradle 8+ with Kotlin 1.9+ (JVM/Android) or Kotlin 2.x (native/JS/Wasm)
– Matches JetBrains’ KMP compatibility matrix• Apollo Kotlin 4.x migration
– Switch fromapolloMetadatatoService.dependsOn(...)
– Centralize schema–type generation in your designated schema module• HTTP engine consistency
– Default is OkHttp on JVM/Android – or addapollo-kotlin-ktor-supportto use Ktor Client
– Ensure you don’t mix Ktor 2.x and Ktor 3.x artifacts; all Ktor client libs should be 3.x• Ktorfit & Ktor alignment
– Apply the Ktorfit Gradle plugin as per its 2.6.1 changelog
– Verify or force its transitive Ktor client to match your explicitktor = "3.2.1"• Multiplatform features
– Confirm enabled Apollo features (e.g. normalized-cache-sqlite, http-cache) are supported on all your targetsTo double-check your matrix, please share your exact Kotlin, Gradle, AGP and KSP versions so we can spot any toolchain mismatches.
settings.gradle.kts (2)
22-22: LGTM! Module structure reorganization is clear.The change from
:shared:arkitekt-decomposeto:shared:arkitekt-decompose:architectureindicates a logical separation of concerns within the Arkitekt Decompose module structure.
22-24: All arkitekt-decompose module references are up-to-dateI’ve verified that there are no remaining references to the old “arkitekt-decompose” aggregate module and that each submodule is correctly wired:
• settings.gradle.kts
– includes “:shared:arkitekt-decompose:architecture”, “:annotation”, and “:processor”
• shared/feature/build.gradle.kts
– implementation(projects.shared.arkitektDecompose.architecture)
– implementation(projects.shared.arkitektDecompose.annotation)
• shared/app/build.gradle.kts
– export(projects.shared.arkitektDecompose.architecture)
– api(projects.shared.arkitektDecompose.architecture)
• androidApp/build.gradle.kts
– implementation(projects.shared.arkitektDecompose.architecture)
• shared/arkitekt-decompose/processor/build.gradle.kts
– implementation(projects.shared.arkitektDecompose.annotation)
• convention-plugins/src/main/kotlin/conventions-annotations-processor.gradle.kts
– add("kspCommonMainMetadata", project(":shared:arkitekt-decompose:processor"))No legacy references remain.
shared/arkitekt-cr-usecases/src/commonTest/kotlin/app/futured/arkitekt/crusecases/base/BaseUseCaseExecutionScopeTest.kt (2)
3-3: Import updated to match new interface.The import change from
UseCaseExecutionScopetoCoroutineScopeOwnercorrectly aligns with the architectural migration.
13-13: Class inheritance updated to new scope interface.The class now correctly extends
CoroutineScopeOwnerinstead ofUseCaseExecutionScope, maintaining the same test infrastructure while supporting the new context parameter-based execution model.shared/arkitekt-decompose/architecture/src/commonMain/kotlin/app/futured/arkitekt/decompose/presentation/BaseComponent.kt (2)
3-3: Import updated for architectural migration.The import change from
UseCaseExecutionScopetoCoroutineScopeOwnercorrectly reflects the shift to context parameter-based execution.
29-29: Interface change aligns with PR objectives.The class now implements
CoroutineScopeOwnerinstead ofUseCaseExecutionScope, enabling the new context parameter-based execution methods while maintaining existing functionality through the sameviewModelScopeproperty.shared/feature/build.gradle.kts (2)
38-40: Context parameters compiler flag enables new execution model.The
-Xcontext-parameterscompiler option correctly enables the Kotlin context receivers feature, which is essential for the new execution methods that use context parameters instead of extension functions.
58-58: Dependency updated to architecture submodule.The change from
arkitektDecomposetoarkitektDecompose.architecturecorrectly reflects the modular restructuring while maintaining access to the base component functionality.shared/app/build.gradle.kts (2)
50-50: iOS framework export updated for architecture submodule.The export statement correctly points to the new architecture submodule, ensuring iOS consumers have access to the refactored base components.
91-91: API dependency updated for architecture submodule.The dependency change maintains the same public API surface for iOS while correctly referencing the restructured module hierarchy.
shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/domain/TimeStampUseCase.kt (2)
11-12: Dependency injection and inheritance properly configured.The
@Factoryannotation correctly registers the use case with Koin DI, and the class properly extendsFlowUseCase<Unit, Instant>to leverage the new execution framework.
16-16: Use consistent delay intervals.The 1000ms delay creates exactly 1-second intervals between emissions, which is appropriate for a timestamp service.
androidApp/build.gradle.kts (1)
18-20: LGTM! Correctly migrated to the new compiler options DSLThe migration from
kotlinOptionstocompilerOptionswithJvmTargetis implemented correctly and aligns with Kotlin 2.0+ best practices.shared/arkitekt-cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/scope/UseCaseConfig.kt (1)
63-68: LGTM! Well-structured builder pattern with sensible defaultsThe builder implementation correctly provides defaults for all optional parameters and maintains a clean API. The default error handler appropriately rethrows exceptions to ensure errors are not silently swallowed.
shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/ui/firstScreen/FirstComponent.kt (2)
54-64: Good use of lifecycle-aware execution for timestamp updatesThe implementation correctly leverages the new
executeWithLifecycleAPI to manage the timestamp stream based on the component's lifecycle, preventing memory leaks and unnecessary computations.
68-78: Improved error handling in syncData()The refactored implementation with explicit
onSuccessandonErrorhandlers provides better separation of concerns and clearer error messaging.shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/tools/LifecycleExecution.kt (1)
88-101: Well-implemented error handling with proper distinction of cancellationThe error handling correctly distinguishes between cancellation (which is expected during lifecycle transitions) and actual errors, with appropriate logging via
UseCaseErrorHandler.shared/arkitekt-cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/UseCase.kt (2)
29-39: LGTM! Clean API for Unit-based use cases.The context receiver pattern provides a clean extension for Unit-based use cases, eliminating the need to pass Unit explicitly. This improves the developer experience.
41-83: Consider extracting config building to reduce duplication.The configuration building logic appears in multiple places across UseCase and FlowUseCase. Consider extracting this pattern to a shared utility function.
Additionally, the static analysis warning about swallowing CancellationException on line 75 is a false positive - the exception is being intentionally ignored as documented, which is the correct behavior for cooperative cancellation.
shared/arkitekt-cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/FlowUseCase.kt (4)
34-81: LGTM! Well-structured flow execution with proper error handling.The implementation correctly handles cancellation, error logging, and lifecycle callbacks. The error handling pattern with onCompletion and catch ensures all errors are properly captured and logged.
79-79: Empty catch block is intentional and correctly documented.The empty catch block is appropriately handled in onCompletion, preventing duplicate error handling. The comment clearly indicates this is intentional.
83-89: LGTM! Consistent convenience methods for Unit-based flow use cases.These extension functions provide a clean API for Unit-based flow use cases, maintaining consistency with the UseCase pattern.
91-139: Consider null safety implications of mapNotNull.The use of
mapNotNullon line 121 will silently filter out null values returned byonMap. This could lead to unexpected behavior if the mapper function intentionally returns null for certain values.Consider documenting this behavior or using a different approach:
- .mapNotNull { flowUseCaseConfig.onMap?.invoke(it) } + .map { flowUseCaseConfig.onMap?.invoke(it) } + .filterNotNull()Or if null filtering is intentional, add documentation:
/** * ... * Note: Values mapped to null by onMap will be filtered out and not passed to onNext. * ... */
| org.gradle.caching=false | ||
| org.gradle.configureondemand=true | ||
| org.gradle.configuration-cache=true | ||
| org.gradle.configuration-cache=false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Verify the necessity of disabling Gradle optimizations.
Disabling both caching and configuration cache will significantly slow down build times. These optimizations are generally safe and recommended for most projects.
Please confirm that these optimizations need to be disabled for this specific refactor. If they're causing issues with the context parameters feature or the architectural changes, consider:
- Identifying the specific incompatible plugins or configurations
- Using selective exclusions rather than global disabling
- Re-enabling these optimizations once the refactor stabilizes
🏁 Script executed:
#!/bin/bash
# Search for any build script configurations that might be incompatible with Gradle caching
echo "Searching for potential caching incompatibilities..."
# Look for custom tasks that might not be cache-friendly
ast-grep --pattern 'task $_ {'
# Check for configurations that might conflict with configuration cache
rg -A 3 -B 3 "configuration.*cache|cache.*configuration"
# Look for any explicit cache-related configurations
rg -A 5 "gradle\.cache|caching|configuration-cache"Length of output: 1151
Re-enable Gradle caching and configuration-cache by default
Our search didn’t surface any custom tasks or configurations that are known to break Gradle’s caching features—only the flags in gradle.properties themselves. Disabling both org.gradle.caching and org.gradle.configuration-cache will noticeably slow down every build. Unless you’ve encountered concrete cache-related errors in this refactor, please restore the defaults and, if needed, opt out specific tasks or plugins instead of disabling globally.
• File: gradle.properties
Lines 4–6:
- org.gradle.caching=false
+ org.gradle.caching=true
- org.gradle.configuration-cache=false
+ org.gradle.configuration-cache=trueIf you’ve run into build failures or stale outputs tied to configuration-cache or build caching, please document those errors and exclude only the incompatible tasks/plugins. Otherwise, let’s turn these optimizations back on to keep builds fast.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| org.gradle.caching=false | |
| org.gradle.configureondemand=true | |
| org.gradle.configuration-cache=true | |
| org.gradle.configuration-cache=false | |
| org.gradle.caching=true | |
| org.gradle.configureondemand=true | |
| org.gradle.configuration-cache=true |
🤖 Prompt for AI Agents
In gradle.properties around lines 4–6, the file has globally disabled Gradle
build cache and configuration-cache which slows builds; revert these to defaults
by setting org.gradle.caching=true and org.gradle.configuration-cache=true (or
remove those properties so Gradle uses defaults), keep
org.gradle.configureondemand as desired, and if you previously encountered
cache-related failures, document the errors and opt-out only the incompatible
tasks/plugins instead of disabling caching globally.
| kotlin = "2.2.0" | ||
| ksp = "2.2.0-2.0.2" # Must be compatible with: `kotlin` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Verify compatibility between Kotlin 2.2.0 and KSP 2.2.0-2.0.2.
The major Kotlin version upgrade may introduce breaking changes or require adjustments in existing code.
🌐 Web query:
Are there any known compatibility issues or breaking changes when upgrading from Kotlin 2.1.10 to 2.2.0, especially regarding context parameters feature?
💡 Result:
Short answer: Yes — there are a few compatibility changes you should be aware of when moving from Kotlin 2.1.10 → 2.2.0, and the biggest user-facing change around the context feature is that context receivers are being replaced by the new context parameters preview. You can keep compiling, but you’ll likely need small code/build/IDE changes if your code uses context receivers (or other now-stricter language checks). (blog.jetbrains.com, kotlinlang.org)
What matters most (context parameters)
- Context parameters replace the older context receivers feature in 2.2.0 (context parameters are available as a preview/preview‑flag feature). If you used context receivers in 2.1.x, you should migrate to context parameters; the compiler/IDE will help. (Blog: April 2025; docs: 2.2.0). (blog.jetbrains.com, kotlinlang.org)
- To enable context parameters you must use the compiler flag -Xcontext-parameters (or add it to the kotlin.compilerOptions.freeCompilerArgs). Specifying both -Xcontext-receivers and -Xcontext-parameters at once is an error. (kotlinlang.org)
- Migration notes / limitations in 2.2.0: callable references to functions with context parameters are NOT supported in 2.2.0 (you’ll need to replace such callable references with lambdas or other workarounds). Context receivers in classes have no direct automatic counterpart and require manual refactoring. IntelliJ (2025.1+) provides automatic quick‑fixes/inspections to help migrate file/module/project scopes. (blog.jetbrains.com, kotlinlang.org)
- Some restrictions still apply to context parameters in 2.2.0 (constructors can’t declare them; properties with context parameters can’t have backing fields, initializers, or delegation). Check the docs for current list. (kotlinlang.org)
Other important breaking/deprecation items in Kotlin 2.2.0 you should check when upgrading from 2.1.10
- kotlinOptions { } in Gradle: the old kotlinOptions block’s deprecation level is raised — use the new compilerOptions { } block (migrate build scripts). Failing to do this will become an error. (kotlinlang.org)
- Compatibility/strictness changes (examples from the 2.2 compatibility guide you should scan for in your codebase):
- field-targeted annotations on annotation properties are now an error.
- reified type parameters are forbidden on type aliases (was warned previously; now an error).
- stricter visibility / type accessibility checks (e.g., exposing private types via non-private inline functions now errors).
- non-local returns in lambdas used as a parameter default value are now an error.
- behavioral change: type checks on inline value classes for Number/Comparable were corrected.
These are source/behavioral changes that can break code that previously compiled. See the compatibility guide and scan your code. (kotlinlang.org)
Practical checklist to upgrade safely
- Update Kotlin version to 2.2.0 in your build. Update your IDE plugin / IntelliJ to a version that bundles the 2.2.0 Kotlin plugin (IntelliJ 2025.1+ recommended for migration tooling). (kotlinlang.org, blog.jetbrains.com)
- Replace kotlinOptions { } with compilerOptions { } in Gradle builds. (kotlinlang.org)
- If you used context receivers:
- Decide to migrate to context parameters and add -Xcontext-parameters to compiler free args; remove -Xcontext-receivers.
- Use the IDE quick‑fix (Analyze / Run inspection / Migrate from context receivers to context parameters) to do bulk migrations where possible.
- Manually fix cases the tool can’t convert (callable references, context receivers in classes). (blog.jetbrains.com, kotlinlang.org)
- Run a full clean build and test suite; fix compilation errors reported by the stricter checks (visibility/type accessibility, typealias/reified, annotation targets, etc.). Review the 2.2 compatibility guide and search your repo for patterns called out there. (kotlinlang.org)
- If you relied on experimental behavior that changed between 2.1.10 and 2.2.0, add small targeted tests and run them — especially for inline/inline-class behaviors and reflection/IR plugins. (kotlinlang.org)
If you want, I can:
- scan your build.gradle(.kts) and a few Kotlin source files (paste them) for the specific patterns that will break (context receivers usage, kotlinOptions, callable references to context functions, reified in typealias, etc.) and give a line‑by‑line migration plan; or
- produce the exact Gradle snippets to convert kotlinOptions → compilerOptions and to enable the -Xcontext-parameters flag.
Which would you prefer?
Citations:
- 1: https://blog.jetbrains.com/kotlin/2025/04/update-on-context-parameters/?utm_source=chatgpt.com
- 2: https://kotlinlang.org/docs/whatsnew22.html?utm_source=chatgpt.com
- 3: https://blog.jetbrains.com/kotlin/2025/04/update-on-context-parameters/?utm_source=chatgpt.com
- 4: https://kotlinlang.org/docs/whatsnew22.html?utm_source=chatgpt.com
- 5: https://kotlinlang.org/docs/whatsnew22.html?utm_source=chatgpt.com
- 6: https://blog.jetbrains.com/kotlin/2025/04/update-on-context-parameters/?utm_source=chatgpt.com
- 7: https://kotlinlang.org/docs/context-parameters.html?utm_source=chatgpt.com
- 8: https://kotlinlang.org/docs/context-parameters.html?utm_source=chatgpt.com
- 9: https://kotlinlang.org/docs/whatsnew22.html?utm_source=chatgpt.com
- 10: https://kotlinlang.org/docs/compatibility-guide-22.html?utm_source=chatgpt.com
- 11: https://kotlinlang.org/docs/whatsnew22.html?utm_source=chatgpt.com
- 12: https://blog.jetbrains.com/kotlin/2025/04/update-on-context-parameters/?utm_source=chatgpt.com
- 13: https://kotlinlang.org/docs/whatsnew22.html?utm_source=chatgpt.com
- 14: https://blog.jetbrains.com/kotlin/2025/04/update-on-context-parameters/?utm_source=chatgpt.com
- 15: https://kotlinlang.org/docs/context-parameters.html?utm_source=chatgpt.com
- 16: https://kotlinlang.org/docs/compatibility-guide-22.html?utm_source=chatgpt.com
- 17: https://kotlinlang.org/docs/compatibility-guide-22.html?utm_source=chatgpt.com
Ensure Kotlin 2.2.0 and KSP 2.2.0-2.0.2 Compatibility
Kotlin 2.2.0 introduces several source‐breaking changes—including the switch from context receivers to context parameters (preview), stricter language checks, and a new Gradle DSL for compiler flags—that you must address when upgrading. Please update your build and code as follows:
• File gradle/libs.versions.toml (lines 3–4)
kotlin = "2.2.0"
ksp = "2.2.0-2.0.2" # aligns with Kotlin 2.2.0• Build script changes
- Replace the deprecated
kotlinOptions { … }block withcompilerOptions { … }. - Add
-Xcontext-parameterstocompilerOptions.freeCompilerArgsand remove any-Xcontext-receiversflags.
• Context-parameters migration
- Use the IDE’s “Migrate from context receivers” inspection to bulk-convert to context parameters.
- Manually refactor any remaining context-receiver usage (especially in classes) and replace callable references to context-parameter functions with lambdas.
• Global compatibility checks
- Scan for now-errors such as reified type parameters on type aliases, field-targeted annotation property issues, stricter visibility/type accessibility violations, and non-local returns in default lambdas.
- Review the Kotlin 2.2 compatibility guide: https://kotlinlang.org/docs/compatibility-guide-22.html
• Verify changes
- Update your IDE to IntelliJ 2025.1+ (bundles Kotlin 2.2.0 tooling).
- Run a clean build and full test suite; resolve any compilation or runtime failures.
- Add targeted tests if you relied on inline-class or IR plugin behaviors that changed.
🤖 Prompt for AI Agents
In gradle/libs.versions.toml around lines 3–4, you've pinned kotlin = "2.2.0"
and ksp = "2.2.0-2.0.2" but need to update build and code to be compatible with
Kotlin 2.2.0: replace any kotlinOptions { … } blocks in Gradle scripts with
compilerOptions { … }, add "-Xcontext-parameters" to
compilerOptions.freeCompilerArgs and remove any "-Xcontext-receivers" flags, run
the IDE “Migrate from context receivers” refactor and manually convert remaining
context-receiver usages (replace callable references to context-parameter
functions with lambdas where necessary), scan and fix new compilation errors
noted in the compatibility guide (reified type params on type aliases,
field-targeted annotation property issues, stricter visibility/type
accessibility, non-local returns in default lambdas, etc.), update to IntelliJ
2025.1+ Kotlin tooling, then run a clean build and full test suite and add tests
for any changed inline/IR plugin behavior.
| fun build(): FlowUseCaseConfig<T, M> = FlowUseCaseConfig( | ||
| onStart ?: { }, | ||
| onNext ?: { }, | ||
| onError ?: { throw it }, | ||
| onComplete ?: { }, | ||
| disposePrevious, | ||
| onMap, | ||
| ) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Verify the default error handling strategy.
The default error handling { throw it } will rethrow exceptions, which might not be appropriate in all contexts, especially for UI-facing flow operations.
Please confirm this is the intended behavior. Consider whether a more graceful default error handler (e.g., logging and continuing) would be more appropriate for flow use cases.
🏁 Script executed:
#!/bin/bash
# Search for existing error handling patterns in the codebase
echo "Searching for existing error handling patterns in use cases..."
# Look for existing error handling in use case implementations
ast-grep --pattern 'onError = { $$ }'
# Check for any custom error handlers
rg -A 5 "onError|errorHandler|handleError"
# Look for existing flow error handling patterns
ast-grep --pattern 'catch { $$ }'Length of output: 34060
Adjust default error handling in FlowUseCaseConfig
The current default onError lambda simply rethrows the exception, which will crash the coroutine if the caller doesn’t explicitly override it. We recommend changing the default to integrate with the global error logger (or at least log and swallow) so unhandled errors don’t bring down the host scope.
Locations to update:
- shared/arkitekt-cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/scope/FlowUseCaseConfig.kt
Lines 82–90 (thebuild()method)
Suggested diff snippet:
fun build(): FlowUseCaseConfig<T, M> = FlowUseCaseConfig(
- onStart ?: { },
- onNext ?: { },
- onError ?: { throw it },
+ onStart ?: { },
+ onNext ?: { },
+ onError ?: { error ->
+ UseCaseErrorHandler.globalOnErrorLogger(error)
+ // swallow or rethrow depending on host requirements:
+ // throw error
+ },
onComplete ?: { },
disposePrevious,
onMap,
)– Replace throw it with a call to UseCaseErrorHandler.globalOnErrorLogger(error) (and only rethrow if desired) so flows without a custom onError don’t unconditionally crash.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| fun build(): FlowUseCaseConfig<T, M> = FlowUseCaseConfig( | |
| onStart ?: { }, | |
| onNext ?: { }, | |
| onError ?: { throw it }, | |
| onComplete ?: { }, | |
| disposePrevious, | |
| onMap, | |
| ) | |
| } | |
| fun build(): FlowUseCaseConfig<T, M> = FlowUseCaseConfig( | |
| onStart ?: { }, | |
| onNext ?: { }, | |
| onError ?: { error -> | |
| UseCaseErrorHandler.globalOnErrorLogger(error) | |
| // swallow or rethrow depending on host requirements: | |
| // throw error | |
| }, | |
| onComplete ?: { }, | |
| disposePrevious, | |
| onMap, | |
| ) | |
| } |
🤖 Prompt for AI Agents
In
shared/arkitekt-cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/scope/FlowUseCaseConfig.kt
around lines 82–90, the default onError currently rethrows the exception which
will crash the coroutine; change the default to call the global error logger
instead of throwing (e.g. invoke UseCaseErrorHandler.globalOnErrorLogger(error)
and do not rethrow) so unhandled errors are logged and swallowed by default, and
ensure the necessary import or reference to UseCaseErrorHandler is added; keep
the existing API so callers can still provide a custom onError that rethrows if
desired.
| override fun build(args: Unit): Flow<Instant> = flow { | ||
| for (i in 0..1000) { | ||
| emit(Clock.System.now()) | ||
| delay(1000) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider the finite emission count and resource implications.
The implementation emits timestamps for exactly 1001 iterations (0..1000) with 1-second delays, creating a flow that runs for approximately 16.7 minutes before completing. Consider whether this finite duration aligns with the intended use case behavior.
For a timestamp service that should run indefinitely, consider using an infinite loop:
- override fun build(args: Unit): Flow<Instant> = flow {
- for (i in 0..1000) {
- emit(Clock.System.now())
- delay(1000)
- }
- }
+ override fun build(args: Unit): Flow<Instant> = flow {
+ while (true) {
+ emit(Clock.System.now())
+ delay(1000)
+ }
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| override fun build(args: Unit): Flow<Instant> = flow { | |
| for (i in 0..1000) { | |
| emit(Clock.System.now()) | |
| delay(1000) | |
| } | |
| } | |
| override fun build(args: Unit): Flow<Instant> = flow { | |
| while (true) { | |
| emit(Clock.System.now()) | |
| delay(1000) | |
| } | |
| } |
🤖 Prompt for AI Agents
In
shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/domain/TimeStampUseCase.kt
around lines 13–18, the flow currently emits a finite 0..1000 sequence (~16.7
minutes) which likely isn't intended for a continuous timestamp service; replace
the fixed for-loop with an infinite, cancellable loop (e.g., while(true) or
while(coroutineContext.isActive)) that emits Clock.System.now() then delays
1_000ms, and make the loop cooperative with coroutine cancellation by checking
isActive or calling ensureActive/yield so the flow can be cancelled cleanly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Valid point
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@matejsemancik Thank you for confirming! The infinite loop approach will ensure the timestamp flow continues until explicitly cancelled, which better suits a continuous monitoring use case. 👍
|
This one would be very easy approve from me, but we need to figure how to re-enable Gradle cache |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@coderabbitai can you generate docstrings for this file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@matejsemancik, here are the docstrings for the LifecycleExecution.kt file:
package app.futured.kmptemplate.feature.tools
import app.futured.arkitekt.crusecases.FlowUseCase
import app.futured.arkitekt.crusecases.error.UseCaseErrorHandler
import app.futured.arkitekt.crusecases.scope.CoroutineScopeOwner
import app.futured.arkitekt.crusecases.scope.FlowUseCaseConfig
import com.arkivanov.essenty.lifecycle.Lifecycle
import com.arkivanov.essenty.lifecycle.LifecycleOwner
import com.arkivanov.essenty.lifecycle.subscribe
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.*
import kotlin.coroutines.cancellation.CancellationException
/**
* Executes a [FlowUseCase] with lifecycle awareness, gating flow emissions based on the component's lifecycle state.
*
* The flow will only emit values when the lifecycle has reached or exceeded the specified [minActiveState].
* When the lifecycle falls below this state, the flow emissions are paused. This ensures that emissions
* only occur when the component is in an appropriate state to handle them.
*
* ## Context Receivers
* - [CoroutineScopeOwner]: Provides the coroutine scope and dispatcher for executing the flow
* - [LifecycleOwner]: Provides the lifecycle to observe for state changes
*
* ## Lifecycle Behavior
* The flow will start emitting when the lifecycle reaches [minActiveState] and pause when it drops below:
* - [Lifecycle.State.CREATED]: Active between onCreate and onDestroy
* - [Lifecycle.State.STARTED]: Active between onStart and onStop
* - [Lifecycle.State.RESUMED]: Active between onResume and onPause
*
* ## Error Handling
* Errors are logged via [UseCaseErrorHandler.globalOnErrorLogger] and forwarded to the [FlowUseCaseConfig.onError] callback.
* Cancellation exceptions are ignored.
*
* @param ARGS The type of arguments required by the use case
* @param T The type of values emitted by the flow
* @param args Arguments to pass to the use case's build function
* @param minActiveState The minimum lifecycle state required for the flow to emit values. Defaults to [Lifecycle.State.RESUMED]
* @param config Configuration builder for lifecycle callbacks (onStart, onNext, onError, onComplete) and disposal behavior
*
* @see FlowUseCaseConfig
* @see CoroutineScopeOwner
* @see LifecycleOwner
*/
@OptIn(ExperimentalCoroutinesApi::class)
context(coroutineScopeOwner: CoroutineScopeOwner, componentContext: LifecycleOwner)
fun <ARGS, T : Any?> FlowUseCase<ARGS, T>.executeWithLifecycle(
args: ARGS,
minActiveState: Lifecycle.State = Lifecycle.State.RESUMED,
config: FlowUseCaseConfig.Builder<T, T>.() -> Unit,
) = executeWithLifecycleInternal(args = args, minActiveState = minActiveState, config = config)
/**
* Executes a [FlowUseCase] that requires no arguments with lifecycle awareness.
*
* This is a convenience overload for use cases where [ARGS] is [Unit]. The flow will only emit values
* when the lifecycle has reached or exceeded the specified [minActiveState].
*
* ## Context Receivers
* - [CoroutineScopeOwner]: Provides the coroutine scope and dispatcher for executing the flow
* - [LifecycleOwner]: Provides the lifecycle to observe for state changes
*
* ## Lifecycle Behavior
* The flow will start emitting when the lifecycle reaches [minActiveState] and pause when it drops below:
* - [Lifecycle.State.CREATED]: Active between onCreate and onDestroy
* - [Lifecycle.State.STARTED]: Active between onStart and onStop
* - [Lifecycle.State.RESUMED]: Active between onResume and onPause
*
* ## Error Handling
* Errors are logged via [UseCaseErrorHandler.globalOnErrorLogger] and forwarded to the [FlowUseCaseConfig.onError] callback.
* Cancellation exceptions are ignored.
*
* @param T The type of values emitted by the flow
* @param minActiveState The minimum lifecycle state required for the flow to emit values. Defaults to [Lifecycle.State.RESUMED]
* @param config Configuration builder for lifecycle callbacks (onStart, onNext, onError, onComplete) and disposal behavior
*
* @see FlowUseCaseConfig
* @see CoroutineScopeOwner
* @see LifecycleOwner
*/
@OptIn(ExperimentalCoroutinesApi::class)
context(coroutineScopeOwner: CoroutineScopeOwner, componentContext: LifecycleOwner)
fun <T : Any?> FlowUseCase<Unit, T>.executeWithLifecycle(
minActiveState: Lifecycle.State = Lifecycle.State.RESUMED,
config: FlowUseCaseConfig.Builder<T, T>.() -> Unit,
) = executeWithLifecycleInternal(args = Unit, minActiveState = minActiveState, config = config)
/**
* Internal implementation of lifecycle-aware flow execution.
*
* Creates a state flow that tracks whether execution should be enabled based on the current lifecycle state.
* Uses [flatMapLatest] to switch between the target flow and an empty flow based on the lifecycle state.
* Subscribes to lifecycle callbacks to update the execution state when the lifecycle transitions in and out
* of the specified [minActiveState].
*
* The flow is executed on the worker dispatcher provided by [CoroutineScopeOwner] and launched in the
* [CoroutineScopeOwner.viewModelScope].
*
* @param ARGS The type of arguments required by the use case
* @param T The type of values emitted by the flow
* @param args Arguments to pass to the use case's build function
* @param minActiveState The minimum lifecycle state required for the flow to emit values
* @param config Configuration builder for lifecycle callbacks and disposal behavior
*/
@OptIn(ExperimentalCoroutinesApi::class)
context(coroutineScopeOwner: CoroutineScopeOwner, componentContext: LifecycleOwner)
private fun <ARGS, T : Any?> FlowUseCase<ARGS, T>.executeWithLifecycleInternal(
args: ARGS,
minActiveState: Lifecycle.State = Lifecycle.State.RESUMED,
config: FlowUseCaseConfig.Builder<T, T>.() -> Unit,
) {
val flowUseCaseConfig = FlowUseCaseConfig.Builder<T, T>().run {
config.invoke(this)
return@run build()
}
if (flowUseCaseConfig.disposePrevious) {
job?.cancel()
}
val enableExecution = MutableStateFlow(false)
componentContext.lifecycle.subscribe(
onCreate = {
if (minActiveState == Lifecycle.State.CREATED) {
enableExecution.value = true
}
},
onDestroy = {
if (minActiveState == Lifecycle.State.CREATED) {
enableExecution.value = false
}
},
onStart = {
if (minActiveState == Lifecycle.State.STARTED) {
enableExecution.value = true
}
},
onStop = {
if (minActiveState == Lifecycle.State.STARTED) {
enableExecution.value = false
}
},
onResume = {
if (minActiveState == Lifecycle.State.RESUMED) {
enableExecution.value = true
}
},
onPause = {
if (minActiveState == Lifecycle.State.RESUMED) {
enableExecution.value = false
}
},
)
val targetFlow = build(args)
job = enableExecution
.flatMapLatest {
if (it) targetFlow else emptyFlow()
}
.flowOn(coroutineScopeOwner.getWorkerDispatcher())
.onStart { flowUseCaseConfig.onStart() }
.onEach { flowUseCaseConfig.onNext(it) }
.onCompletion { error ->
when {
error is CancellationException -> {
// ignore this exception
}
error != null -> {
UseCaseErrorHandler.globalOnErrorLogger(error)
flowUseCaseConfig.onError(error)
}
else -> flowUseCaseConfig.onComplete()
}
}
.catch { /* handled in onCompletion */ }
.launchIn(coroutineScopeOwner.viewModelScope)
}The docstrings include:
- Comprehensive descriptions of each function's purpose and behavior
- Documentation of context receivers and their roles
- Detailed lifecycle behavior explanation for each state
- Error handling documentation
- Parameter descriptions
- Cross-references to related types
- Clear examples of when emissions are active for each lifecycle state
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like to see this unit tested + perhaps moved to arkitekt-decompose subproject
Refactor execution methods to be defined not as extensions but with context parameters for more flexibility.
Now we can customize behavior of execution methods like
executeWithLifecycleSummary by CodeRabbit
New Features
Chores