-
Notifications
You must be signed in to change notification settings - Fork 296
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
Async getQuestionnairePages and Add progress bar to the 'Next' button #2645
base: master
Are you sure you want to change the base?
Changes from 9 commits
5ac8323
0ae5301
e05493e
e3a55b6
cea5a07
34121d6
db8ffbc
64deedb
4af8cc9
68445e0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,7 @@ | |
package com.google.android.fhir.datacapture | ||
|
||
import android.app.Application | ||
import android.content.Context | ||
import android.net.Uri | ||
import androidx.annotation.VisibleForTesting | ||
import androidx.lifecycle.AndroidViewModel | ||
|
@@ -35,9 +36,12 @@ import com.google.android.fhir.datacapture.extensions.cqfExpression | |
import com.google.android.fhir.datacapture.extensions.createQuestionnaireResponseItem | ||
import com.google.android.fhir.datacapture.extensions.entryMode | ||
import com.google.android.fhir.datacapture.extensions.filterByCodeInNameExtension | ||
import com.google.android.fhir.datacapture.extensions.flattened | ||
import com.google.android.fhir.datacapture.extensions.forEachItemPair | ||
import com.google.android.fhir.datacapture.extensions.hasDifferentAnswerSet | ||
import com.google.android.fhir.datacapture.extensions.isDisplayItem | ||
import com.google.android.fhir.datacapture.extensions.isEnableWhenReferencedBy | ||
import com.google.android.fhir.datacapture.extensions.isExpressionReferencedBy | ||
import com.google.android.fhir.datacapture.extensions.isHelpCode | ||
import com.google.android.fhir.datacapture.extensions.isHidden | ||
import com.google.android.fhir.datacapture.extensions.isPaginated | ||
|
@@ -64,6 +68,7 @@ import com.google.android.fhir.datacapture.validation.Valid | |
import com.google.android.fhir.datacapture.validation.ValidationResult | ||
import com.google.android.fhir.datacapture.views.QuestionTextConfiguration | ||
import com.google.android.fhir.datacapture.views.QuestionnaireViewItem | ||
import kotlinx.coroutines.Dispatchers | ||
import kotlinx.coroutines.flow.MutableStateFlow | ||
import kotlinx.coroutines.flow.SharingStarted | ||
import kotlinx.coroutines.flow.StateFlow | ||
|
@@ -324,6 +329,8 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat | |
*/ | ||
private val draftAnswerMap = mutableMapOf<QuestionnaireResponseItemComponent, Any>() | ||
|
||
private val isLoadingNextPage = MutableStateFlow(true) | ||
|
||
/** | ||
* Callback function to update the view model after the answer(s) to a question have been changed. | ||
* This is passed to the [QuestionnaireViewItem] in its constructor so that it can invoke this | ||
|
@@ -378,10 +385,28 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat | |
) | ||
} | ||
modifiedQuestionnaireResponseItemSet.add(questionnaireResponseItem) | ||
viewModelScope.launch(Dispatchers.IO) { | ||
var isReferenced = false | ||
kotlin.run { | ||
isReferenced = questionnaireItem.isExpressionReferencedBy(questionnaire) | ||
if (isReferenced) return@run | ||
|
||
questionnaire.item.flattened().forEach { item -> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a way to avoid this for loop? If the questionnaire is long? Also is there any value in avoiding it? Maybe we can just time this part and see how long it takes given a large questionnaire with many expressions and enable whens. If it takes < x% of the time then we don't really need to worry about it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. go through only:
consider to use dependency graph, in the future. |
||
isReferenced = questionnaireItem.isEnableWhenReferencedBy(item) | ||
if (isReferenced) return@run | ||
|
||
isReferenced = questionnaireItem.isExpressionReferencedBy(item) | ||
if (isReferenced) return@run | ||
} | ||
} | ||
if (isReferenced) isLoadingNextPage.value = true | ||
Comment on lines
+389
to
+402
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this whole bit should be calculated once for the questionnaire instead of being calculated each time when the answer changes? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. okay |
||
modificationCount.update { it + 1 } | ||
|
||
updateAnswerWithAffectedCalculatedExpression(questionnaireItem) | ||
|
||
modificationCount.update { it + 1 } | ||
updateAnswerWithAffectedCalculatedExpression(questionnaireItem) | ||
pages = getQuestionnairePages() | ||
isLoadingNextPage.value = false | ||
modificationCount.update { it + 1 } | ||
} | ||
Comment on lines
+388
to
+409
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should investigate if parallel coroutine here could cause any issues incase of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. will investigate |
||
} | ||
|
||
private val expressionEvaluator: ExpressionEvaluator = | ||
|
@@ -582,6 +607,8 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat | |
.onEach { | ||
if (it.index == 0) { | ||
initializeCalculatedExpressions() | ||
pages = getQuestionnairePages() | ||
isLoadingNextPage.value = false | ||
modificationCount.update { count -> count + 1 } | ||
} | ||
} | ||
|
@@ -708,7 +735,6 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat | |
// display all items. | ||
val questionnaireItemViewItems = | ||
if (!isReadOnly && !isInReviewModeFlow.value && questionnaire.isPaginated) { | ||
pages = getQuestionnairePages() | ||
if (currentPageIndexFlow.value == null) { | ||
currentPageIndexFlow.value = pages!!.first { it.enabled && !it.hidden }.index | ||
} | ||
|
@@ -728,15 +754,16 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat | |
navSubmit = | ||
if (showSubmitButton) { | ||
QuestionnaireNavigationViewUIState.Enabled( | ||
submitButtonText, | ||
onSubmitButtonClickListener, | ||
labelText = submitButtonText, | ||
onClickAction = onSubmitButtonClickListener, | ||
) | ||
} else { | ||
QuestionnaireNavigationViewUIState.Hidden | ||
}, | ||
navCancel = | ||
if (!isReadOnly && shouldShowCancelButton) { | ||
QuestionnaireNavigationViewUIState.Enabled( | ||
labelText = (getApplication() as Context).getString(R.string.cancel_questionnaire), | ||
onClickAction = onCancelButtonClickListener, | ||
) | ||
} else { | ||
|
@@ -796,16 +823,31 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat | |
navPrevious = | ||
when { | ||
questionnairePagination.isPaginated && questionnairePagination.hasPreviousPage -> { | ||
QuestionnaireNavigationViewUIState.Enabled { goToPreviousPage() } | ||
QuestionnaireNavigationViewUIState.Enabled( | ||
labelText = | ||
(getApplication() as Context).getString(R.string.button_pagination_previous), | ||
onClickAction = { goToPreviousPage() }, | ||
) | ||
} | ||
else -> { | ||
QuestionnaireNavigationViewUIState.Hidden | ||
} | ||
}, | ||
navNext = | ||
when { | ||
questionnairePagination.isPaginated && | ||
questionnairePagination.hasNextPage && | ||
isLoadingNextPage.value -> { | ||
QuestionnaireNavigationViewUIState.Enabled( | ||
labelText = null, | ||
) | ||
} | ||
questionnairePagination.isPaginated && questionnairePagination.hasNextPage -> { | ||
QuestionnaireNavigationViewUIState.Enabled { goToNextPage() } | ||
QuestionnaireNavigationViewUIState.Enabled( | ||
labelText = | ||
(getApplication() as Context).getString(R.string.button_pagination_next), | ||
onClickAction = { goToNextPage() }, | ||
) | ||
} | ||
else -> { | ||
QuestionnaireNavigationViewUIState.Hidden | ||
|
@@ -814,24 +856,39 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat | |
navSubmit = | ||
if (showSubmitButton) { | ||
QuestionnaireNavigationViewUIState.Enabled( | ||
submitButtonText, | ||
onSubmitButtonClickListener, | ||
labelText = submitButtonText, | ||
onClickAction = onSubmitButtonClickListener, | ||
) | ||
} else { | ||
QuestionnaireNavigationViewUIState.Hidden | ||
}, | ||
navReview = | ||
if (showReviewButton) { | ||
QuestionnaireNavigationViewUIState.Enabled { setReviewMode(true) } | ||
QuestionnaireNavigationViewUIState.Enabled( | ||
labelText = (getApplication() as Context).getString(R.string.button_review), | ||
onClickAction = { setReviewMode(true) }, | ||
) | ||
} else { | ||
QuestionnaireNavigationViewUIState.Hidden | ||
}, | ||
navCancel = | ||
if (showCancelButton) { | ||
QuestionnaireNavigationViewUIState.Enabled(onClickAction = onCancelButtonClickListener) | ||
QuestionnaireNavigationViewUIState.Enabled( | ||
labelText = (getApplication() as Context).getString(R.string.cancel_questionnaire), | ||
onClickAction = onCancelButtonClickListener, | ||
) | ||
} else { | ||
QuestionnaireNavigationViewUIState.Hidden | ||
}, | ||
navNextProgressBar = | ||
when { | ||
questionnairePagination.isPaginated && isLoadingNextPage.value -> { | ||
QuestionnaireNavigationViewUIState.Enabled() | ||
} | ||
else -> { | ||
QuestionnaireNavigationViewUIState.Hidden | ||
} | ||
}, | ||
) | ||
val bottomNavigationItems = | ||
listOf(QuestionnaireAdapterItem.Navigation(bottomNavigationUiViewState)) | ||
|
@@ -1082,7 +1139,7 @@ internal class QuestionnaireViewModel(application: Application, state: SavedStat | |
* Gets a list of [QuestionnairePage]s for a paginated questionnaire, or `null` if the | ||
* questionnaire is not paginated. | ||
*/ | ||
private suspend fun getQuestionnairePages(): List<QuestionnairePage>? = | ||
internal suspend fun getQuestionnairePages(): List<QuestionnairePage>? = | ||
if (questionnaire.isPaginated) { | ||
questionnaire.item.zip(questionnaireResponse.item).mapIndexed { | ||
index, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jingtang10 what do you think of this current code?
Initially I did add a new UI state for loading, but changed my mind, then implement it this current way.
My reasoning:
In the future, I'm thinking there might be other types of UI other than button that might use this UI state, it makes sense to have Loading state for button, but might not be for other types of UI.
Or am I just overthinking it(?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Jing:
Use loading state to update both next button and next button's circular progress indicator together