Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,10 @@ suspend fun Questionnaire.prepopulateWithComputedConfigValues(
?: questionnaireConfig.linkIds?.firstOrNull { it.type == LinkIdType.BARCODE }?.linkId)
?.let { barcodeLinkId ->
find(barcodeLinkId)?.apply {
if (hasExtension(EXTENSION_INITIAL_EXPRESSION_URL)) {
removeExtension(EXTENSION_INITIAL_EXPRESSION_URL)
}

initial =
mutableListOf(
Questionnaire.QuestionnaireItemInitialComponent()
Expand Down Expand Up @@ -285,6 +289,10 @@ suspend fun Questionnaire.prepopulateUniqueIdAssignment(
questionnaireComputedValues,
)
if (extractedId.isNotEmpty()) {
if (hasExtension(EXTENSION_INITIAL_EXPRESSION_URL)) {
removeExtension(EXTENSION_INITIAL_EXPRESSION_URL)
}

initial =
mutableListOf(
Questionnaire.QuestionnaireItemInitialComponent().setValue(StringType(extractedId)),
Expand Down
6 changes: 3 additions & 3 deletions android/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ dokkaBase = "1.9.20"
easyRulesCore = "4.1.1"
espresso-core = "3.6.1"
fhir-sdk-common = "0.1.0-alpha05-preview3-SNAPSHOT"
fhir-sdk-contrib-barcode = "0.1.0-beta3-preview7-SNAPSHOT"
fhir-sdk-contrib-locationwidget = "0.1.0-alpha01-preview2-SNAPSHOT"
fhir-sdk-data-capture = "1.3.0-preview1-SNAPSHOT"
fhir-sdk-contrib-barcode = "0.1.0-beta3-preview8-SNAPSHOT"
fhir-sdk-contrib-locationwidget = "0.1.0-alpha01-preview3-SNAPSHOT"
fhir-sdk-data-capture = "1.3.0-preview10-SNAPSHOT"
fhir-sdk-engine = "1.2.0-preview1-SNAPSHOT"
fhir-sdk-knowledge = "0.1.0-beta01-preview1-SNAPSHOT"
fhir-sdk-workflow = "0.1.0-beta01-preview2-SNAPSHOT"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,9 @@ class QuestionnaireActivity : BaseMultiLanguageActivity() {
)
}
?.let { setQuestionnaireResponse(questionnaireResponse.json()) }
?: showToast(getString(R.string.error_populating_questionnaire))
?: withContext(dispatcherProvider.main()) {
showToast(getString(R.string.error_populating_questionnaire))
}
}

launchContextResources
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1164,12 +1164,6 @@ constructor(
launchContextResources(resourceType, resourceIdentifier, actionParameters)
val launchContexts = launchContextResources.associateBy { it.resourceType.name.lowercase() }

// Populate questionnaire with initial default values
ResourceMapper.populate(
questionnaire,
launchContexts = launchContexts,
)

questionnaire.prepopulateWithComputedConfigValues(
questionnaireConfig,
actionParameters,
Expand All @@ -1194,82 +1188,118 @@ constructor(
},
)

// Populate questionnaire with latest QuestionnaireResponse
// Populate questionnaire with latest QuestionnaireResponse and initial default values
val questionnaireResponse =
if (
resourceType != null &&
!resourceIdentifier.isNullOrEmpty() &&
(questionnaireConfig.isEditable() ||
questionnaireConfig.isReadOnly() ||
questionnaireConfig.isSummary() ||
questionnaireConfig.saveDraft)
) {
defaultRepository
.searchQuestionnaireResponse(
resourceId = resourceIdentifier,
resourceType = resourceType,
questionnaireId = questionnaire.logicalId,
encounterId = questionnaireConfig.encounterId,
questionnaireResponseStatus = questionnaireConfig.questionnaireResponseStatus(),
)
?.let {
QuestionnaireResponse().apply {
/**
* Only set the ID value when [saveDraft] is set to true] this ensues that a new
* [QuestionnaireResponse] is generated when the questionnaire is submitted
*/
if (questionnaireConfig.saveDraft) {
id = it.id
}
status = it.status
item = it.item.removeUnAnsweredItems()
// Clearing the text prompts the SDK to re-process the content, which includes
// HTML
clearText()
}
}
} else {
null
}
fetchRepositoryQuestionnaireResponse(
questionnaireConfig,
resourceIdentifier,
resourceType,
questionnaire,
defaultQuestionnaire =
withContext(dispatcherProvider.default()) {
ResourceMapper.populate(
questionnaire,
launchContexts = launchContexts,
)
},
)

// Exclude the configured fields from QR
if (questionnaireResponse != null) {
val exclusionLinkIdsMap: Map<String, Boolean> =
questionnaireConfig.linkIds
?.asSequence()
?.filter { it.type == LinkIdType.PREPOPULATION_EXCLUSION }
?.associateBy { it.linkId }
?.mapValues { it.value.type == LinkIdType.PREPOPULATION_EXCLUSION } ?: emptyMap()

questionnaireResponse.item =
excludePrepopulationFields(
questionnaireResponse.item.toMutableList(),
exclusionLinkIdsMap,
questionnaireResponse.apply { item = item.removeUnAnsweredItems() }

Pair(questionnaireResponse, launchContextResources)
}

private suspend fun fetchRepositoryQuestionnaireResponse(
questionnaireConfig: QuestionnaireConfig,
resourceIdentifier: String?,
resourceType: ResourceType?,
questionnaire: Questionnaire,
defaultQuestionnaire: QuestionnaireResponse,
): QuestionnaireResponse {
if (resourceType == null || resourceIdentifier.isNullOrEmpty()) return defaultQuestionnaire

val searchQR =
questionnaireConfig
.takeIf { it.isEditable() || it.isReadOnly() || it.isSummary() || it.saveDraft }
?.let {
defaultRepository.searchQuestionnaireResponse(
resourceId = resourceIdentifier,
resourceType = resourceType,
questionnaireId = questionnaire.logicalId,
encounterId = questionnaireConfig.encounterId,
questionnaireResponseStatus = questionnaireConfig.questionnaireResponseStatus(),
)
}

if (searchQR == null) return defaultQuestionnaire

val exclusionLinkIdsMap: Map<String, Boolean> =
questionnaireConfig.linkIds
?.asSequence()
?.filter { it.type == LinkIdType.PREPOPULATION_EXCLUSION }
?.associateBy { it.linkId }
?.mapValues { it.value.type == LinkIdType.PREPOPULATION_EXCLUSION } ?: emptyMap()

return QuestionnaireResponse().apply {
/**
* Only set the ID value when [saveDraft] is set to true] this ensues that a new
* [QuestionnaireResponse] is generated when the questionnaire is submitted
*/
if (questionnaireConfig.saveDraft) {
id = searchQR.id
}
status = searchQR.status

Pair(questionnaireResponse, launchContextResources)
// Exclude the configured fields from QR
item =
excludePreviouslyAnsweredFields(
searchQR.item,
defaultQuestionnaire.item,
exclusionLinkIdsMap,
)

// Clearing the text prompts the SDK to re-process the content, which includes
// HTML
clearText()
}
}

fun excludePrepopulationFields(
items: MutableList<QuestionnaireResponseItemComponent>,
fun excludePreviouslyAnsweredFields(
previouslyAnsweredItems: MutableList<QuestionnaireResponseItemComponent>,
newlyPopulatedItems: List<QuestionnaireResponseItemComponent>,
exclusionMap: Map<String, Boolean>,
): MutableList<QuestionnaireResponseItemComponent> {
val stack = LinkedList<MutableList<QuestionnaireResponseItemComponent>>()
stack.push(items)
while (stack.isNotEmpty()) {
val currentItems = stack.pop()
val iterator = currentItems.iterator()
while (iterator.hasNext()) {
val item = iterator.next()
if (exclusionMap.containsKey(item.linkId)) {
iterator.remove()
} else if (item.item.isNotEmpty()) {
stack.push(item.item)
val prevStack =
LinkedList<MutableList<QuestionnaireResponseItemComponent>>().apply {
push(previouslyAnsweredItems)
}
val newlyStack =
LinkedList<List<QuestionnaireResponseItemComponent>>().apply { push(newlyPopulatedItems) }

while (prevStack.isNotEmpty()) {
val prevAnswerItemsIterator = prevStack.pop().listIterator()
val newItemsIterator: Iterator<QuestionnaireResponseItemComponent> =
(if (newlyStack.isNotEmpty()) newlyStack.pop() else emptyList()).iterator()

while (prevAnswerItemsIterator.hasNext()) {
val item = prevAnswerItemsIterator.next()
val newPopulatedItem = if (newItemsIterator.hasNext()) newItemsIterator.next() else null

when {
exclusionMap.containsKey(item.linkId) && newPopulatedItem?.linkId == item.linkId -> {
prevAnswerItemsIterator.set(newPopulatedItem)
}
exclusionMap.containsKey(item.linkId) -> {
prevAnswerItemsIterator.remove()
}
else -> {
prevStack.push(item.item)
newPopulatedItem?.let { newlyStack.push(it.item) }
}
}
}
}
return items
return previouslyAnsweredItems
}

private fun List<QuestionnaireResponseItemComponent>.removeUnAnsweredItems():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,20 @@ import com.google.android.fhir.datacapture.extensions.itemControlCode
import com.google.android.fhir.datacapture.extensions.tryUnwrapContext
import com.google.android.fhir.datacapture.views.HeaderView
import com.google.android.fhir.datacapture.views.QuestionnaireViewItem
import com.google.android.fhir.datacapture.views.factories.QuestionnaireItemViewHolderDelegate
import com.google.android.fhir.datacapture.views.factories.QuestionnaireItemViewHolderFactory
import com.google.android.fhir.datacapture.views.factories.QuestionnaireItemAndroidViewHolderDelegate
import com.google.android.fhir.datacapture.views.factories.QuestionnaireItemAndroidViewHolderFactory
import com.google.android.material.textfield.TextInputEditText
import kotlinx.coroutines.launch
import org.hl7.fhir.r4.model.Questionnaire
import org.hl7.fhir.r4.model.QuestionnaireResponse
import org.hl7.fhir.r4.model.StringType

object PasswordViewHolderFactory :
QuestionnaireItemViewHolderFactory(
QuestionnaireItemAndroidViewHolderFactory(
org.smartregister.fhircore.quest.R.layout.password_view,
) {
override fun getQuestionnaireItemViewHolderDelegate() =
object : QuestionnaireItemViewHolderDelegate {
object : QuestionnaireItemAndroidViewHolderDelegate {
override lateinit var questionnaireViewItem: QuestionnaireViewItem

private lateinit var header: HeaderView
Expand All @@ -58,7 +58,7 @@ object PasswordViewHolderFactory :

passwordEditText =
itemView
.findViewById<TextInputEditText?>(
.findViewById<TextInputEditText>(
org.smartregister.fhircore.quest.R.id.password_edit_text,
)
.apply {
Expand Down
Loading
Loading