[Quest/Malawi Core] Technical Proposal: Performance improvements for large facilities#117
Conversation
-> Downloading resources using custom download manager [ApiRepository]
-> Downloading resources using FHIR SDK [org.smartregister.fhircore.engine.data.remote.resource.syncStrategy.fhir.LogicalIdSyncParamsBased]
-> Bug fixes when syncing
1. Add patients on-demand 2. Show offline first features based on configs from Binary file 3. Fix & reset cached patient ids
…l-performance-improvements-for-large-facilities
1. Add Identifier
|
@KhumboLihonga During initialization the app fetches the Below is sample code for per organization configuration for offline first enabled tingathe-saa-prod "organizationSyncConfig": {
"items": [
{
"id": "9365a7f3-dc63-4972-9dc4-c290cebb9ce2",
"name": "Chagunda Organization",
"offlineFirst": true
},
{
"id": "c97798b6-9c12-4b0a-bb66-3cd63237206f",
"name": "Salima DHO Organization",
"offlineFirst": false
},
]
}When a CHW logs in, the app checks for this configuration in this using this function fun getPerOrgSyncConfigs(): OrganizationSyncConfig? =
applicationConfiguration.value?.organizationSyncConfigthis helper function can be called anywhere to get this config fun syncConfigOfflineFirst(
configurationRegistry: ConfigurationRegistry,
sharedPreferencesHelper: SharedPreferencesHelper,
): Boolean {
val configs =
onPerOrgSyncConfigItem(configurationRegistry, sharedPreferencesHelper) ?: return false
return !configs.offlineFirst
}after a successful anthentication, the app goes calls private fun downloadWorkManager(): DownloadWorkManager = runBlocking {
syncConfigOfflineFirst(configurationRegistry, preference)
.takeIf { it }
?.let {
getIdentifiers(engine)?.let { item ->
return@runBlocking IdentifierSyncParams(item.data, tagSystem) {
runBlocking {
onSendBroadcast(broadcaster, it)
syncStrategyCacheDao.insert(it.logicalId.toEntity())
if (it.patientPositionAt == item.data.size) {
purgeListResource()
}
}
}
}
}
val logicalIds = logicalIds(syncStrategyCacheDao)
return@runBlocking if (logicalIds.isNotEmpty()) {
LogicalIdSyncParamsBased(logicalIds) {
Timber.e("${it.patientPositionAt} of ${logicalIds.size}")
runBlocking {
it.logicalId
.toEntity()
.map { catchEntity -> catchEntity.copy(shouldSync = true) }
.also { syncStrategyCacheDao.upsert(it.map { it.logicalId }) }
saveLastUpdatedTimestamp(dataStore)
onSendBroadcast(broadcaster, it)
}
}
} else {
defaultDownloadManager()
}
}If this is true, for the very first sync, the app will go straight to the else case which calls defaultDownloadManager which will call syncParams private fun syncParams(): Map<ResourceType, Map<String, String>> {
if (
syncConfigOfflineFirst(
configurationRegistry,
preference,
)
.not()
) {
return syncListenerManager.loadSyncParams()
}
return when {
hasCompletedInitialSync(preference) -> SyncParamStrategy(preference).syncParams()
else -> syncListenerManager.loadSyncParams()
}
}
The flow of code at point will go straight to fun syncParams(): Map<ResourceType, Map<String, String>> {
listOf(
ResourceType.Questionnaire,
ResourceType.StructureMap,
ResourceType.Patient,
ResourceType.List,
)
.forEach {
when (it) {
ResourceType.Patient ->
syncParams.add(
MutablePair(
it,
mutableMapOf(
"_tag1" to tagMeta,
"_tag2" to tagSystem,
"_count" to "500",
),
)
.toPair(),
)
ResourceType.List ->
syncParams.add(MutablePair(it, mutableMapOf("code" to identifierCoding)).toPair())
else -> syncParams.add(MutablePair(it, mutableMapOf("_count" to "500")).toPair())
}
}
return syncParams.toMap()
}At this point, the app will sync these resources For When sync is complete, there is a listener in the The app goes through the same path by calling AppSyncworker At this point, the app will proceed with suspend fun getIdentifiers(fhirEngine: FhirEngine): ListResourceItem? {
val listResource: ListResource = getListResource(fhirEngine) ?: return null
return ListResourceItem(
data = listResource.entry.mapNotNull { entry -> entry.item.display.toInt() }.toList(),
idPart = listResource.idPart,
)
}this function query the data class ParamSyncStatus(
val logicalId: List<String>,
val idsTotal: Int,
val patientPositionAt: Int,
) : SerializableThis will first create a bundleand then make a request passing the bundle to @Entity
data class SyncStrategyCacheEntity(
@PrimaryKey val logicalId: String,
val shouldSync: Boolean = false,
val timestamp: Long = System.currentTimeMillis(),
)when this is done Next up is querying the Here is the flow diagram |
Fixes #116
Checklist
./gradlew spotlessApplyand./gradlew spotlessCheckto check my code follows the project's style guidestrings.xmlfile