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

Refactor demo project #2348

Merged
merged 16 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
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 @@ -27,6 +27,7 @@ import com.google.android.fhir.FhirEngine
import com.google.android.fhir.datacapture.mapping.ResourceMapper
import com.google.android.fhir.datacapture.validation.Invalid
import com.google.android.fhir.datacapture.validation.QuestionnaireResponseValidator
import com.google.android.fhir.demo.extensions.readFileFromAssets
import java.util.UUID
import kotlinx.coroutines.launch
import org.hl7.fhir.r4.model.Patient
Expand Down Expand Up @@ -91,16 +92,12 @@ class AddPatientViewModel(application: Application, private val state: SavedStat
_questionnaireJson?.let {
return it
}
_questionnaireJson = readFileFromAssets(state[AddPatientFragment.QUESTIONNAIRE_FILE_PATH_KEY]!!)
_questionnaireJson =
getApplication<Application>()
.readFileFromAssets(state[AddPatientFragment.QUESTIONNAIRE_FILE_PATH_KEY]!!)
return _questionnaireJson!!
}

private fun readFileFromAssets(filename: String): String {
return getApplication<Application>().assets.open(filename).bufferedReader().use {
it.readText()
}
}

private fun generateUuid(): String {
return UUID.randomUUID().toString()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import ca.uhn.fhir.context.FhirContext
import ca.uhn.fhir.context.FhirVersionEnum
import com.google.android.fhir.FhirEngine
import com.google.android.fhir.datacapture.mapping.ResourceMapper
import com.google.android.fhir.demo.extensions.readFileFromAssets
import com.google.android.fhir.get
import kotlinx.coroutines.launch
import org.hl7.fhir.r4.model.Patient
Expand All @@ -47,7 +48,10 @@ class EditPatientViewModel(application: Application, private val state: SavedSta
private suspend fun prepareEditPatient(): Pair<String, String> {
val patient = fhirEngine.get<Patient>(patientId)
val launchContexts = mapOf<String, Resource>("client" to patient)
val question = readFileFromAssets("new-patient-registration-paginated.json").trimIndent()
val question =
getApplication<Application>()
.readFileFromAssets("new-patient-registration-paginated.json")
.trimIndent()
val parser = FhirContext.forCached(FhirVersionEnum.R4).newJsonParser()
val questionnaire = parser.parseResource(Questionnaire::class.java, question) as Questionnaire

Expand Down Expand Up @@ -101,13 +105,11 @@ class EditPatientViewModel(application: Application, private val state: SavedSta
questionnaireJson?.let {
return it
}
questionnaireJson = readFileFromAssets(state[EditPatientFragment.QUESTIONNAIRE_FILE_PATH_KEY]!!)
questionnaireJson =
getApplication<Application>()
.readFileFromAssets(
state[EditPatientFragment.QUESTIONNAIRE_FILE_PATH_KEY]!!,
)
return questionnaireJson!!
}

private fun readFileFromAssets(filename: String): String {
return getApplication<Application>().assets.open(filename).bufferedReader().use {
it.readText()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,48 +27,45 @@ import com.google.android.fhir.demo.data.DemoFhirSyncWorker
import com.google.android.fhir.sync.PeriodicSyncConfiguration
import com.google.android.fhir.sync.RepeatInterval
import com.google.android.fhir.sync.Sync
import com.google.android.fhir.sync.SyncJobStatus
import java.time.format.DateTimeFormatter
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.launch

/** View model for [MainActivity]. */
@OptIn(InternalCoroutinesApi::class)
@OptIn(ExperimentalCoroutinesApi::class)
class MainActivityViewModel(application: Application) : AndroidViewModel(application) {
private val _lastSyncTimestampLiveData = MutableLiveData<String>()
val lastSyncTimestampLiveData: LiveData<String>
get() = _lastSyncTimestampLiveData

private val _pollState = MutableSharedFlow<SyncJobStatus>()
val pollState: Flow<SyncJobStatus>
get() = _pollState
private val _oneTimeSyncTrigger = MutableStateFlow(false)

init {
viewModelScope.launch {
Sync.periodicSync<DemoFhirSyncWorker>(
application.applicationContext,
periodicSyncConfiguration =
PeriodicSyncConfiguration(
syncConstraints = Constraints.Builder().build(),
repeat = RepeatInterval(interval = 15, timeUnit = TimeUnit.MINUTES),
),
)
.shareIn(this, SharingStarted.Eagerly, 10)
.collect { _pollState.emit(it) }
}
}
val pollState =
Sync.periodicSync<DemoFhirSyncWorker>(
context = application.applicationContext,
periodicSyncConfiguration =
PeriodicSyncConfiguration(
syncConstraints = Constraints.Builder().build(),
repeat = RepeatInterval(interval = 15, timeUnit = TimeUnit.MINUTES),
),
)
.shareIn(viewModelScope, SharingStarted.Eagerly, 10)

val oneTimeSyncState =
_oneTimeSyncTrigger
.combine(
flow = Sync.oneTimeSync<DemoFhirSyncWorker>(context = application.applicationContext),
) { _, syncJobStatus ->
syncJobStatus
}
.shareIn(viewModelScope, SharingStarted.Eagerly, 10)

fun triggerOneTimeSync() {
viewModelScope.launch {
Sync.oneTimeSync<DemoFhirSyncWorker>(getApplication())
.shareIn(this, SharingStarted.Eagerly, 10)
.collect { _pollState.emit(it) }
}
_oneTimeSyncTrigger.value = !_oneTimeSyncTrigger.value
}

/** Emits last sync time. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,15 @@ import androidx.appcompat.widget.SearchView
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.RecyclerView
import com.google.android.fhir.FhirEngine
import com.google.android.fhir.demo.PatientListViewModel.PatientListViewModelFactory
import com.google.android.fhir.demo.databinding.FragmentPatientListBinding
import com.google.android.fhir.demo.extensions.launchAndRepeatStarted
import com.google.android.fhir.sync.SyncJobStatus
import kotlin.math.roundToInt
import kotlinx.coroutines.launch
import timber.log.Timber

class PatientListFragment : Fragment() {
Expand Down Expand Up @@ -151,37 +150,39 @@ class PatientListFragment : Fragment() {
setHasOptionsMenu(true)
(activity as MainActivity).setDrawerEnabled(true)

lifecycleScope.launch {
mainActivityViewModel.pollState.collect {
Timber.d("onViewCreated: pollState Got status $it")
when (it) {
is SyncJobStatus.Started -> {
Timber.i("Sync: ${it::class.java.simpleName}")
fadeInTopBanner(it)
}
is SyncJobStatus.InProgress -> {
Timber.i("Sync: ${it::class.java.simpleName} with data $it")
fadeInTopBanner(it)
}
is SyncJobStatus.Finished -> {
Timber.i("Sync: ${it::class.java.simpleName} at ${it.timestamp}")
patientListViewModel.searchPatientsByName(searchView.query.toString().trim())
mainActivityViewModel.updateLastSyncTimestamp()
fadeOutTopBanner(it)
}
is SyncJobStatus.Failed -> {
Timber.i("Sync: ${it::class.java.simpleName} at ${it.timestamp}")
patientListViewModel.searchPatientsByName(searchView.query.toString().trim())
mainActivityViewModel.updateLastSyncTimestamp()
fadeOutTopBanner(it)
}
else -> {
Timber.i("Sync: Unknown state.")
patientListViewModel.searchPatientsByName(searchView.query.toString().trim())
mainActivityViewModel.updateLastSyncTimestamp()
fadeOutTopBanner(it)
}
}
launchAndRepeatStarted(
{ mainActivityViewModel.pollState.collect(::syncJobStatus) },
{ mainActivityViewModel.oneTimeSyncState.collect(::syncJobStatus) },
)
}

private fun syncJobStatus(it: SyncJobStatus) {
when (it) {
is SyncJobStatus.Started -> {
Timber.i("Sync: ${it::class.java.simpleName}")
fadeInTopBanner(it)
}
is SyncJobStatus.InProgress -> {
Timber.i("Sync: ${it::class.java.simpleName} with data $it")
fadeInTopBanner(it)
}
is SyncJobStatus.Finished -> {
Timber.i("Sync: ${it::class.java.simpleName} at ${it.timestamp}")
patientListViewModel.searchPatientsByName(searchView.query.toString().trim())
mainActivityViewModel.updateLastSyncTimestamp()
fadeOutTopBanner(it)
}
is SyncJobStatus.Failed -> {
Timber.i("Sync: ${it::class.java.simpleName} at ${it.timestamp}")
patientListViewModel.searchPatientsByName(searchView.query.toString().trim())
mainActivityViewModel.updateLastSyncTimestamp()
fadeOutTopBanner(it)
}
else -> {
Timber.i("Sync: Unknown state.")
patientListViewModel.searchPatientsByName(searchView.query.toString().trim())
mainActivityViewModel.updateLastSyncTimestamp()
fadeOutTopBanner(it)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import ca.uhn.fhir.context.FhirContext
import ca.uhn.fhir.context.FhirVersionEnum
import com.google.android.fhir.FhirEngine
import com.google.android.fhir.datacapture.mapping.ResourceMapper
import com.google.android.fhir.demo.extensions.readFileFromAssets
import java.math.BigDecimal
import java.util.UUID
import kotlinx.coroutines.launch
Expand Down Expand Up @@ -113,8 +114,7 @@ class ScreenerViewModel(application: Application, private val state: SavedStateH

private fun isRequiredFieldMissing(bundle: Bundle): Boolean {
bundle.entry.forEach {
val resource = it.resource
when (resource) {
when (val resource = it.resource) {
is Observation -> {
if (resource.hasValueQuantity() && !resource.valueQuantity.hasValueElement()) {
return true
Expand All @@ -132,18 +132,14 @@ class ScreenerViewModel(application: Application, private val state: SavedStateH

private fun getQuestionnaireJson(): String {
questionnaireJson?.let {
return it!!
return it
}
questionnaireJson = readFileFromAssets(state[ScreenerFragment.QUESTIONNAIRE_FILE_PATH_KEY]!!)
questionnaireJson =
getApplication<Application>()
.readFileFromAssets(state[ScreenerFragment.QUESTIONNAIRE_FILE_PATH_KEY]!!)
return questionnaireJson!!
}

private fun readFileFromAssets(filename: String): String {
return getApplication<Application>().assets.open(filename).bufferedReader().use {
it.readText()
}
}

private fun generateUuid(): String {
return UUID.randomUUID().toString()
}
Expand Down Expand Up @@ -217,8 +213,7 @@ class ScreenerViewModel(application: Application, private val state: SavedStateH
.filter { it.hasCode() && it.code.hasCoding() }
.flatMap { it.code.coding }
.map { it.code }
.filter { isSymptomPresent(it) }
.count()
.count { isSymptomPresent(it) }
return count > 0
}

Expand All @@ -234,8 +229,7 @@ class ScreenerViewModel(application: Application, private val state: SavedStateH
.filter { it.hasCode() && it.code.hasCoding() }
.flatMap { it.code.coding }
.map { it.code }
.filter { isComorbidityPresent(it) }
.count()
.count { isComorbidityPresent(it) }
return count > 0
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright 2023 Google LLC
*
* 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 com.google.android.fhir.demo.extensions

import android.content.Context

fun Context.readFileFromAssets(fileName: String): String =
assets.open(fileName).bufferedReader().use { it.readText() }
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2023 Google LLC
*
* 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 com.google.android.fhir.demo.extensions

import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.launch

fun Fragment.launchAndRepeatStarted(
hoangchungk53qx1 marked this conversation as resolved.
Show resolved Hide resolved
vararg launchBlock: suspend () -> Unit,
doAfterLaunch: (() -> Unit)? = null,
) {
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
launchBlock.forEach { launch { it.invoke() } }
doAfterLaunch?.invoke()
}
}
}