Skip to content

Commit 7520dd3

Browse files
feat: Migrated :core:datastore module to KMP (#2737)
1 parent d88e08f commit 7520dd3

File tree

15 files changed

+504
-243
lines changed

15 files changed

+504
-243
lines changed

androidApp/dependencies/demoDebugRuntimeClasspath.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,14 @@ com.google.j2objc:j2objc-annotations:1.3
180180
com.google.maps.android:maps-compose:4.4.1
181181
com.google.maps.android:maps-ktx:5.0.0
182182
com.google.zxing:core:3.5.3
183+
com.russhwolf:multiplatform-settings-android-debug:1.2.0
184+
com.russhwolf:multiplatform-settings-coroutines-android-debug:1.2.0
185+
com.russhwolf:multiplatform-settings-coroutines:1.2.0
186+
com.russhwolf:multiplatform-settings-no-arg-android-debug:1.2.0
187+
com.russhwolf:multiplatform-settings-no-arg:1.2.0
188+
com.russhwolf:multiplatform-settings-serialization-android-debug:1.2.0
189+
com.russhwolf:multiplatform-settings-serialization:1.2.0
190+
com.russhwolf:multiplatform-settings:1.2.0
183191
com.squareup.okhttp3:logging-interceptor:4.12.0
184192
com.squareup.okhttp3:okhttp:4.12.0
185193
com.squareup.okio:okio-jvm:3.9.1

androidApp/dependencies/demoReleaseRuntimeClasspath.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,14 @@ com.google.j2objc:j2objc-annotations:1.3
175175
com.google.maps.android:maps-compose:4.4.1
176176
com.google.maps.android:maps-ktx:5.0.0
177177
com.google.zxing:core:3.5.3
178+
com.russhwolf:multiplatform-settings-android:1.2.0
179+
com.russhwolf:multiplatform-settings-coroutines-android:1.2.0
180+
com.russhwolf:multiplatform-settings-coroutines:1.2.0
181+
com.russhwolf:multiplatform-settings-no-arg-android:1.2.0
182+
com.russhwolf:multiplatform-settings-no-arg:1.2.0
183+
com.russhwolf:multiplatform-settings-serialization-android:1.2.0
184+
com.russhwolf:multiplatform-settings-serialization:1.2.0
185+
com.russhwolf:multiplatform-settings:1.2.0
178186
com.squareup.okhttp3:logging-interceptor:4.12.0
179187
com.squareup.okhttp3:okhttp:4.12.0
180188
com.squareup.okio:okio-jvm:3.9.1

androidApp/dependencies/prodDebugRuntimeClasspath.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,14 @@ com.google.j2objc:j2objc-annotations:1.3
180180
com.google.maps.android:maps-compose:4.4.1
181181
com.google.maps.android:maps-ktx:5.0.0
182182
com.google.zxing:core:3.5.3
183+
com.russhwolf:multiplatform-settings-android-debug:1.2.0
184+
com.russhwolf:multiplatform-settings-coroutines-android-debug:1.2.0
185+
com.russhwolf:multiplatform-settings-coroutines:1.2.0
186+
com.russhwolf:multiplatform-settings-no-arg-android-debug:1.2.0
187+
com.russhwolf:multiplatform-settings-no-arg:1.2.0
188+
com.russhwolf:multiplatform-settings-serialization-android-debug:1.2.0
189+
com.russhwolf:multiplatform-settings-serialization:1.2.0
190+
com.russhwolf:multiplatform-settings:1.2.0
183191
com.squareup.okhttp3:logging-interceptor:4.12.0
184192
com.squareup.okhttp3:okhttp:4.12.0
185193
com.squareup.okio:okio-jvm:3.9.1

androidApp/dependencies/prodReleaseRuntimeClasspath.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,14 @@ com.google.j2objc:j2objc-annotations:1.3
175175
com.google.maps.android:maps-compose:4.4.1
176176
com.google.maps.android:maps-ktx:5.0.0
177177
com.google.zxing:core:3.5.3
178+
com.russhwolf:multiplatform-settings-android:1.2.0
179+
com.russhwolf:multiplatform-settings-coroutines-android:1.2.0
180+
com.russhwolf:multiplatform-settings-coroutines:1.2.0
181+
com.russhwolf:multiplatform-settings-no-arg-android:1.2.0
182+
com.russhwolf:multiplatform-settings-no-arg:1.2.0
183+
com.russhwolf:multiplatform-settings-serialization-android:1.2.0
184+
com.russhwolf:multiplatform-settings-serialization:1.2.0
185+
com.russhwolf:multiplatform-settings:1.2.0
178186
com.squareup.okhttp3:logging-interceptor:4.12.0
179187
com.squareup.okhttp3:okhttp:4.12.0
180188
com.squareup.okio:okio-jvm:3.9.1

core/datastore/build.gradle.kts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
*
88
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
99
*/
10+
1011
plugins {
11-
alias(libs.plugins.mifos.android.library)
12-
alias(libs.plugins.mifos.android.hilt)
13-
alias(libs.plugins.kotlin.serialization)
12+
alias(libs.plugins.mifos.kmp.library)
13+
id("kotlinx-serialization")
1414
}
1515

1616
android {
@@ -22,9 +22,17 @@ android {
2222

2323
}
2424

25-
dependencies {
26-
implementation(projects.core.common)
27-
implementation(projects.core.model)
25+
kotlin{
26+
27+
sourceSets{
28+
commonMain.dependencies {
29+
implementation(libs.multiplatform.settings)
30+
implementation(libs.multiplatform.settings.serialization)
31+
implementation(libs.multiplatform.settings.coroutines)
32+
implementation(libs.kotlinx.coroutines.core)
33+
implementation(libs.kotlinx.serialization.core)
34+
implementation(projects.core.common)
35+
}
36+
}
37+
}
2838

29-
30-
}
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/*
2+
* Copyright 2025 Mifos Initiative
3+
*
4+
* This Source Code Form is subject to the terms of the Mozilla Public
5+
* License, v. 2.0. If a copy of the MPL was not distributed with this
6+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
7+
*
8+
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
9+
*/
10+
@file:OptIn(ExperimentalSerializationApi::class, ExperimentalSettingsApi::class)
11+
12+
package org.mifos.mobile.core.datastore
13+
14+
import com.russhwolf.settings.ExperimentalSettingsApi
15+
import com.russhwolf.settings.Settings
16+
import com.russhwolf.settings.serialization.decodeValue
17+
import com.russhwolf.settings.serialization.decodeValueOrNull
18+
import com.russhwolf.settings.serialization.encodeValue
19+
import kotlinx.coroutines.CoroutineDispatcher
20+
import kotlinx.coroutines.flow.MutableStateFlow
21+
import kotlinx.coroutines.flow.map
22+
import kotlinx.coroutines.withContext
23+
import kotlinx.serialization.ExperimentalSerializationApi
24+
import org.mifos.mobile.core.datastore.model.AppSettings
25+
import org.mifos.mobile.core.datastore.model.AppTheme
26+
import org.mifos.mobile.core.datastore.model.UserData
27+
28+
private const val USER_DATA = "userData"
29+
private const val APP_SETTINGS = "appSettings"
30+
31+
class UserPreferencesDataSource(
32+
private val settings: Settings,
33+
private val dispatcher: CoroutineDispatcher,
34+
) {
35+
36+
private val _userInfo = MutableStateFlow(
37+
settings.decodeValue(
38+
key = USER_DATA,
39+
serializer = UserData.serializer(),
40+
defaultValue = settings.decodeValueOrNull(
41+
key = USER_DATA,
42+
serializer = UserData.serializer(),
43+
) ?: UserData.DEFAULT,
44+
),
45+
)
46+
47+
private val _settingsInfo = MutableStateFlow(
48+
settings.decodeValue(
49+
key = APP_SETTINGS,
50+
serializer = AppSettings.serializer(),
51+
defaultValue = settings.decodeValueOrNull(
52+
key = APP_SETTINGS,
53+
serializer = AppSettings.serializer(),
54+
) ?: AppSettings.DEFAULT,
55+
),
56+
)
57+
58+
val token = _userInfo.map {
59+
it.base64EncodedAuthenticationKey
60+
}
61+
62+
val userInfo = _userInfo
63+
64+
val settingsInfo = _settingsInfo
65+
66+
val clientId = _userInfo.map { it.clientId }
67+
68+
val appTheme = _settingsInfo.map { it.appTheme }
69+
70+
suspend fun updateSettingsInfo(appSettings: AppSettings) {
71+
withContext(dispatcher) {
72+
settings.putSettingsPreference(appSettings)
73+
_settingsInfo.value = appSettings
74+
}
75+
}
76+
77+
suspend fun updateUserInfo(user: UserData) {
78+
withContext(dispatcher) {
79+
settings.putUserPreference(user)
80+
_userInfo.value = user
81+
}
82+
}
83+
84+
suspend fun updateToken(token: String) {
85+
withContext(dispatcher) {
86+
settings.putUserPreference(
87+
UserData.DEFAULT.copy(
88+
base64EncodedAuthenticationKey = token,
89+
),
90+
)
91+
_userInfo.value = UserData.DEFAULT.copy(
92+
base64EncodedAuthenticationKey = token,
93+
)
94+
}
95+
}
96+
97+
suspend fun updateTheme(theme: AppTheme) {
98+
withContext(dispatcher) {
99+
settings.putSettingsPreference(
100+
AppSettings.DEFAULT.copy(
101+
appTheme = theme,
102+
),
103+
)
104+
_settingsInfo.value = AppSettings.DEFAULT.copy(
105+
appTheme = theme,
106+
)
107+
}
108+
}
109+
110+
fun updateProfileImage(image: String) {
111+
settings.putString(PROFILE_IMAGE, image)
112+
}
113+
114+
fun getProfileImage(): String? {
115+
return settings.getString(PROFILE_IMAGE, "").ifEmpty { null }
116+
}
117+
118+
suspend fun clearInfo() {
119+
withContext(dispatcher) {
120+
settings.clear()
121+
}
122+
}
123+
124+
companion object {
125+
private const val PROFILE_IMAGE = "preferences_profile_image"
126+
}
127+
}
128+
129+
@OptIn(ExperimentalSerializationApi::class)
130+
private fun Settings.putUserPreference(user: UserData) {
131+
encodeValue(
132+
key = USER_DATA,
133+
serializer = UserData.serializer(),
134+
value = user,
135+
)
136+
}
137+
138+
private fun Settings.putSettingsPreference(settings: AppSettings) {
139+
encodeValue(
140+
key = APP_SETTINGS,
141+
serializer = AppSettings.serializer(),
142+
value = settings,
143+
)
144+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2025 Mifos Initiative
3+
*
4+
* This Source Code Form is subject to the terms of the Mozilla Public
5+
* License, v. 2.0. If a copy of the MPL was not distributed with this
6+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
7+
*
8+
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
9+
*/
10+
package org.mifos.mobile.core.datastore
11+
12+
import kotlinx.coroutines.flow.Flow
13+
import kotlinx.coroutines.flow.StateFlow
14+
import org.mifos.mobile.core.datastore.model.AppSettings
15+
import org.mifos.mobile.core.datastore.model.AppTheme
16+
import org.mifos.mobile.core.datastore.model.UserData
17+
import org.mifospay.core.common.DataState
18+
19+
interface UserPreferencesRepository {
20+
val userInfo: Flow<UserData>
21+
22+
val settingsInfo: Flow<AppSettings>
23+
24+
val token: StateFlow<String?>
25+
26+
val clientId: StateFlow<Long?>
27+
28+
val appTheme: StateFlow<AppTheme?>
29+
30+
val profileImage: String?
31+
32+
suspend fun updateToken(token: String): DataState<Unit>
33+
34+
suspend fun updateTheme(theme: AppTheme): DataState<Unit>
35+
36+
suspend fun updateUser(user: UserData): DataState<Unit>
37+
38+
suspend fun updateSettings(appSettings: AppSettings): DataState<Unit>
39+
40+
suspend fun updateProfileImage(image: String): DataState<Unit>
41+
42+
suspend fun logOut(): Unit
43+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
* Copyright 2025 Mifos Initiative
3+
*
4+
* This Source Code Form is subject to the terms of the Mozilla Public
5+
* License, v. 2.0. If a copy of the MPL was not distributed with this
6+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
7+
*
8+
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
9+
*/
10+
package org.mifos.mobile.core.datastore
11+
12+
import kotlinx.coroutines.CoroutineDispatcher
13+
import kotlinx.coroutines.CoroutineScope
14+
import kotlinx.coroutines.flow.Flow
15+
import kotlinx.coroutines.flow.SharingStarted
16+
import kotlinx.coroutines.flow.StateFlow
17+
import kotlinx.coroutines.flow.stateIn
18+
import org.mifos.mobile.core.datastore.model.AppSettings
19+
import org.mifos.mobile.core.datastore.model.AppTheme
20+
import org.mifos.mobile.core.datastore.model.UserData
21+
import org.mifospay.core.common.DataState
22+
23+
class UserPreferencesRepositoryImpl(
24+
private val preferenceManager: UserPreferencesDataSource,
25+
private val ioDispatcher: CoroutineDispatcher,
26+
unconfinedDispatcher: CoroutineDispatcher,
27+
) : UserPreferencesRepository {
28+
private val unconfinedScope = CoroutineScope(unconfinedDispatcher)
29+
30+
override val userInfo: Flow<UserData>
31+
get() = preferenceManager.userInfo
32+
33+
override val settingsInfo: Flow<AppSettings>
34+
get() = preferenceManager.settingsInfo
35+
36+
override val appTheme: StateFlow<AppTheme?>
37+
get() = preferenceManager.appTheme.stateIn(
38+
scope = unconfinedScope,
39+
initialValue = null,
40+
started = SharingStarted.Eagerly,
41+
)
42+
override val token: StateFlow<String?>
43+
get() = preferenceManager.token.stateIn(
44+
scope = unconfinedScope,
45+
initialValue = null,
46+
started = SharingStarted.Eagerly,
47+
)
48+
49+
override val clientId: StateFlow<Long?>
50+
get() = preferenceManager.clientId.stateIn(
51+
scope = unconfinedScope,
52+
initialValue = null,
53+
started = SharingStarted.Eagerly,
54+
)
55+
56+
override val profileImage: String?
57+
get() = preferenceManager.getProfileImage()
58+
59+
override suspend fun updateToken(token: String): DataState<Unit> {
60+
return try {
61+
val result = preferenceManager.updateToken(token)
62+
DataState.Success(result)
63+
} catch (e: Exception) {
64+
DataState.Error(e)
65+
}
66+
}
67+
68+
override suspend fun updateTheme(theme: AppTheme): DataState<Unit> {
69+
return try {
70+
val result = preferenceManager.updateTheme(theme)
71+
DataState.Success(result)
72+
} catch (e: Exception) {
73+
DataState.Error(e)
74+
}
75+
}
76+
77+
override suspend fun updateUser(user: UserData): DataState<Unit> {
78+
return try {
79+
val result = preferenceManager.updateUserInfo(user)
80+
DataState.Success(result)
81+
} catch (e: Exception) {
82+
DataState.Error(e)
83+
}
84+
}
85+
86+
override suspend fun updateSettings(appSettings: AppSettings): DataState<Unit> {
87+
return try {
88+
val result = preferenceManager.updateSettingsInfo(appSettings)
89+
DataState.Success(result)
90+
} catch (e: Exception) {
91+
DataState.Error(e)
92+
}
93+
}
94+
95+
override suspend fun updateProfileImage(image: String): DataState<Unit> {
96+
return try {
97+
val result = preferenceManager.updateProfileImage(image)
98+
DataState.Success(result)
99+
} catch (e: Exception) {
100+
DataState.Error(e)
101+
}
102+
}
103+
104+
override suspend fun logOut() {
105+
preferenceManager.clearInfo()
106+
}
107+
}

0 commit comments

Comments
 (0)