Skip to content
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

feat: Migrated :core:datastore module to KMP #2737

Merged
merged 12 commits into from
Jan 14, 2025
8 changes: 8 additions & 0 deletions androidApp/dependencies/demoDebugRuntimeClasspath.txt
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,14 @@ com.google.j2objc:j2objc-annotations:1.3
com.google.maps.android:maps-compose:4.4.1
com.google.maps.android:maps-ktx:5.0.0
com.google.zxing:core:3.5.3
com.russhwolf:multiplatform-settings-android-debug:1.2.0
com.russhwolf:multiplatform-settings-coroutines-android-debug:1.2.0
com.russhwolf:multiplatform-settings-coroutines:1.2.0
com.russhwolf:multiplatform-settings-no-arg-android-debug:1.2.0
com.russhwolf:multiplatform-settings-no-arg:1.2.0
com.russhwolf:multiplatform-settings-serialization-android-debug:1.2.0
com.russhwolf:multiplatform-settings-serialization:1.2.0
com.russhwolf:multiplatform-settings:1.2.0
com.squareup.okhttp3:logging-interceptor:4.12.0
com.squareup.okhttp3:okhttp:4.12.0
com.squareup.okio:okio-jvm:3.9.1
Expand Down
8 changes: 8 additions & 0 deletions androidApp/dependencies/demoReleaseRuntimeClasspath.txt
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,14 @@ com.google.j2objc:j2objc-annotations:1.3
com.google.maps.android:maps-compose:4.4.1
com.google.maps.android:maps-ktx:5.0.0
com.google.zxing:core:3.5.3
com.russhwolf:multiplatform-settings-android:1.2.0
com.russhwolf:multiplatform-settings-coroutines-android:1.2.0
com.russhwolf:multiplatform-settings-coroutines:1.2.0
com.russhwolf:multiplatform-settings-no-arg-android:1.2.0
com.russhwolf:multiplatform-settings-no-arg:1.2.0
com.russhwolf:multiplatform-settings-serialization-android:1.2.0
com.russhwolf:multiplatform-settings-serialization:1.2.0
com.russhwolf:multiplatform-settings:1.2.0
com.squareup.okhttp3:logging-interceptor:4.12.0
com.squareup.okhttp3:okhttp:4.12.0
com.squareup.okio:okio-jvm:3.9.1
Expand Down
8 changes: 8 additions & 0 deletions androidApp/dependencies/prodDebugRuntimeClasspath.txt
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,14 @@ com.google.j2objc:j2objc-annotations:1.3
com.google.maps.android:maps-compose:4.4.1
com.google.maps.android:maps-ktx:5.0.0
com.google.zxing:core:3.5.3
com.russhwolf:multiplatform-settings-android-debug:1.2.0
com.russhwolf:multiplatform-settings-coroutines-android-debug:1.2.0
com.russhwolf:multiplatform-settings-coroutines:1.2.0
com.russhwolf:multiplatform-settings-no-arg-android-debug:1.2.0
com.russhwolf:multiplatform-settings-no-arg:1.2.0
com.russhwolf:multiplatform-settings-serialization-android-debug:1.2.0
com.russhwolf:multiplatform-settings-serialization:1.2.0
com.russhwolf:multiplatform-settings:1.2.0
com.squareup.okhttp3:logging-interceptor:4.12.0
com.squareup.okhttp3:okhttp:4.12.0
com.squareup.okio:okio-jvm:3.9.1
Expand Down
8 changes: 8 additions & 0 deletions androidApp/dependencies/prodReleaseRuntimeClasspath.txt
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,14 @@ com.google.j2objc:j2objc-annotations:1.3
com.google.maps.android:maps-compose:4.4.1
com.google.maps.android:maps-ktx:5.0.0
com.google.zxing:core:3.5.3
com.russhwolf:multiplatform-settings-android:1.2.0
com.russhwolf:multiplatform-settings-coroutines-android:1.2.0
com.russhwolf:multiplatform-settings-coroutines:1.2.0
com.russhwolf:multiplatform-settings-no-arg-android:1.2.0
com.russhwolf:multiplatform-settings-no-arg:1.2.0
com.russhwolf:multiplatform-settings-serialization-android:1.2.0
com.russhwolf:multiplatform-settings-serialization:1.2.0
com.russhwolf:multiplatform-settings:1.2.0
com.squareup.okhttp3:logging-interceptor:4.12.0
com.squareup.okhttp3:okhttp:4.12.0
com.squareup.okio:okio-jvm:3.9.1
Expand Down
24 changes: 16 additions & 8 deletions core/datastore/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/

plugins {
alias(libs.plugins.mifos.android.library)
alias(libs.plugins.mifos.android.hilt)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.mifos.kmp.library)
id("kotlinx-serialization")
}

android {
Expand All @@ -22,9 +22,17 @@ android {

}

dependencies {
implementation(projects.core.common)
implementation(projects.core.model)
kotlin{

sourceSets{
commonMain.dependencies {
implementation(libs.multiplatform.settings)
implementation(libs.multiplatform.settings.serialization)
implementation(libs.multiplatform.settings.coroutines)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.serialization.core)
implementation(projects.core.common)
}
}
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/*
* Copyright 2025 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
@file:OptIn(ExperimentalSerializationApi::class, ExperimentalSettingsApi::class)

package org.mifos.mobile.core.datastore

import com.russhwolf.settings.ExperimentalSettingsApi
import com.russhwolf.settings.Settings
import com.russhwolf.settings.serialization.decodeValue
import com.russhwolf.settings.serialization.decodeValueOrNull
import com.russhwolf.settings.serialization.encodeValue
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import kotlinx.serialization.ExperimentalSerializationApi
import org.mifos.mobile.core.datastore.model.AppSettings
import org.mifos.mobile.core.datastore.model.AppTheme
import org.mifos.mobile.core.datastore.model.UserData

private const val USER_DATA = "userData"
private const val APP_SETTINGS = "appSettings"

class UserPreferencesDataSource(
private val settings: Settings,
private val dispatcher: CoroutineDispatcher,
) {

private val _userInfo = MutableStateFlow(
settings.decodeValue(
key = USER_DATA,
serializer = UserData.serializer(),
defaultValue = settings.decodeValueOrNull(
key = USER_DATA,
serializer = UserData.serializer(),
) ?: UserData.DEFAULT,
),
)

private val _settingsInfo = MutableStateFlow(
settings.decodeValue(
key = APP_SETTINGS,
serializer = AppSettings.serializer(),
defaultValue = settings.decodeValueOrNull(
key = APP_SETTINGS,
serializer = AppSettings.serializer(),
) ?: AppSettings.DEFAULT,
),
)

val token = _userInfo.map {
it.base64EncodedAuthenticationKey
}

val userInfo = _userInfo

val settingsInfo = _settingsInfo

val clientId = _userInfo.map { it.clientId }

val appTheme = _settingsInfo.map { it.appTheme }

suspend fun updateSettingsInfo(appSettings: AppSettings) {
withContext(dispatcher) {
settings.putSettingsPreference(appSettings)
_settingsInfo.value = appSettings
}
}

suspend fun updateUserInfo(user: UserData) {
withContext(dispatcher) {
settings.putUserPreference(user)
_userInfo.value = user
}
}

suspend fun updateToken(token: String) {
withContext(dispatcher) {
settings.putUserPreference(
UserData.DEFAULT.copy(
base64EncodedAuthenticationKey = token,
),
)
_userInfo.value = UserData.DEFAULT.copy(
base64EncodedAuthenticationKey = token,
)
}
}

suspend fun updateTheme(theme: AppTheme) {
withContext(dispatcher) {
settings.putSettingsPreference(
AppSettings.DEFAULT.copy(
appTheme = theme,
),
)
_settingsInfo.value = AppSettings.DEFAULT.copy(
appTheme = theme,
)
}
}

fun updateProfileImage(image: String) {
settings.putString(PROFILE_IMAGE, image)
}

fun getProfileImage(): String? {
return settings.getString(PROFILE_IMAGE, "").ifEmpty { null }
}

suspend fun clearInfo() {
withContext(dispatcher) {
settings.clear()
}
}

companion object {
private const val PROFILE_IMAGE = "preferences_profile_image"
}
}

@OptIn(ExperimentalSerializationApi::class)
private fun Settings.putUserPreference(user: UserData) {
encodeValue(
key = USER_DATA,
serializer = UserData.serializer(),
value = user,
)
}

private fun Settings.putSettingsPreference(settings: AppSettings) {
encodeValue(
key = APP_SETTINGS,
serializer = AppSettings.serializer(),
value = settings,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2025 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.core.datastore

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import org.mifos.mobile.core.datastore.model.AppSettings
import org.mifos.mobile.core.datastore.model.AppTheme
import org.mifos.mobile.core.datastore.model.UserData
import org.mifospay.core.common.DataState

interface UserPreferencesRepository {
val userInfo: Flow<UserData>

val settingsInfo: Flow<AppSettings>

val token: StateFlow<String?>

val clientId: StateFlow<Long?>

val appTheme: StateFlow<AppTheme?>

val profileImage: String?

suspend fun updateToken(token: String): DataState<Unit>
revanthkumarJ marked this conversation as resolved.
Show resolved Hide resolved

suspend fun updateTheme(theme: AppTheme): DataState<Unit>

suspend fun updateUser(user: UserData): DataState<Unit>

suspend fun updateSettings(appSettings: AppSettings): DataState<Unit>

suspend fun updateProfileImage(image: String): DataState<Unit>

suspend fun logOut(): Unit
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* Copyright 2025 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-mobile/blob/master/LICENSE.md
*/
package org.mifos.mobile.core.datastore

import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.stateIn
import org.mifos.mobile.core.datastore.model.AppSettings
import org.mifos.mobile.core.datastore.model.AppTheme
import org.mifos.mobile.core.datastore.model.UserData
import org.mifospay.core.common.DataState

class UserPreferencesRepositoryImpl(
private val preferenceManager: UserPreferencesDataSource,
private val ioDispatcher: CoroutineDispatcher,
unconfinedDispatcher: CoroutineDispatcher,
) : UserPreferencesRepository {
private val unconfinedScope = CoroutineScope(unconfinedDispatcher)

override val userInfo: Flow<UserData>
get() = preferenceManager.userInfo

override val settingsInfo: Flow<AppSettings>
get() = preferenceManager.settingsInfo

override val appTheme: StateFlow<AppTheme?>
get() = preferenceManager.appTheme.stateIn(
scope = unconfinedScope,
initialValue = null,
started = SharingStarted.Eagerly,
)
override val token: StateFlow<String?>
get() = preferenceManager.token.stateIn(
scope = unconfinedScope,
initialValue = null,
started = SharingStarted.Eagerly,
)

override val clientId: StateFlow<Long?>
get() = preferenceManager.clientId.stateIn(
scope = unconfinedScope,
initialValue = null,
started = SharingStarted.Eagerly,
)

override val profileImage: String?
get() = preferenceManager.getProfileImage()

override suspend fun updateToken(token: String): DataState<Unit> {
return try {
val result = preferenceManager.updateToken(token)
DataState.Success(result)
} catch (e: Exception) {
DataState.Error(e)
}
}

override suspend fun updateTheme(theme: AppTheme): DataState<Unit> {
return try {
val result = preferenceManager.updateTheme(theme)
DataState.Success(result)
} catch (e: Exception) {
DataState.Error(e)
}
}

override suspend fun updateUser(user: UserData): DataState<Unit> {
return try {
val result = preferenceManager.updateUserInfo(user)
DataState.Success(result)
} catch (e: Exception) {
DataState.Error(e)
}
}

override suspend fun updateSettings(appSettings: AppSettings): DataState<Unit> {
return try {
val result = preferenceManager.updateSettingsInfo(appSettings)
DataState.Success(result)
} catch (e: Exception) {
DataState.Error(e)
}
}

override suspend fun updateProfileImage(image: String): DataState<Unit> {
return try {
val result = preferenceManager.updateProfileImage(image)
DataState.Success(result)
} catch (e: Exception) {
DataState.Error(e)
}
}

override suspend fun logOut() {
preferenceManager.clearInfo()
}
}
Loading
Loading