From 3666671a94606737f7d7162da037c512a9c0e75b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=E2=89=A1ZRS?= <12814349+LZRS@users.noreply.github.com> Date: Wed, 2 Oct 2024 13:05:13 +0300 Subject: [PATCH] Wrap fhirengine methods in viewmodel to use io dispatcher --- android/buildSrc/src/main/kotlin/Deps.kt | 4 +- android/engine/build.gradle.kts | 3 +- .../questionnaire/QuestionnaireViewModel.kt | 306 ++++++++++-------- .../fhircore/quest/QuestGlideModule.kt | 22 ++ .../profile/tranfer/TransferOutViewModel.kt | 4 +- 5 files changed, 197 insertions(+), 142 deletions(-) create mode 100644 android/quest/src/main/java/org/smartregister/fhircore/quest/QuestGlideModule.kt diff --git a/android/buildSrc/src/main/kotlin/Deps.kt b/android/buildSrc/src/main/kotlin/Deps.kt index b7441d0dadf..06bf0c8746c 100644 --- a/android/buildSrc/src/main/kotlin/Deps.kt +++ b/android/buildSrc/src/main/kotlin/Deps.kt @@ -5,9 +5,9 @@ object Deps { object sdk_versions { - const val compile_sdk = 34 + const val compile_sdk = 35 const val min_sdk = 26 - const val target_sdk = 34 + const val target_sdk = 35 } const val build_tool_version = "30.0.3" diff --git a/android/engine/build.gradle.kts b/android/engine/build.gradle.kts index d3ea5dcbf92..c8e3a812d41 100644 --- a/android/engine/build.gradle.kts +++ b/android/engine/build.gradle.kts @@ -112,7 +112,8 @@ dependencies { implementation("androidx.cardview:cardview:1.0.0") implementation("joda-time:joda-time:2.10.14") implementation("androidx.paging:paging-runtime-ktx:3.3.0") - implementation("com.github.bumptech.glide:glide:4.16.0") + api("com.github.bumptech.glide:glide:4.16.0") + kapt("com.github.bumptech.glide:compiler:4.16.0") implementation("id.zelory:compressor:3.0.1") implementation(group = "javax.xml.stream", name = "stax-api", version = "1.0-2") diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/QuestionnaireViewModel.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/QuestionnaireViewModel.kt index 979343bba83..2d9008aecbb 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/QuestionnaireViewModel.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/ui/questionnaire/QuestionnaireViewModel.kt @@ -426,18 +426,22 @@ constructor( suspend fun carePlanAndPatientMetaExtraction(source: Resource) { try { - /** Get a FHIR [Resource] in the local storage. */ - var resource = fhirEngine.get(source.resourceType, source.id) - /** Increment [Resource.meta] versionId of [source]. */ - resource.meta.versionId?.toInt()?.plus(1)?.let { - /** Append passed [Resource.meta] to the [source]. */ - resource.addTags(source.meta.tag) - /** Assign [Resource.meta] versionId of [source]. */ - resource = resource.copy().apply { meta.versionId = "$it" } - /** Delete a FHIR [source] in the local storage. */ - fhirEngine.delete(resource.resourceType, resource.id) - /** Recreate a FHIR [source] in the local storage. */ - fhirEngine.create(resource) + withContext(dispatcherProvider.io()) { + /** Get a FHIR [Resource] in the local storage. */ + var resource = fhirEngine.get(source.resourceType, source.id) + /** Increment [Resource.meta] versionId of [source]. */ + resource.meta.versionId?.toInt()?.plus(1)?.let { + /** Append passed [Resource.meta] to the [source]. */ + resource.addTags(source.meta.tag) + /** Assign [Resource.meta] versionId of [source]. */ + resource = resource.copy().apply { meta.versionId = "$it" } + fhirEngine.withTransaction { + /** Delete a FHIR [source] in the local storage. */ + fhirEngine.delete(resource.resourceType, resource.id) + /** Recreate a FHIR [source] in the local storage. */ + fhirEngine.create(resource) + } + } } } catch (e: Exception) { Timber.e(e) @@ -546,111 +550,130 @@ constructor( } private suspend fun loadScheduledAppointments(patientId: String): Iterable { - return fhirEngine - .search { - filter( - Appointment.STATUS, - { value = of(Appointment.AppointmentStatus.BOOKED.toCode()) }, - { value = of(Appointment.AppointmentStatus.WAITLIST.toCode()) }, - operation = Operation.OR, - ) - } - .map { it.resource } - // filter on patient subject - .filter { appointment -> - appointment.participant.any { - it.hasActor() && - it.actor.referenceElement.resourceType == ResourceType.Patient.name && - it.actor.referenceElement.idPart == patientId - } - } - .filter { - (it.status == Appointment.AppointmentStatus.BOOKED || - it.status == Appointment.AppointmentStatus.WAITLIST) && - it.hasStart() && - it.start.after( - Date.from( - LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant().minusSeconds(30), - ), - ) - } - } - - private suspend fun getLastActiveCarePlan(patientId: String): CarePlan? { - val carePlans = + return withContext(dispatcherProvider.io()) { fhirEngine - .search { - filterByResourceTypeId(CarePlan.SUBJECT, ResourceType.Patient, patientId) + .search { filter( - CarePlan.STATUS, - { value = of(CarePlan.CarePlanStatus.COMPLETED.toCoding()) }, + Appointment.STATUS, + { value = of(Appointment.AppointmentStatus.BOOKED.toCode()) }, + { value = of(Appointment.AppointmentStatus.WAITLIST.toCode()) }, operation = Operation.OR, ) } .map { it.resource } + // filter on patient subject + .filter { appointment -> + appointment.participant.any { + it.hasActor() && + it.actor.referenceElement.resourceType == ResourceType.Patient.name && + it.actor.referenceElement.idPart == patientId + } + } + .filter { + (it.status == Appointment.AppointmentStatus.BOOKED || + it.status == Appointment.AppointmentStatus.WAITLIST) && + it.hasStart() && + it.start.after( + Date.from( + LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant().minusSeconds(30), + ), + ) + } + } + } + + private suspend fun getLastActiveCarePlan(patientId: String): CarePlan? { + val carePlans = + withContext(dispatcherProvider.io()) { + fhirEngine + .search { + filterByResourceTypeId(CarePlan.SUBJECT, ResourceType.Patient, patientId) + filter( + CarePlan.STATUS, + { value = of(CarePlan.CarePlanStatus.COMPLETED.toCoding()) }, + operation = Operation.OR, + ) + } + .map { it.resource } + } return carePlans.sortedByDescending { it.meta.lastUpdated }.firstOrNull() } private suspend fun getActiveListResource(patient: String): ListResource? { val list = - fhirEngine - .search { - filter(ListResource.SUBJECT, { value = "Patient/$patient" }) - filter(ListResource.STATUS, { value = of(ListResource.ListStatus.CURRENT.toCode()) }) - sort(ListResource.TITLE, Order.DESCENDING) - count = 1 - from = 0 - } - .map { it.resource } + withContext(dispatcherProvider.io()) { + fhirEngine + .search { + filter(ListResource.SUBJECT, { value = "Patient/$patient" }) + filter(ListResource.STATUS, { value = of(ListResource.ListStatus.CURRENT.toCode()) }) + sort(ListResource.TITLE, Order.DESCENDING) + count = 1 + from = 0 + } + .map { it.resource } + } return list.firstOrNull() } suspend fun loadLatestAppointmentWithNoStartDate(patientId: String): Appointment? { - return fhirEngine - .search { - filter(Appointment.STATUS, { value = of(Appointment.AppointmentStatus.BOOKED.toCode()) }) - } - .map { it.resource } - // filter on patient subject - .filter { appointment -> - appointment.participant.any { - it.hasActor() && - it.actor.referenceElement.resourceType == ResourceType.Patient.name && - it.actor.referenceElement.idPart == patientId + return withContext(dispatcherProvider.io()) { + fhirEngine + .search { + filter(Appointment.STATUS, { value = of(Appointment.AppointmentStatus.BOOKED.toCode()) }) } - } - .filterNot { it.hasStart() && it.status == Appointment.AppointmentStatus.BOOKED } - .sortedBy { it.created } - .firstOrNull() + .map { it.resource } + // filter on patient subject + .filter { appointment -> + appointment.participant.any { + it.hasActor() && + it.actor.referenceElement.resourceType == ResourceType.Patient.name && + it.actor.referenceElement.idPart == patientId + } + } + .filterNot { it.hasStart() && it.status == Appointment.AppointmentStatus.BOOKED } + .sortedBy { it.created } + .firstOrNull() + } } suspend fun loadTracing(patientId: String): List { val tasks = - fhirEngine - .search { - filter(Task.SUBJECT, { value = "Patient/$patientId" }) - filter( - TokenClientParam("code"), - { - value = - of(CodeableConcept().addCoding(Coding("http://snomed.info/sct", "225368008", null))) - }, - ) - filter( - Task.STATUS, - { value = of(Task.TaskStatus.READY.toCode()) }, - { value = of(Task.TaskStatus.INPROGRESS.toCode()) }, - operation = Operation.OR, - ) - filter( - Task.PERIOD, - { - value = of(DateTimeType.now()) - prefix = ParamPrefixEnum.GREATERTHAN - }, - ) - } - .map { it.resource } + withContext(dispatcherProvider.io()) { + fhirEngine + .search { + filter(Task.SUBJECT, { value = "Patient/$patientId" }) + filter( + TokenClientParam("code"), + { + value = + of( + CodeableConcept() + .addCoding( + Coding( + "http://snomed.info/sct", + "225368008", + null, + ), + ), + ) + }, + ) + filter( + Task.STATUS, + { value = of(Task.TaskStatus.READY.toCode()) }, + { value = of(Task.TaskStatus.INPROGRESS.toCode()) }, + operation = Operation.OR, + ) + filter( + Task.PERIOD, + { + value = of(DateTimeType.now()) + prefix = ParamPrefixEnum.GREATERTHAN + }, + ) + } + .map { it.resource } + } return tasks.filter { it.status in arrayOf(TaskStatus.READY, TaskStatus.INPROGRESS) } } @@ -687,52 +710,56 @@ constructor( val resourcesList = getPopulationResourcesFromIntent(intent).toMutableList() intent.getStringExtra(QuestionnaireActivity.QUESTIONNAIRE_ARG_PATIENT_KEY)?.let { patientId -> - fhirEngine.withTransaction { - loadPatient(patientId)?.apply { resourcesList.add(this) } - ?: defaultRepository.loadResource(patientId)?.apply { resourcesList.add(this) } - - val bundleIndex = resourcesList.indexOfFirst { x -> x is Bundle } - if (bundleIndex != -1) { - val currentBundle = resourcesList[bundleIndex] as Bundle - - if (TracingHelpers.requireTracingTasks(questionnaireConfig.identifier)) { - val bundle = Bundle() - bundle.id = TracingHelpers.tracingBundleId - val tasks = loadTracing(patientId) - tasks.forEach { bundle.addEntry(Bundle.BundleEntryComponent().setResource(it)) } - - val list = getActiveListResource(patientId) - if (list != null) { - bundle.addEntry(Bundle.BundleEntryComponent().setResource(list)) + withContext(dispatcherProvider.io()) { + fhirEngine.withTransaction { + loadPatient(patientId)?.apply { resourcesList.add(this) } + ?: defaultRepository.loadResource(patientId)?.apply { resourcesList.add(this) } + + val bundleIndex = resourcesList.indexOfFirst { x -> x is Bundle } + if (bundleIndex != -1) { + val currentBundle = resourcesList[bundleIndex] as Bundle + + if (TracingHelpers.requireTracingTasks(questionnaireConfig.identifier)) { + val bundle = Bundle() + bundle.id = TracingHelpers.tracingBundleId + val tasks = loadTracing(patientId) + tasks.forEach { bundle.addEntry(Bundle.BundleEntryComponent().setResource(it)) } + + val list = getActiveListResource(patientId) + if (list != null) { + bundle.addEntry(Bundle.BundleEntryComponent().setResource(list)) + } + + currentBundle.addEntry( + Bundle.BundleEntryComponent().setResource(bundle).apply { + id = TracingHelpers.tracingBundleId + }, + ) } - currentBundle.addEntry( - Bundle.BundleEntryComponent().setResource(bundle).apply { - id = TracingHelpers.tracingBundleId - }, - ) - } + val appointmentToPopulate = loadLatestAppointmentWithNoStartDate(patientId) + if (appointmentToPopulate != null) { + currentBundle.addEntry( + Bundle.BundleEntryComponent().setResource(appointmentToPopulate), + ) + } + // Add appointments that may need to be closed + loadScheduledAppointments(patientId).forEach { + currentBundle.addEntry(Bundle.BundleEntryComponent().setResource(it)) + } - val appointmentToPopulate = loadLatestAppointmentWithNoStartDate(patientId) - if (appointmentToPopulate != null) { - currentBundle.addEntry(Bundle.BundleEntryComponent().setResource(appointmentToPopulate)) - } - // Add appointments that may need to be closed - loadScheduledAppointments(patientId).forEach { - currentBundle.addEntry(Bundle.BundleEntryComponent().setResource(it)) - } + val lastCarePlan = getLastActiveCarePlan(patientId) + if (lastCarePlan != null) { + currentBundle.addEntry(Bundle.BundleEntryComponent().setResource(lastCarePlan)) + } - val lastCarePlan = getLastActiveCarePlan(patientId) - if (lastCarePlan != null) { - currentBundle.addEntry(Bundle.BundleEntryComponent().setResource(lastCarePlan)) + resourcesList[bundleIndex] = currentBundle } - resourcesList[bundleIndex] = currentBundle - } - - // for situations where patient RelatedPersons not passed through intent extras - if (resourcesList.none { it.resourceType == ResourceType.RelatedPerson }) { - loadRelatedPerson(patientId)?.forEach { resourcesList.add(it) } + // for situations where patient RelatedPersons not passed through intent extras + if (resourcesList.none { it.resourceType == ResourceType.RelatedPerson }) { + loadRelatedPerson(patientId)?.forEach { resourcesList.add(it) } + } } } } @@ -746,7 +773,10 @@ constructor( val populationResourcesList = getPopulationResources(intent, questionnaire.logicalId) val populationResourceTypeResourceMap = populationResourcesList.associateBy { it.resourceType.name.lowercase() } - val questResponse = ResourceMapper.populate(questionnaire, populationResourceTypeResourceMap) + val questResponse = + withContext(dispatcherProvider.default()) { + ResourceMapper.populate(questionnaire, populationResourceTypeResourceMap) + } questResponse.contained = populationResourcesList.toList() questResponse.questionnaire = "${questionnaire.resourceType}/${questionnaire.logicalId}" return questResponse diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/QuestGlideModule.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/QuestGlideModule.kt new file mode 100644 index 00000000000..538540908e2 --- /dev/null +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/QuestGlideModule.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2021 Ona Systems, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.smartregister.fhircore.quest + +import com.bumptech.glide.annotation.GlideModule +import com.bumptech.glide.module.AppGlideModule + +@GlideModule class QuestGlideModule : AppGlideModule() diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/patient/profile/tranfer/TransferOutViewModel.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/patient/profile/tranfer/TransferOutViewModel.kt index 2585ee75f05..a5772179874 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/patient/profile/tranfer/TransferOutViewModel.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/patient/profile/tranfer/TransferOutViewModel.kt @@ -33,6 +33,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.hl7.fhir.r4.model.Patient import org.hl7.fhir.r4.model.Questionnaire import org.hl7.fhir.r4.model.QuestionnaireResponse @@ -155,7 +156,8 @@ constructor( } suspend fun generateResponse(questionnaire: Questionnaire): QuestionnaireResponse { - val questResponse = ResourceMapper.populate(questionnaire, mapOf()) + val questResponse = + withContext(Dispatchers.Default) { ResourceMapper.populate(questionnaire, mapOf()) } return questResponse }