-
Notifications
You must be signed in to change notification settings - Fork 3
Use koin annotations in persistence module #126
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
Changes from all commits
55b3b67
a1f314a
b01fc40
458d732
64f1aa8
4d77e10
ec89606
a9b96d8
70cea7b
108b0a1
3223eea
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package app.futured.kmptemplate.feature.domain | ||
|
|
||
| import app.futured.arkitekt.crusecases.UseCase | ||
| import app.futured.kmptemplate.persistence.persistence.user.UserPersistence | ||
| import org.koin.core.annotation.Factory | ||
|
|
||
| @Factory | ||
| class IsUserLoggedInUseCase(private val userPersistence: UserPersistence) : UseCase<Unit, Boolean>() { | ||
| override suspend fun build(args: Unit): Boolean = userPersistence.isUserLoggedIn() | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package app.futured.kmptemplate.feature.domain | ||
|
|
||
| import app.futured.arkitekt.crusecases.UseCase | ||
| import app.futured.kmptemplate.persistence.persistence.user.UserPersistence | ||
| import org.koin.core.annotation.Factory | ||
|
|
||
| @Factory | ||
| class SetUserLoggedInUseCase(private val userPersistence: UserPersistence) : UseCase<SetUserLoggedInUseCase.Args, Unit>() { | ||
|
|
||
| override suspend fun build(args: Args) = userPersistence.setUserLoggedIn(args.isLoggedIn) | ||
|
|
||
| data class Args(val isLoggedIn: Boolean) | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,6 +1,7 @@ | ||||||||||||||||||||||||||||||||||||||||||
| package app.futured.kmptemplate.feature.ui.loginScreen | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| import app.futured.factorygenerator.annotation.GenerateFactory | ||||||||||||||||||||||||||||||||||||||||||
| import app.futured.kmptemplate.feature.domain.SetUserLoggedInUseCase | ||||||||||||||||||||||||||||||||||||||||||
| import app.futured.kmptemplate.feature.ui.base.AppComponentContext | ||||||||||||||||||||||||||||||||||||||||||
| import app.futured.kmptemplate.feature.ui.base.ScreenComponent | ||||||||||||||||||||||||||||||||||||||||||
| import kotlinx.coroutines.flow.StateFlow | ||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -12,6 +13,7 @@ import org.koin.core.annotation.InjectedParam | |||||||||||||||||||||||||||||||||||||||||
| internal class LoginComponent( | ||||||||||||||||||||||||||||||||||||||||||
| @InjectedParam componentContext: AppComponentContext, | ||||||||||||||||||||||||||||||||||||||||||
| @InjectedParam override val navigation: LoginScreenNavigation, | ||||||||||||||||||||||||||||||||||||||||||
| private val setUserLoggedInUseCase: SetUserLoggedInUseCase, | ||||||||||||||||||||||||||||||||||||||||||
| ) : ScreenComponent<LoginViewState, Nothing, LoginScreenNavigation>( | ||||||||||||||||||||||||||||||||||||||||||
| componentContext = componentContext, | ||||||||||||||||||||||||||||||||||||||||||
| defaultState = LoginViewState, | ||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -23,5 +25,11 @@ internal class LoginComponent( | |||||||||||||||||||||||||||||||||||||||||
| override val actions: LoginScreen.Actions = this | ||||||||||||||||||||||||||||||||||||||||||
| override val viewState: StateFlow<LoginViewState> = componentState | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| override fun onLoginClick() = navigateToSignedIn() | ||||||||||||||||||||||||||||||||||||||||||
| override fun onLoginClick() { | ||||||||||||||||||||||||||||||||||||||||||
| setUserLoggedInUseCase.execute(SetUserLoggedInUseCase.Args(true)) { | ||||||||||||||||||||||||||||||||||||||||||
| onSuccess { | ||||||||||||||||||||||||||||||||||||||||||
| navigateToSignedIn() | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+28
to
+34
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Missing loading / failure handling in login flow
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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,6 +1,7 @@ | ||||||||||||||||||||||||||||
| package app.futured.kmptemplate.feature.ui.profileScreen | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| import app.futured.factorygenerator.annotation.GenerateFactory | ||||||||||||||||||||||||||||
| import app.futured.kmptemplate.feature.domain.SetUserLoggedInUseCase | ||||||||||||||||||||||||||||
| import app.futured.kmptemplate.feature.ui.base.AppComponentContext | ||||||||||||||||||||||||||||
| import app.futured.kmptemplate.feature.ui.base.ScreenComponent | ||||||||||||||||||||||||||||
| import kotlinx.coroutines.flow.StateFlow | ||||||||||||||||||||||||||||
|
|
@@ -12,6 +13,7 @@ import org.koin.core.annotation.InjectedParam | |||||||||||||||||||||||||||
| internal class ProfileComponent( | ||||||||||||||||||||||||||||
| @InjectedParam componentContext: AppComponentContext, | ||||||||||||||||||||||||||||
| @InjectedParam override val navigation: ProfileScreenNavigation, | ||||||||||||||||||||||||||||
| private val setUserLoggedInUseCase: SetUserLoggedInUseCase, | ||||||||||||||||||||||||||||
| ) : ScreenComponent<ProfileViewState, Nothing, ProfileScreenNavigation>( | ||||||||||||||||||||||||||||
| componentContext, | ||||||||||||||||||||||||||||
| ProfileViewState, | ||||||||||||||||||||||||||||
|
|
@@ -22,6 +24,12 @@ internal class ProfileComponent( | |||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| override val actions: ProfileScreen.Actions = this | ||||||||||||||||||||||||||||
| override val viewState: StateFlow<ProfileViewState> = componentState | ||||||||||||||||||||||||||||
| override fun onLogout() = navigateToLogin() | ||||||||||||||||||||||||||||
| override fun onLogout() { | ||||||||||||||||||||||||||||
| setUserLoggedInUseCase.execute(SetUserLoggedInUseCase.Args(false)) { | ||||||||||||||||||||||||||||
| onSuccess { | ||||||||||||||||||||||||||||
| navigateToLogin() | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
Comment on lines
+27
to
+33
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Handle failure path of 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 - 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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
| override fun onThird() = navigateToThird("hello third from profile") | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| package app.futured.kmptemplate.persistence.injection | ||
|
|
||
| import android.content.Context | ||
| import androidx.datastore.core.DataStore | ||
| import androidx.datastore.preferences.core.PreferenceDataStoreFactory | ||
| import androidx.datastore.preferences.core.Preferences | ||
| import app.futured.kmptemplate.persistence.tools.SETTINGS_DATASTORE_FILENAME | ||
| import okio.Path.Companion.toPath | ||
| import org.koin.core.annotation.Module | ||
| import org.koin.core.annotation.Single | ||
| import org.koin.core.component.KoinComponent | ||
| import org.koin.core.component.inject | ||
|
|
||
| @Module | ||
| actual class DataStoreModule actual constructor() : KoinComponent { | ||
|
|
||
| private val context: Context by inject() | ||
|
|
||
| @Single | ||
| actual fun provideDataStore(): DataStore<Preferences> = PreferenceDataStoreFactory.createWithPath( | ||
| produceFile = { context.filesDir.resolve(SETTINGS_DATASTORE_FILENAME).absolutePath.toPath() }, | ||
| ) | ||
matejsemancik marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,31 +1,29 @@ | ||
| package app.futured.kmptemplate.persistence.injection | ||
|
|
||
| import androidx.datastore.preferences.core.PreferenceDataStoreFactory | ||
| import app.futured.kmptemplate.persistence.persistence.JsonPersistence | ||
| import app.futured.kmptemplate.persistence.persistence.PrimitivePersistence | ||
| import androidx.datastore.core.DataStore | ||
| import androidx.datastore.preferences.core.Preferences | ||
| import kotlinx.serialization.json.Json | ||
| import org.koin.core.module.dsl.singleOf | ||
| import org.koin.dsl.module | ||
| import org.koin.core.annotation.ComponentScan | ||
| import org.koin.core.annotation.Module | ||
| import org.koin.core.annotation.Single | ||
|
|
||
| fun persistenceModule() = module { | ||
| // expect/actual Koin module | ||
| includes(persistencePlatformModule()) | ||
| @Module(includes = [DataStoreModule::class]) | ||
| @ComponentScan("app.futured.kmptemplate.persistence") | ||
| class PersistenceModule { | ||
|
|
||
| single { | ||
| PreferenceDataStoreFactory.createWithPath( | ||
| produceFile = { get(Qualifiers.DataStorePath) }, | ||
| ) | ||
| @Single | ||
| @PersistenceJson | ||
| internal fun provideJson(): Json = Json { | ||
| encodeDefaults = true | ||
| isLenient = false | ||
| ignoreUnknownKeys = true | ||
| prettyPrint = false | ||
| } | ||
| } | ||
|
|
||
| singleOf(::PrimitivePersistence) | ||
| single { JsonPersistence(get(), get(Qualifiers.PersistenceJson)) } | ||
| @Module | ||
| expect class DataStoreModule() { | ||
|
|
||
| single(Qualifiers.PersistenceJson) { | ||
| Json { | ||
| encodeDefaults = true | ||
| isLenient = false | ||
| ignoreUnknownKeys = true | ||
| prettyPrint = false | ||
| } | ||
| } | ||
| @Single | ||
| fun provideDataStore(): DataStore<Preferences> | ||
| } |
This file was deleted.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,9 +1,6 @@ | ||||||||||||||||||||||||||||
| package app.futured.kmptemplate.persistence.injection | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| import org.koin.core.qualifier.named | ||||||||||||||||||||||||||||
| import org.koin.core.annotation.Named | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| internal object Qualifiers { | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| val PersistenceJson = named("PersistenceJson") | ||||||||||||||||||||||||||||
| val DataStorePath = named("DataStoreFilePath") | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| @Named | ||||||||||||||||||||||||||||
| annotation class PersistenceJson | ||||||||||||||||||||||||||||
|
Comment on lines
+3
to
+6
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wrong meta-annotation – this won’t compile
-import org.koin.core.annotation.Named
+import org.koin.core.annotation.Qualifier
@@
-@Named
-annotation class PersistenceJson
+@Qualifier
+annotation class PersistenceJson(Optionally add 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
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
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