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

Migration of Primitive Datatypes from SharedPreferences to PreferenceDataStore #3205

Draft
wants to merge 46 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
56a63f2
Setup migration to PreferenceDataStore
DebbieArita Apr 18, 2024
fc81417
Update keys
DebbieArita Apr 19, 2024
4e4b84b
refactor questionnaire viewmodel,and part of usersetting viewmodel
brandyodhiambo Apr 19, 2024
6863926
Merge branch 'main' into migrate-to-preference
DebbieArita Apr 22, 2024
8a430d8
refactor appMainviewmodel, usersettingviewmodel , AppSettingViewmodel…
brandyodhiambo Apr 22, 2024
776e4de
Merge remote-tracking branch 'origin/migrate-to-preference' into migr…
brandyodhiambo Apr 22, 2024
f69f5d1
Merge branch 'main' into migrate-to-preference
DebbieArita Apr 24, 2024
b5dca70
refactor register viewmodel
brandyodhiambo Apr 25, 2024
41ec232
started on viewmodel test
brandyodhiambo Apr 30, 2024
772f755
Merge branch 'main' into migrate-to-preference
DebbieArita May 2, 2024
465f34c
Resolve conflicts
DebbieArita May 15, 2024
1bedd07
Merge branch 'main' into migrate-to-preference
DebbieArita May 23, 2024
60349fe
Resolve conflict
DebbieArita May 30, 2024
3ab150f
Resolve conflict
DebbieArita May 30, 2024
0f09535
Resolve merge conflicts
DebbieArita Jun 18, 2024
0ff201b
Merge branch 'main' into migrate-to-preference
DebbieArita Jun 18, 2024
48241a2
Run spotlessApply
DebbieArita Jun 18, 2024
fbd7987
Fix conflicts
DebbieArita Jul 25, 2024
2eab778
Merge branch 'main' into migrate-to-preference
DebbieArita Jul 31, 2024
d448564
Merge branch 'main' into migrate-to-preference
DebbieArita Aug 1, 2024
a549ecb
WIP tests
DebbieArita Aug 2, 2024
c516a4d
Resolve merge conflicts
DebbieArita Aug 2, 2024
fbe74cb
Resolve merge conflicts
DebbieArita Aug 22, 2024
51106e4
Run spotlessApply
DebbieArita Aug 22, 2024
408cded
Run spotlessApply
DebbieArita Aug 22, 2024
95c330c
Resolve conflicts
DebbieArita Aug 22, 2024
ec4b1ff
Run spotlessApply
DebbieArita Aug 22, 2024
2ae218d
Merge branch 'main' into migrate-to-preference
DebbieArita Aug 23, 2024
7f8a9c0
Merge branch 'main' into migrate-to-preference
DebbieArita Aug 27, 2024
bd02589
Update DefaultRepository Tests
DebbieArita Aug 29, 2024
64716cd
Resolve AppMainViewModel
DebbieArita Sep 3, 2024
e59a6f8
Resolve LoginViewModel
DebbieArita Sep 6, 2024
5d862cd
Merge branch 'main' into migrate-to-preference
DebbieArita Sep 23, 2024
266a697
Update RulesFactoryTest
DebbieArita Sep 25, 2024
99e4540
Resolve conflicts
DebbieArita Oct 1, 2024
fd5cae8
Fix test and run spotlessApply
DebbieArita Oct 1, 2024
c524d5a
Update Faker
DebbieArita Oct 2, 2024
362acc7
Merge branch 'main' into migrate-to-preference
DebbieArita Oct 11, 2024
86cc530
Merge branch 'main' into migrate-to-preference
DebbieArita Oct 15, 2024
5cd114e
Merge branch 'main' into migrate-to-preference
DebbieArita Oct 18, 2024
b88d80d
Merge branch 'main' into migrate-to-preference
pld Oct 18, 2024
01202e7
Resolve merge conflicts
DebbieArita Nov 7, 2024
f481d59
Update SharedPreferenceHelper with PreferenceDatastore
DebbieArita Nov 11, 2024
688d0b1
Resolve merge conflicts
DebbieArita Nov 14, 2024
ade8c3e
Resolve merge conflict
DebbieArita Nov 19, 2024
37b2723
WIP migration from sharedPrefHelper to datastore
DebbieArita Nov 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,12 @@ import org.smartregister.fhircore.engine.BuildConfig
import org.smartregister.fhircore.engine.configuration.app.ApplicationConfiguration
import org.smartregister.fhircore.engine.configuration.app.ConfigService
import org.smartregister.fhircore.engine.data.remote.fhir.resource.FhirResourceDataSource
import org.smartregister.fhircore.engine.datastore.PreferenceDataStore
import org.smartregister.fhircore.engine.di.NetworkModule
import org.smartregister.fhircore.engine.domain.model.MultiSelectViewAction
import org.smartregister.fhircore.engine.util.DispatcherProvider
import org.smartregister.fhircore.engine.util.KnowledgeManagerUtil
import org.smartregister.fhircore.engine.util.SharedPreferenceKey
import org.smartregister.fhircore.engine.util.SharedPreferencesHelper
import org.smartregister.fhircore.engine.util.extension.camelCase
import org.smartregister.fhircore.engine.util.extension.decodeJson
import org.smartregister.fhircore.engine.util.extension.decodeResourceFromString
Expand All @@ -86,7 +86,7 @@ class ConfigurationRegistry
constructor(
val fhirEngine: FhirEngine,
val fhirResourceDataSource: FhirResourceDataSource,
val sharedPreferencesHelper: SharedPreferencesHelper,
val preferenceDataStore: PreferenceDataStore,
val dispatcherProvider: DispatcherProvider,
val configService: ConfigService,
val json: Json,
Expand Down Expand Up @@ -159,10 +159,25 @@ constructor(
* placeholders e.g. {{ placeholder }} with value retrieved from the [paramsMap] using [configKey]
* as the key. If value is null the placeholder is returned
*/
fun getConfigValueWithParam(paramsMap: Map<String, String>?, configKey: String) =
configsJsonMap.getValue(configKey).let { jsonValue ->
if (paramsMap != null) jsonValue.interpolate(paramsMap) else jsonValue
// fun getConfigValueWithParam(paramsMap: Map<String, String>?, configKey: String) =
// configsJsonMap.getValue(configKey).let { jsonValue ->
// if (paramsMap != null) jsonValue.interpolate(paramsMap) else jsonValue
// }

fun getConfigValueWithParam(paramsMap: Map<String, String>?, configKey: String): String {
try {
configsJsonMap.getValue(configKey).let { jsonValue ->
val interpolatedValue = if (paramsMap != null) jsonValue.interpolate(paramsMap) else jsonValue
// Use interpolatedValue here if needed, or perform any side effects
return interpolatedValue
}
} catch (e: Exception) {
Timber.e("Error processing config key: $configKey. Exception: ${e.message}")
// You can log or handle the exception as needed
return ""
}
}


/**
* Retrieve configuration for the provided [ConfigType]. The JSON retrieved from [configsJsonMap]
Expand Down Expand Up @@ -410,7 +425,7 @@ constructor(
suspend fun fetchNonWorkflowConfigResources() {
Timber.d("Triggered fetching application configurations remotely")
configCacheMap.clear()
sharedPreferencesHelper.read(SharedPreferenceKey.APP_ID.name, null)?.let { appId ->
preferenceDataStore.readOnce(PreferenceDataStore.APP_ID, null)?.let { appId ->
val parsedAppId = appId.substringBefore(TYPE_REFERENCE_DELIMITER).trim()
val compositionResource = fetchRemoteCompositionByAppId(parsedAppId)
compositionResource?.let { composition ->
Expand Down Expand Up @@ -729,7 +744,10 @@ constructor(
val fhirResourceSearchParams = mutableMapOf<ResourceType, MutableMap<String, String>>()
val organizationResourceTag =
configService.defineResourceTags().find { it.type == ResourceType.Organization.name }
val mandatoryTags = configService.provideResourceTags(sharedPreferencesHelper)
val mandatoryTags =
configService.provideResourceTags(
preferenceDataStore
)

val locationIds =
context.retrieveRelatedEntitySyncLocationState(MultiSelectViewAction.SYNC_DATA).map {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@

package org.smartregister.fhircore.engine.configuration.app

import androidx.datastore.preferences.core.stringPreferencesKey
import org.hl7.fhir.r4.model.Coding
import org.hl7.fhir.r4.model.Enumerations
import org.hl7.fhir.r4.model.ResourceType
import org.hl7.fhir.r4.model.SearchParameter
import org.smartregister.fhircore.engine.datastore.PreferenceDataStore
import org.smartregister.fhircore.engine.sync.ResourceTag
import org.smartregister.fhircore.engine.util.SharedPreferenceKey
import org.smartregister.fhircore.engine.util.SharedPreferencesHelper
import org.smartregister.fhircore.engine.util.extension.extractLogicalIdUuid

/** An interface that provides the application configurations. */
Expand All @@ -38,12 +38,14 @@ interface ConfigService {
* Provide a list of [Coding] that represents [ResourceTag]. [Coding] can be directly appended to
* a FHIR resource.
*/
fun provideResourceTags(sharedPreferencesHelper: SharedPreferencesHelper): List<Coding> {
suspend fun provideResourceTags(
preferenceDataStore: PreferenceDataStore
): List<Coding> {
val tags = mutableListOf<Coding>()
defineResourceTags().forEach { strategy ->
when (strategy.type) {
ResourceType.Practitioner.name -> {
val id = sharedPreferencesHelper.read(SharedPreferenceKey.PRACTITIONER_ID.name, null)
val id = preferenceDataStore.readOnce(PreferenceDataStore.PRACTITIONER_ID)
if (id.isNullOrBlank() || id.isEmpty()) {
strategy.tag.let { tag -> tags.add(tag.copy().apply { code = "Not defined" }) }
} else {
Expand All @@ -54,16 +56,32 @@ interface ConfigService {
}
APP_VERSION -> tags.add(strategy.tag.copy())
else -> {
val ids = sharedPreferencesHelper.read<List<String>>(strategy.type)
if (ids.isNullOrEmpty()) {
// ToDO: This is an object type
val datastoreKey = stringPreferencesKey("key")

val ids = preferenceDataStore.readOnce(datastoreKey, defaultValue = null)
val idList = ids?.split(",")

if (idList.isNullOrEmpty()) {
strategy.tag.let { tag -> tags.add(tag.copy().apply { code = "Not defined" }) }
} else {
ids.forEach { id ->
idList.forEach { id ->
strategy.tag.let { tag ->
tags.add(tag.copy().apply { code = id.extractLogicalIdUuid() })
}
}
}

// //sharedPreferencesHelper.read<List<String>>(strategy.type)
// if (ids.isNullOrEmpty()) {
// strategy.tag.let { tag -> tags.add(tag.copy().apply { code = "Not defined" }) }
// } else {
// ids.forEach { id ->
// strategy.tag.let { tag ->
// tags.add(tag.copy().apply { code = id.extractLogicalIdUuid() })
// }
// }
// }
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ import org.smartregister.fhircore.engine.configuration.app.ConfigService
import org.smartregister.fhircore.engine.configuration.event.EventWorkflow
import org.smartregister.fhircore.engine.configuration.profile.ManagingEntityConfig
import org.smartregister.fhircore.engine.configuration.register.ActiveResourceFilterConfig
import org.smartregister.fhircore.engine.datastore.PreferenceDataStore
import org.smartregister.fhircore.engine.domain.model.Code
import org.smartregister.fhircore.engine.domain.model.DataQuery
import org.smartregister.fhircore.engine.domain.model.FhirResourceConfig
Expand All @@ -85,7 +86,6 @@ import org.smartregister.fhircore.engine.domain.model.RuleConfig
import org.smartregister.fhircore.engine.domain.model.SortConfig
import org.smartregister.fhircore.engine.rulesengine.ConfigRulesExecutor
import org.smartregister.fhircore.engine.util.DispatcherProvider
import org.smartregister.fhircore.engine.util.SharedPreferencesHelper
import org.smartregister.fhircore.engine.util.extension.asReference
import org.smartregister.fhircore.engine.util.extension.batchedSearch
import org.smartregister.fhircore.engine.util.extension.encodeResourceToString
Expand All @@ -107,7 +107,7 @@ open class DefaultRepository
constructor(
open val fhirEngine: FhirEngine,
open val dispatcherProvider: DispatcherProvider,
open val sharedPreferencesHelper: SharedPreferencesHelper,
open val preferenceDataStore: PreferenceDataStore,
open val configurationRegistry: ConfigurationRegistry,
open val configService: ConfigService,
open val configRulesExecutor: ConfigRulesExecutor,
Expand Down Expand Up @@ -179,14 +179,17 @@ constructor(
fhirEngine.create(*resource, isLocalOnly = true)
}

private fun preProcessResources(addResourceTags: Boolean, vararg resource: Resource) {
private suspend fun preProcessResources(addResourceTags: Boolean, vararg resource: Resource) {
resource.onEach { currentResource ->
currentResource.apply {
updateLastUpdated()
generateMissingId()
}
if (addResourceTags) {
val tags = configService.provideResourceTags(sharedPreferencesHelper)
val tags =
configService.provideResourceTags(
preferenceDataStore = preferenceDataStore
)
tags.forEach {
val existingTag = currentResource.meta.getTag(it.system, it.code)
if (existingTag == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ import org.smartregister.fhircore.engine.configuration.profile.ProfileConfigurat
import org.smartregister.fhircore.engine.configuration.register.RegisterConfiguration
import org.smartregister.fhircore.engine.data.local.ContentCache
import org.smartregister.fhircore.engine.data.local.DefaultRepository
import org.smartregister.fhircore.engine.datastore.PreferenceDataStore
import org.smartregister.fhircore.engine.domain.model.ActionParameter
import org.smartregister.fhircore.engine.domain.model.ActionParameterType
import org.smartregister.fhircore.engine.domain.model.FhirResourceConfig
import org.smartregister.fhircore.engine.domain.model.RepositoryResourceData
import org.smartregister.fhircore.engine.domain.repository.Repository
import org.smartregister.fhircore.engine.rulesengine.ConfigRulesExecutor
import org.smartregister.fhircore.engine.util.DispatcherProvider
import org.smartregister.fhircore.engine.util.SharedPreferencesHelper
import org.smartregister.fhircore.engine.util.extension.extractLogicalIdUuid
import org.smartregister.fhircore.engine.util.fhirpath.FhirPathDataExtractor

Expand All @@ -46,7 +46,7 @@ class RegisterRepository
constructor(
override val fhirEngine: FhirEngine,
override val dispatcherProvider: DispatcherProvider,
override val sharedPreferencesHelper: SharedPreferencesHelper,
override val preferenceDataStore: PreferenceDataStore,
override val configurationRegistry: ConfigurationRegistry,
override val configService: ConfigService,
override val configRulesExecutor: ConfigRulesExecutor,
Expand All @@ -59,13 +59,13 @@ constructor(
DefaultRepository(
fhirEngine = fhirEngine,
dispatcherProvider = dispatcherProvider,
sharedPreferencesHelper = sharedPreferencesHelper,
configurationRegistry = configurationRegistry,
configService = configService,
configRulesExecutor = configRulesExecutor,
fhirPathDataExtractor = fhirPathDataExtractor,
parser = parser,
context = context,
preferenceDataStore = preferenceDataStore,
contentCache = contentCache,
) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,37 @@ package org.smartregister.fhircore.engine.datastore

import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.dataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.emptyPreferences
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import com.google.android.fhir.sync.SyncJobStatus
import com.google.gson.Gson
import dagger.hilt.android.qualifiers.ApplicationContext
import java.io.IOException
import javax.inject.Inject
import javax.inject.Singleton
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.map

const val DATASTORE_NAME = "preferences_datastore"
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = DATASTORE_NAME)
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.Serializable
import org.hl7.fhir.r4.model.ResourceType
import org.smartregister.fhircore.engine.util.SharedPreferenceKey
import org.smartregister.fhircore.engine.util.extension.lastOffset
import org.smartregister.model.practitioner.PractitionerDetails
import java.util.Locale

@Singleton
class PreferenceDataStore @Inject constructor(@ApplicationContext val context: Context) {
open class PreferenceDataStore
@Inject
constructor(@ApplicationContext val context: Context, val dataStore: DataStore<Preferences>) {
fun <T> read(key: Preferences.Key<T>) =
context.dataStore.data
dataStore.data
.catch { exception ->
if (exception is IOException) {
emit(emptyPreferences())
Expand All @@ -47,13 +58,82 @@ class PreferenceDataStore @Inject constructor(@ApplicationContext val context: C
}
.map { preferences -> preferences[key] as T }

suspend fun <T> write(key: Preferences.Key<T>, data: T) {
context.dataStore.edit { preferences -> preferences[key] = data }
fun <T> readOnce(key: Preferences.Key<T>, defaultValue: T? = null) = runBlocking {
dataStore.data.map { preferences -> preferences[key] ?: defaultValue }.firstOrNull()
}

suspend fun <T> write(key: Preferences.Key<T>, value: T) {
dataStore.edit { preferences -> preferences[key] = value }
}

suspend fun write(key: Preferences.Key<String>, value: String) {
dataStore.edit { preferences -> preferences[key] = value }
}

suspend fun <T> remove(key: Preferences.Key<T>) {
dataStore.edit { it.remove(key) }
}

suspend fun clear() {
dataStore.edit { it.clear() }
}

fun retrieveApplicationId() = read(APP_ID).toString()

fun resetPreferences() {
runBlocking {
dataStore.edit {
preferences ->
preferences.clear()
}
}
}

companion object Keys {
val PRACTITIONER_DETAILS by lazy { stringPreferencesKey("practitioner_details") }
val APP_ID by lazy { stringPreferencesKey("appId") }
val LANG by lazy { stringPreferencesKey("lang") }
val MIGRATION_VERSION by lazy { intPreferencesKey("migrationVersion") }
val LAST_SYNC_TIMESTAMP by lazy { stringPreferencesKey("lastSyncTimestamp") }
val PRACTITIONER_ID by lazy { stringPreferencesKey("practitionerId") }
val PRACTITIONER_LOCATION by lazy { stringPreferencesKey("practitionerLocation") }
val PRACTITIONER_LOCATION_ID by lazy { stringPreferencesKey("practitionerLocationId") }
val REMOTE_SYNC_RESOURCES by lazy { stringPreferencesKey("remoteSyncResources") }
val PREFS_SYNC_PROGRESS_TOTAL by lazy { longPreferencesKey("syncProgressTotal") }
val CARE_TEAM_ID by lazy { stringPreferencesKey("careTeamId") }
val ORGANIZATION_ID by lazy { stringPreferencesKey("organizationId") }
val LOCATION_ID by lazy { stringPreferencesKey("locationId") }
val PRACTITIONER_LOCATION_NAME by lazy { stringPreferencesKey("practitionerLocationName") }
val CARE_TEAM_NAME by lazy { stringPreferencesKey("careTeamName") }
val ORGANIZATION_NAME by lazy { stringPreferencesKey("organizationName") }

val CAREPLAN_LAST_SYNC_TIMESTAMP by lazy { stringPreferencesKey("careplanLastSyncTimestamp") }
val COMPLETE_CAREPLAN_WORKER_LAST_OFFSET by lazy { intPreferencesKey("completeCarePlanWorkerLastOffset".lastOffset()) }

val CAREPLAN_WORK_ID by lazy { stringPreferencesKey("careplanWorkerId") }
val CAREPLAN_BATCH_SIZE_FACTOR by lazy { intPreferencesKey("careplanBatchSizeFactor") }

val USER_INFO by lazy { stringPreferencesKey("user_info") }
fun resourceTimestampKey(resourceType: ResourceType) = stringPreferencesKey(
"${resourceType.name.uppercase()}_${SharedPreferenceKey.LAST_SYNC_TIMESTAMP.name}"
)

val PRACTITIONER_LOCATION_HIERARCHIES by lazy { stringPreferencesKey("practitionerLocationHierarchies") }

fun syncProgress(progressSyncJobStatus: SyncJobStatus.InProgress) = longPreferencesKey(
"$PREFS_SYNC_PROGRESS_TOTAL + ${
progressSyncJobStatus.syncOperation.name}")

fun currentLanguage() = stringPreferencesKey(
"$LANG, ${Locale.ENGLISH.toLanguageTag()}")

val PREFS_NAME by lazy { stringPreferencesKey("params") }

val Context.dataStore by preferencesDataStore(PREFS_NAME.name)

//TODO: check this vs similar one above
fun resetPrefs(context: Context) = runBlocking {
context.dataStore.edit { it.clear() }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import org.smartregister.fhircore.engine.domain.model.SyncLocationState
import org.smartregister.fhircore.engine.rulesengine.services.LocationCoordinate
import timber.log.Timber

// TODO : Don't use
private const val PRACTITIONER_DETAILS_DATASTORE_JSON = "practitioner_details.json"
private const val USER_INFO_DATASTORE_JSON = "user_info.json"
private const val LOCATION_COORDINATES_DATASTORE_JSON = "location_coordinates.json"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import org.smartregister.fhircore.engine.data.remote.auth.OAuthService
import org.smartregister.fhircore.engine.data.remote.fhir.resource.FhirConverterFactory
import org.smartregister.fhircore.engine.data.remote.fhir.resource.FhirResourceService
import org.smartregister.fhircore.engine.data.remote.shared.TokenAuthenticator
import org.smartregister.fhircore.engine.util.SharedPreferencesHelper
import org.smartregister.fhircore.engine.datastore.PreferenceDataStore
import org.smartregister.fhircore.engine.util.TimeZoneTypeAdapter
import org.smartregister.fhircore.engine.util.extension.getCustomJsonParser
import retrofit2.Retrofit
Expand Down Expand Up @@ -82,7 +82,7 @@ class NetworkModule {
@WithAuthorizationOkHttpClientQualifier
fun provideOkHttpClient(
tokenAuthenticator: TokenAuthenticator,
sharedPreferencesHelper: SharedPreferencesHelper,
preferenceDataStore: PreferenceDataStore,
configService: ConfigService,
) =
OkHttpClient.Builder()
Expand Down Expand Up @@ -121,7 +121,7 @@ class NetworkModule {
val request = chain.request().newBuilder()
if (accessToken.isNotEmpty()) {
request.addHeader(AUTHORIZATION, "Bearer $accessToken")
sharedPreferencesHelper.retrieveApplicationId()?.let {
preferenceDataStore.retrieveApplicationId().let {
request.addHeader(APPLICATION_ID, it)
}
}
Expand Down
Loading
Loading