Skip to content

Conversation

@Syntey
Copy link
Contributor

@Syntey Syntey commented Jun 17, 2025

Summary by CodeRabbit

  • New Features

    • Added logic to track and persist user login status, enabling conditional navigation based on authentication state.
    • Introduced use cases for checking and updating user login status.
    • Implemented platform-specific modules for managing persistent storage on Android and iOS.
  • Improvements

    • Enhanced dependency injection setup using annotation-based modules for better maintainability and clarity.
    • Migrated persistence qualifiers to annotation-based approach for improved type safety.
  • Dependency Updates

    • Updated versions of Android Gradle Plugin, Kotlin, Koin, and related libraries for improved compatibility and features.
  • Refactor

    • Refactored persistence and dependency injection structure to streamline platform-specific implementations and module definitions.

@coderabbitai
Copy link

coderabbitai bot commented Jun 17, 2025

Walkthrough

This change set introduces a comprehensive refactor and enhancement of the persistence and authentication flow in a Kotlin Multiplatform project. The persistence module's dependency injection setup is migrated from manual Koin module definitions to annotation-driven modules using Koin's annotation processing. Platform-specific DataStore provisioning is now handled by dedicated DataStoreModule classes for Android and iOS. The persistence layer introduces a new UserPersistence interface and implementation for managing user login state, which is then used by new domain-layer use cases (IsUserLoggedInUseCase, SetUserLoggedInUseCase). These use cases are injected into UI components, enabling asynchronous, state-driven navigation and authentication flows. Several dependency versions are updated, and Koin annotation processing is enabled and expanded across platforms.

Sequence Diagram(s)

User Login State Flow

sequenceDiagram
    participant UI_LoginComponent
    participant SetUserLoggedInUseCase
    participant UserPersistence
    participant PrimitivePersistence

    UI_LoginComponent->>SetUserLoggedInUseCase: execute(Args(isLoggedIn=true))
    SetUserLoggedInUseCase->>UserPersistence: setUserLoggedIn(true)
    UserPersistence->>PrimitivePersistence: putBoolean("IS_USER_LOGGED_IN", true)
    PrimitivePersistence-->>UserPersistence: (persisted)
    UserPersistence-->>SetUserLoggedInUseCase: (complete)
    SetUserLoggedInUseCase-->>UI_LoginComponent: (success)
    UI_LoginComponent->>UI_LoginComponent: navigateToSignedIn()
Loading

User Logout State Flow

sequenceDiagram
    participant UI_ProfileComponent
    participant SetUserLoggedInUseCase
    participant UserPersistence
    participant PrimitivePersistence

    UI_ProfileComponent->>SetUserLoggedInUseCase: execute(Args(isLoggedIn=false))
    SetUserLoggedInUseCase->>UserPersistence: setUserLoggedIn(false)
    UserPersistence->>PrimitivePersistence: putBoolean("IS_USER_LOGGED_IN", false)
    PrimitivePersistence-->>UserPersistence: (persisted)
    UserPersistence-->>SetUserLoggedInUseCase: (complete)
    SetUserLoggedInUseCase-->>UI_ProfileComponent: (success)
    UI_ProfileComponent->>UI_ProfileComponent: navigateToLogin()
Loading

App Startup: Root Navigation Decision

sequenceDiagram
    participant RootNavHostComponent
    participant IsUserLoggedInUseCase
    participant UserPersistence
    participant PrimitivePersistence

    RootNavHostComponent->>IsUserLoggedInUseCase: execute(Unit)
    IsUserLoggedInUseCase->>UserPersistence: isUserLoggedIn()
    UserPersistence->>PrimitivePersistence: getBoolean("IS_USER_LOGGED_IN")
    PrimitivePersistence-->>UserPersistence: Boolean value
    UserPersistence-->>IsUserLoggedInUseCase: Boolean value
    IsUserLoggedInUseCase-->>RootNavHostComponent: Boolean value
    alt User is logged in
        RootNavHostComponent->>RootNavHostComponent: activate(SignedIn)
    else User is not logged in
        RootNavHostComponent->>RootNavHostComponent: activate(Login)
    end
Loading

Persistence Module Initialization (Platform-specific)

sequenceDiagram
    participant Koin
    participant PersistenceModule
    participant DataStoreModule (Android/iOS)
    participant DataStore

    Koin->>PersistenceModule: Instantiate
    PersistenceModule->>DataStoreModule: (platform-specific instantiation)
    DataStoreModule->>DataStore: provideDataStore()
    DataStore-->>DataStoreModule: DataStore<Preferences>
    DataStoreModule-->>PersistenceModule: DataStore<Preferences>
    PersistenceModule-->>Koin: register components
Loading

📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 108b0a1 and 3223eea.

📒 Files selected for processing (1)
  • convention-plugins/src/main/kotlin/conventions-annotations-processor.gradle.kts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • convention-plugins/src/main/kotlin/conventions-annotations-processor.gradle.kts
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: check-android / Lint & Tests
  • GitHub Check: check-ios / Test
✨ Finishing Touches
  • 📝 Generate Docstrings

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (6)
convention-plugins/src/main/kotlin/conventions-annotations-processor.gradle.kts (1)

35-38: Consider adding the JVM target or parametrising the list

Only Android & iOS source sets are wired for KSP.
If the project ever adds a pure JVM target (desktop, server), the processor will be missing.
A small helper avoids future drift:

listOf(
    "kspCommonMainMetadata",
    "kspAndroid",
    "kspIosX64",
    "kspIosArm64",
    "kspIosSimulatorArm64",
    "kspJvm"               // ← future-proof
).forEach { conf -> add(conf, libs.koin.ksp.compiler) }
shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/persistence/PrimitivePersistence.kt (1)

15-17: @Provided is unnecessary noise here

Koin automatically wires constructor parameters that have a definition in the graph, so annotating dataStore with @Provided does not add value. You can drop the annotation and keep the signature simpler:

-@Single
-internal class PrimitivePersistence(@Provided private val dataStore: DataStore<Preferences>) {
+@Single
+internal class PrimitivePersistence(private val dataStore: DataStore<Preferences>) {

Fewer annotations → quicker reads, less KSP work.

shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/ui/loginScreen/LoginComponent.kt (1)

16-16: Injecting UseCase directly couples UI with persistence concerns

The component now depends on SetUserLoggedInUseCase, which is fine, but it increases the surface of responsibilities inside the UI layer. Consider introducing an intermediate “LoginInteractor” (or similar) that owns the business rule and keeps the screen slimmer.

shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/domain/SetUserLoggedInUseCase.kt (1)

7-13: Nit: add KDoc & consider typealias Args = Boolean

For such a small payload a full data class is verbose. Either document the intent with KDoc or collapse it:

typealias Args = Boolean

Not critical but keeps the API concise.

shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/navigation/root/RootNavHostComponent.kt (1)

23-26: Constructor now quite wide – consider grouping dependencies

RootNavHostComponent already injects navigation factories, deep-link resolver and now the login UseCase. Grouping navigation concerns (e.g. a NavigationDependencies data holder) keeps the signature readable and eases future additions.

shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.kt (1)

14-21: Minor: reuse a global Json instance instead of configuring on every KSP run

Although the provider is marked @Single, KSP will still generate a builder every compilation.
Declaring a val JsonConfig = Json { … } in an internal object avoids rebuilding the same lambda in generated code and keeps the DI graph cleaner.

internal object JsonProvider {
    val instance: Json = Json {
        encodeDefaults = true
        isLenient = false
        ignoreUnknownKeys = true
        prettyPrint = false
    }
}

@Single
@PersistenceJson
internal fun provideJson(): Json = JsonProvider.instance

Purely a micro-optimisation – feel free to skip if build size is not a concern.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 799040c and a9b96d8.

📒 Files selected for processing (20)
  • convention-plugins/src/main/kotlin/conventions-annotations-processor.gradle.kts (2 hunks)
  • gradle/libs.versions.toml (2 hunks)
  • shared/app/src/commonMain/kotlin/app/futured/kmptemplate/app/injection/AppInjection.kt (2 hunks)
  • shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/domain/IsUserLoggedInUseCase.kt (1 hunks)
  • shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/domain/SetUserLoggedInUseCase.kt (1 hunks)
  • shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/navigation/root/RootNavHostComponent.kt (4 hunks)
  • shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/ui/loginScreen/LoginComponent.kt (3 hunks)
  • shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/ui/profileScreen/ProfileComponent.kt (3 hunks)
  • shared/network/rest/src/commonMain/kotlin/app/futured/kmptemplate/network/rest/plugin/UserAgentPlugin.kt (1 hunks)
  • shared/persistence/build.gradle.kts (2 hunks)
  • shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/injection/DataStoreModule.kt (1 hunks)
  • shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistencePlatformModule.kt (0 hunks)
  • shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.kt (1 hunks)
  • shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistencePlatformModule.kt (0 hunks)
  • shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/injection/Qualifiers.kt (1 hunks)
  • shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/persistence/JsonPersistence.kt (1 hunks)
  • shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/persistence/PrimitivePersistence.kt (1 hunks)
  • shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/persistence/user/UserPersistence.kt (1 hunks)
  • shared/persistence/src/iosMain/kotlin/app/futured/kmptemplate/persistence/injection/DataStoreModule.kt (1 hunks)
  • shared/persistence/src/iosMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistencePlatformModule.kt (0 hunks)
💤 Files with no reviewable changes (3)
  • shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistencePlatformModule.kt
  • shared/persistence/src/iosMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistencePlatformModule.kt
  • shared/persistence/src/androidMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistencePlatformModule.kt
🧰 Additional context used
🧬 Code Graph Analysis (1)
shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/ui/profileScreen/ProfileComponent.kt (5)
shared/arkitekt-cr-usecases/src/commonMain/kotlin/app/futured/arkitekt/crusecases/scope/SingleUseCaseExecutionScope.kt (1)
  • onSuccess (138-140)
shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/navigation/profile/ProfileNavHostNavigation.kt (1)
  • navigateToLogin (21-21)
shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/navigation/root/RootNavHostNavigation.kt (1)
  • navigateToLogin (23-25)
shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/navigation/signedIn/SignedInNavHostNavigation.kt (2)
  • navigateToLogin (5-7)
  • navigateToLogin (6-6)
shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/ui/profileScreen/ProfileScreenNavigation.kt (2)
  • navigateToLogin (5-8)
  • navigateToLogin (6-6)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: check-android / Lint & Tests
  • GitHub Check: check-ios / Test
🔇 Additional comments (8)
gradle/libs.versions.toml (1)

2-4: Double-check cross-version compatibility

Upgrading Kotlin (2.1.20), KSP (2.1.20-1.0.32) and Koin (4.1.0 / annotations 2.1.0) in one sweep is fine in principle, but these versions were released within days of each other. Ensure:

  1. koin-annotations:2.1.0 is actually built against Koin 4.1.x (it was for 4.0.x in previous releases).
  2. The Compose compiler plugin you use supports Kotlin 2.1.20.
  3. No KSP processor (Apollo, Ktorfit, etc.) hard-pins to older Kotlin.

A dry run of ./gradlew :shared:app:assembleDebug (or CI pipeline) should reveal any ABI mismatch immediately.

Also applies to: 12-14, 27-28

convention-plugins/src/main/kotlin/conventions-annotations-processor.gradle.kts (1)

22-26: Nice touch – config check enabled

Turning on KOIN_CONFIG_CHECK will surface invalid graphs at compile-time rather than runtime. Good call.

shared/network/rest/src/commonMain/kotlin/app/futured/kmptemplate/network/rest/plugin/UserAgentPlugin.kt (1)

7-11: LGTM – correct use of @Provided

Annotating the platform constructor parameter with @Provided makes the intent explicit and relies on Koin’s generated provider. No issues spotted.

shared/app/src/commonMain/kotlin/app/futured/kmptemplate/app/injection/AppInjection.kt (1)

6-7: Injection wiring updated correctly

Swapping the function-based persistenceModule() for PersistenceModule().module aligns with the new annotation-generated modules. Looks good.

Also applies to: 33-34

shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/domain/IsUserLoggedInUseCase.kt (1)

7-10: Looks good – straightforward use-case wrapper

The implementation is minimal, type-safe, and follows your existing UseCase pattern.

shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/persistence/JsonPersistence.kt (1)

21-23: DI annotations look correct – good move to @Single

@Provided for DataStore and qualifier for Json aligns with the new annotation-driven DI. Nice.

shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/persistence/user/UserPersistence.kt (1)

13-24: Potential IO off-main-thread guarantee

PrimitivePersistence.save is presumably a suspend function hitting DataStore.edit, which is already dispatcher-aware, but double-check that PrimitivePersistence enforces Dispatchers.IO; otherwise add withContext(Dispatchers.IO) here to avoid accidental main-thread writes.

shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/injection/PersistenceModule.kt (1)

10-12: Verify Koin-KSP can resolve expect class in includes

@Module(includes = [DataStoreModule::class]) refers to an expect declaration that is materialised only in platform source sets.
Historically, Koin-KSP struggles to resolve such symbols from common code and can silently skip the include, yielding “no definition found” at runtime.

Please run the KSP task on both Android & iOS targets and ensure the generated files contain the DataStoreModule binding; if not, move the includes to the platform-specific Modules instead.

Comment on lines +3 to +6
import org.koin.core.annotation.Named

internal object Qualifiers {

val PersistenceJson = named("PersistenceJson")
val DataStorePath = named("DataStoreFilePath")
}
@Named
annotation class PersistenceJson
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Wrong meta-annotation – this won’t compile

@Named requires a non-optional value argument (Named(val value: String)), so using it as a meta-annotation without a value produces a compilation error.
If you intend to create a custom qualifier, annotate it with @Qualifier instead (Koin will still recognise it), or pass an explicit value to @Named.

-import org.koin.core.annotation.Named
+import org.koin.core.annotation.Qualifier
@@
-@Named
-annotation class PersistenceJson
+@Qualifier
+annotation class PersistenceJson

(Optionally add @Retention(BINARY) / @Target(*) if you need finer control.)

📝 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.

Suggested change
import org.koin.core.annotation.Named
internal object Qualifiers {
val PersistenceJson = named("PersistenceJson")
val DataStorePath = named("DataStoreFilePath")
}
@Named
annotation class PersistenceJson
import org.koin.core.annotation.Qualifier
@Qualifier
annotation class PersistenceJson
🤖 Prompt for AI Agents
In
shared/persistence/src/commonMain/kotlin/app/futured/kmptemplate/persistence/injection/Qualifiers.kt
lines 3 to 6, the @Named annotation is used without the required value argument,
causing a compilation error. Replace @Named with @Qualifier to define a custom
qualifier annotation correctly, and optionally add @Retention and @Target
annotations for finer control over its usage.

Comment on lines 33 to 35
implementation(libs.koin.core)
api(libs.koin.annotations)
implementation(libs.kotlinx.coroutines.core)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid leaking koin.annotations to downstream modules

koin.annotations is only required at compile-time for KSP and is not needed on the runtime class-path of consumers. Declaring it as api will expose the dependency transitively and enlarge every consumer’s class-path.

-                api(libs.koin.annotations)
+                compileOnly(libs.koin.annotations) // or `implementation` if runtime is really required

Reducing the surface keeps ABI stable and speeds up compilation of dependent modules.

📝 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.

Suggested change
implementation(libs.koin.core)
api(libs.koin.annotations)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.koin.core)
compileOnly(libs.koin.annotations) // or `implementation` if runtime is really required
implementation(libs.kotlinx.coroutines.core)
🤖 Prompt for AI Agents
In shared/persistence/build.gradle.kts around lines 33 to 35, the dependency on
koin.annotations is declared as api, which exposes it transitively to downstream
modules unnecessarily. Change the declaration from api to compileOnly to ensure
koin.annotations is only used at compile-time for KSP and not included in the
runtime class-path of consumers, reducing dependency leakage and improving build
performance.

Comment on lines +27 to +33
override fun onLogout() {
setUserLoggedInUseCase.execute(SetUserLoggedInUseCase.Args(false)) {
onSuccess {
navigateToLogin()
}
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Handle failure path of SetUserLoggedInUseCase

Currently only the success branch is handled; if the persistence write fails the user stays on the profile screen with no feedback. At minimum capture onError and surface it (toast / snackbar) or still navigate to login if failure is non-critical.

-        setUserLoggedInUseCase.execute(SetUserLoggedInUseCase.Args(false)) {
-            onSuccess {
-                navigateToLogin()
-            }
-        }
+        setUserLoggedInUseCase.execute(SetUserLoggedInUseCase.Args(false)) {
+            onSuccess { navigateToLogin() }
+            onError { /* TODO: show error or decide to navigate anyway */ }
+        }

Leaving errors unhandled can hide persistence issues and confuse users.

📝 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.

Suggested change
override fun onLogout() {
setUserLoggedInUseCase.execute(SetUserLoggedInUseCase.Args(false)) {
onSuccess {
navigateToLogin()
}
}
}
override fun onLogout() {
setUserLoggedInUseCase.execute(SetUserLoggedInUseCase.Args(false)) {
onSuccess { navigateToLogin() }
onError { /* TODO: show error or decide to navigate anyway */ }
}
}
🤖 Prompt for AI Agents
In
shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/ui/profileScreen/ProfileComponent.kt
around lines 27 to 33, the onLogout function only handles the success case of
setUserLoggedInUseCase.execute and ignores failures. Add an onError handler to
capture errors from the use case execution. In the onError block, either show a
user-visible message like a toast or snackbar to indicate the failure or decide
to navigate to the login screen anyway if the failure is not critical, ensuring
the user is not left without feedback.

Comment on lines +28 to +34
override fun onLoginClick() {
setUserLoggedInUseCase.execute(SetUserLoggedInUseCase.Args(true)) {
onSuccess {
navigateToSignedIn()
}
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Missing loading / failure handling in login flow

execute { onSuccess { … } } ignores the onError and onStart / onComplete callbacks that UseCase exposes.
Users can tap the button repeatedly while the coroutine is running, or the call can fail silently and leave the UI stalled. At minimum, disable the button during the operation and surface an error if the persistence write fails.

 override fun onLoginClick() {
-    setUserLoggedInUseCase.execute(SetUserLoggedInUseCase.Args(true)) {
-        onSuccess {
-            navigateToSignedIn()
-        }
-    }
+    setUserLoggedInUseCase.execute(SetUserLoggedInUseCase.Args(true)) {
+        onStart   { setLoading(true)  }
+        onSuccess { navigateToSignedIn() }
+        onError   { showError(it); setLoading(false) }
+        onComplete{ setLoading(false) }
+    }
 }
📝 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.

Suggested change
override fun onLoginClick() {
setUserLoggedInUseCase.execute(SetUserLoggedInUseCase.Args(true)) {
onSuccess {
navigateToSignedIn()
}
}
}
override fun onLoginClick() {
- setUserLoggedInUseCase.execute(SetUserLoggedInUseCase.Args(true)) {
- onSuccess {
- navigateToSignedIn()
- }
- }
+ setUserLoggedInUseCase.execute(SetUserLoggedInUseCase.Args(true)) {
+ onStart { setLoading(true) }
+ onSuccess { navigateToSignedIn() }
+ onError { showError(it); setLoading(false) }
+ onComplete { setLoading(false) }
+ }
}
🤖 Prompt for AI Agents
In
shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/ui/loginScreen/LoginComponent.kt
between lines 28 and 34, the onLoginClick function currently only handles the
onSuccess callback of the use case execution, ignoring onError and
onStart/onComplete. To fix this, update the execute call to handle onStart by
disabling the login button, onComplete by re-enabling it, and onError by showing
an error message to the user. This will prevent multiple taps during the
operation and provide feedback if the login persistence fails.

Comment on lines +92 to +102
private fun checkUserLoggedIn() {
isUserLoggedInUseCase.execute {
onSuccess { isLoggedIn ->
if (isLoggedIn) {
rootNavigator.slotNavigator.activate(RootConfig.SignedIn())
} else {
rootNavigator.slotNavigator.activate(RootConfig.Login)
}
}
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Race-condition: deep link received after async login check

checkUserLoggedIn() is async; if a deep link arrives while the coroutine is running, the slot may first activate Login/SignedIn and then immediately re-activate the deep-link config, causing a visible “flash”.

Consider cancelling the job when a deep link is consumed or delaying slot activation until the first router decision is final.

🤖 Prompt for AI Agents
In
shared/feature/src/commonMain/kotlin/app/futured/kmptemplate/feature/navigation/root/RootNavHostComponent.kt
around lines 92 to 102, the async checkUserLoggedIn function can cause a race
condition with deep link handling, leading to UI flashing due to multiple slot
activations. To fix this, implement cancellation of the ongoing login check
coroutine when a deep link is received or delay activating the slot navigator
until the login check completes and the first routing decision is finalized,
ensuring only one activation occurs.

Comment on lines +21 to +33
actual fun provideDataStore(): DataStore<Preferences> = PreferenceDataStoreFactory.createWithPath(
produceFile = {
val documentDirectory: NSURL? = NSFileManager.defaultManager.URLForDirectory(
directory = NSDocumentDirectory,
inDomain = NSUserDomainMask,
appropriateForURL = null,
create = false,
error = null,
)

(requireNotNull(documentDirectory).path + "/$SETTINGS_DATASTORE_FILENAME").toPath()
},
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Handle potential null path & avoid runtime crash

NSURL.path is an optional in Apple SDKs ➜ requireNotNull(documentDirectory).path may still yield null.
Dereferencing it before the toPath() call would crash on devices where the document directory cannot be resolved (sandbox issues, corrupted container, unit-test runners, etc.).

-            (requireNotNull(documentDirectory).path + "/$SETTINGS_DATASTORE_FILENAME").toPath()
+            (
+                requireNotNull(documentDirectory?.path) {
+                    "Unable to resolve iOS documents directory for DataStore"
+                } + "/$SETTINGS_DATASTORE_FILENAME"
+            ).toPath()

Guarding both documentDirectory and its path keeps the app from an obscure native crash and provides a clear diagnostic message instead.

📝 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.

Suggested change
actual fun provideDataStore(): DataStore<Preferences> = PreferenceDataStoreFactory.createWithPath(
produceFile = {
val documentDirectory: NSURL? = NSFileManager.defaultManager.URLForDirectory(
directory = NSDocumentDirectory,
inDomain = NSUserDomainMask,
appropriateForURL = null,
create = false,
error = null,
)
(requireNotNull(documentDirectory).path + "/$SETTINGS_DATASTORE_FILENAME").toPath()
},
)
actual fun provideDataStore(): DataStore<Preferences> = PreferenceDataStoreFactory.createWithPath(
produceFile = {
val documentDirectory: NSURL? = NSFileManager.defaultManager.URLForDirectory(
directory = NSDocumentDirectory,
inDomain = NSUserDomainMask,
appropriateForURL = null,
create = false,
error = null,
)
(
requireNotNull(documentDirectory?.path) {
"Unable to resolve iOS documents directory for DataStore"
} + "/$SETTINGS_DATASTORE_FILENAME"
).toPath()
},
)
🤖 Prompt for AI Agents
In
shared/persistence/src/iosMain/kotlin/app/futured/kmptemplate/persistence/injection/DataStoreModule.kt
around lines 21 to 33, the code uses requireNotNull on documentDirectory but
does not check if its path property is null, which can cause a runtime crash.
Update the code to safely unwrap both documentDirectory and its path, and if
either is null, throw an exception or provide a clear error message before
calling toPath(), preventing obscure native crashes.

Comment on lines +19 to +33
@OptIn(ExperimentalForeignApi::class)
@Single
actual fun provideDataStore(): DataStore<Preferences> = PreferenceDataStoreFactory.createWithPath(
produceFile = {
val documentDirectory: NSURL? = NSFileManager.defaultManager.URLForDirectory(
directory = NSDocumentDirectory,
inDomain = NSUserDomainMask,
appropriateForURL = null,
create = false,
error = null,
)

(requireNotNull(documentDirectory).path + "/$SETTINGS_DATASTORE_FILENAME").toPath()
},
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Provide explicit CoroutineScope to PreferenceDataStoreFactory.createWithPath

createWithPath defaults to coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO).
On iOS this scope lives forever, leaking after Koin.stopKoin() or during app quick-relaunches (UITests, widget processes, etc.).

Consider passing an appScope you already manage (e.g. ApplicationScope) so you can cancel it at shutdown:

-actual fun provideDataStore(): DataStore<Preferences> = PreferenceDataStoreFactory.createWithPath(
+actual fun provideDataStore(): DataStore<Preferences> = PreferenceDataStoreFactory.createWithPath(
+    coroutineScope = appScope,
     produceFile = {
         …
     },
 )

If you don’t have such a scope yet, injecting one via Koin (single) keeps lifetime management transparent.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
shared/persistence/src/iosMain/kotlin/app/futured/kmptemplate/persistence/injection/DataStoreModule.kt
around lines 19 to 33, the call to PreferenceDataStoreFactory.createWithPath
does not specify a CoroutineScope, causing a scope with a SupervisorJob and
Dispatchers.IO to live indefinitely on iOS, leading to resource leaks. To fix
this, define or inject an application-wide CoroutineScope (e.g.,
ApplicationScope) and pass it explicitly as the coroutineScope parameter to
createWithPath. This allows proper cancellation of the scope during app shutdown
or Koin stop, preventing leaks.

@github-actions
Copy link

2 Warnings
⚠️ Feature or fix PR title should include JIRA-ID and short description.
⚠️ Feature or fix PR branch name should include JIRA-ID and short description.

Generated by 🚫 Danger

@Syntey Syntey enabled auto-merge July 2, 2025 09:16
@github-actions
Copy link

github-actions bot commented Jul 2, 2025

2 Warnings
⚠️ Feature or fix PR title should include JIRA-ID and short description.
⚠️ Feature or fix PR branch name should include JIRA-ID and short description.
2 Messages
📖 iosAppTests: Executed 1 test, with 0 failures (0 expected) in 0.001 (0.001) seconds
📖 iosAppUITests: Executed 1 test, with 0 failures (0 expected) in 14.182 (14.182) seconds

Generated by 🚫 Danger

@Syntey Syntey merged commit f7d6484 into develop Jul 2, 2025
6 checks passed
@Syntey Syntey deleted the feature/koin-annotations branch July 2, 2025 09:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants