Skip to content

Commit f343e26

Browse files
santosh-pingleSantosh Pingle
and
Santosh Pingle
authored
Search use case in the demo app. (#2754)
* Search use case. * Update ui edge cases. * spotless apply. * Address ui changes. * Code refactoring * address review comments. --------- Co-authored-by: Santosh Pingle <[email protected]>
1 parent 8d306be commit f343e26

File tree

4 files changed

+153
-32
lines changed

4 files changed

+153
-32
lines changed

demo/src/main/java/com/google/android/fhir/demo/PatientListFragment.kt

+31-23
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2022-2024 Google LLC
2+
* Copyright 2022-2025 Google LLC
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@ import android.view.inputmethod.InputMethodManager
2828
import androidx.activity.OnBackPressedCallback
2929
import androidx.appcompat.app.AppCompatActivity
3030
import androidx.appcompat.widget.SearchView
31+
import androidx.core.widget.addTextChangedListener
3132
import androidx.fragment.app.Fragment
3233
import androidx.lifecycle.ViewModelProvider
3334
import androidx.navigation.fragment.NavHostFragment
@@ -53,6 +54,35 @@ class PatientListFragment : Fragment() {
5354
savedInstanceState: Bundle?,
5455
): View {
5556
_binding = FragmentPatientListBinding.inflate(inflater, container, false)
57+
58+
val imm = requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
59+
60+
binding.givenNameEditText.apply {
61+
addTextChangedListener(
62+
onTextChanged = { text, _, _, _ ->
63+
patientListViewModel.setPatientGivenName(text.toString())
64+
},
65+
)
66+
setOnFocusChangeListener { view, hasFocus ->
67+
if (!hasFocus) {
68+
imm.hideSoftInputFromWindow(view.windowToken, 0)
69+
}
70+
}
71+
}
72+
73+
binding.familyNameEditText.apply {
74+
addTextChangedListener(
75+
onTextChanged = { text, _, _, _ ->
76+
patientListViewModel.setPatientFamilyName(text.toString())
77+
},
78+
)
79+
setOnFocusChangeListener { view, hasFocus ->
80+
if (!hasFocus) {
81+
imm.hideSoftInputFromWindow(view.windowToken, 0)
82+
}
83+
}
84+
}
85+
5686
return binding.root
5787
}
5888

@@ -87,27 +117,6 @@ class PatientListFragment : Fragment() {
87117
binding.patientListContainer.patientCount.text = "$it Patient(s)"
88118
}
89119

90-
searchView = binding.search
91-
searchView.setOnQueryTextListener(
92-
object : SearchView.OnQueryTextListener {
93-
override fun onQueryTextChange(newText: String): Boolean {
94-
patientListViewModel.searchPatientsByName(newText)
95-
return true
96-
}
97-
98-
override fun onQueryTextSubmit(query: String): Boolean {
99-
patientListViewModel.searchPatientsByName(query)
100-
return true
101-
}
102-
},
103-
)
104-
searchView.setOnQueryTextFocusChangeListener { view, focused ->
105-
if (!focused) {
106-
// hide soft keyboard
107-
(requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager)
108-
.hideSoftInputFromWindow(view.windowToken, 0)
109-
}
110-
}
111120
requireActivity()
112121
.onBackPressedDispatcher
113122
.addCallback(
@@ -123,7 +132,6 @@ class PatientListFragment : Fragment() {
123132
}
124133
},
125134
)
126-
127135
setHasOptionsMenu(true)
128136
}
129137

demo/src/main/java/com/google/android/fhir/demo/PatientListViewModel.kt

+75-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023-2024 Google LLC
2+
* Copyright 2023-2025 Google LLC
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -39,12 +39,11 @@ import org.hl7.fhir.r4.model.RiskAssessment
3939
*/
4040
class PatientListViewModel(application: Application, private val fhirEngine: FhirEngine) :
4141
AndroidViewModel(application) {
42-
4342
val liveSearchedPatients = MutableLiveData<List<PatientItem>>()
4443
val patientCount = MutableLiveData<Long>()
4544

4645
init {
47-
updatePatientListAndPatientCount({ getSearchResults() }, { count() })
46+
updatePatientListAndPatientCount({ getSearchResults() }, { searchedPatientCount() })
4847
}
4948

5049
fun searchPatientsByName(nameQuery: String) {
@@ -174,6 +173,79 @@ class PatientListViewModel(application: Application, private val fhirEngine: Fhi
174173
throw IllegalArgumentException("Unknown ViewModel class")
175174
}
176175
}
176+
177+
private var patientGivenName: String? = null
178+
private var patientFamilyName: String? = null
179+
180+
fun setPatientGivenName(givenName: String) {
181+
patientGivenName = givenName
182+
searchPatientsByParameter()
183+
}
184+
185+
fun setPatientFamilyName(familyName: String) {
186+
patientFamilyName = familyName
187+
searchPatientsByParameter()
188+
}
189+
190+
private fun searchPatientsByParameter() {
191+
viewModelScope.launch {
192+
liveSearchedPatients.value = searchPatients()
193+
patientCount.value = searchedPatientCount()
194+
}
195+
}
196+
197+
private suspend fun searchPatients(): List<PatientItem> {
198+
val patients =
199+
fhirEngine
200+
.search<Patient> {
201+
filter(
202+
Patient.GIVEN,
203+
{
204+
modifier = StringFilterModifier.CONTAINS
205+
this.value = patientGivenName ?: ""
206+
},
207+
)
208+
filter(
209+
Patient.FAMILY,
210+
{
211+
modifier = StringFilterModifier.CONTAINS
212+
this.value = patientFamilyName ?: ""
213+
},
214+
)
215+
sort(Patient.GIVEN, Order.ASCENDING)
216+
count = 100
217+
from = 0
218+
}
219+
.mapIndexed { index, fhirPatient -> fhirPatient.resource.toPatientItem(index + 1) }
220+
.toMutableList()
221+
222+
val risks = getRiskAssessments()
223+
patients.forEach { patient ->
224+
risks["Patient/${patient.resourceId}"]?.let {
225+
patient.risk = it.prediction?.first()?.qualitativeRisk?.coding?.first()?.code
226+
}
227+
}
228+
return patients
229+
}
230+
231+
private suspend fun searchedPatientCount(): Long {
232+
return fhirEngine.count<Patient> {
233+
filter(
234+
Patient.GIVEN,
235+
{
236+
modifier = StringFilterModifier.CONTAINS
237+
this.value = patientGivenName ?: ""
238+
},
239+
)
240+
filter(
241+
Patient.FAMILY,
242+
{
243+
modifier = StringFilterModifier.CONTAINS
244+
this.value = patientFamilyName ?: ""
245+
},
246+
)
247+
}
248+
}
177249
}
178250

179251
internal fun Patient.toPatientItem(position: Int): PatientListViewModel.PatientItem {

demo/src/main/res/layout/fragment_patient_list.xml

+44-5
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,54 @@
1313
android:focusableInTouchMode="true"
1414
android:orientation="vertical"
1515
>
16-
17-
<androidx.appcompat.widget.SearchView
18-
android:id="@+id/search"
16+
<!-- Label for Search -->
17+
<TextView
1918
android:layout_width="match_parent"
2019
android:layout_height="wrap_content"
21-
app:iconifiedByDefault="false"
22-
app:queryHint="@string/query_hint_patient_search"
20+
android:layout_marginHorizontal="20dp"
21+
android:text="@string/search_patient_by"
22+
android:textAppearance="@style/TextAppearance.Material3.LabelLarge"
2323
/>
2424

25+
<!-- Horizontal layout for Given Name and Family Name -->
26+
<LinearLayout
27+
android:layout_width="match_parent"
28+
android:layout_height="wrap_content"
29+
android:orientation="horizontal"
30+
android:layout_marginHorizontal="20dp"
31+
android:layout_marginVertical="10dp"
32+
>
33+
<com.google.android.material.textfield.TextInputLayout
34+
android:layout_width="0dp"
35+
android:layout_height="wrap_content"
36+
android:layout_weight="1"
37+
app:hintEnabled="true"
38+
android:hint="@string/given_name"
39+
>
40+
<com.google.android.material.textfield.TextInputEditText
41+
android:id="@+id/given_name_edit_text"
42+
android:layout_width="match_parent"
43+
android:layout_height="wrap_content"
44+
/>
45+
</com.google.android.material.textfield.TextInputLayout>
46+
47+
<com.google.android.material.textfield.TextInputLayout
48+
android:layout_width="0dp"
49+
android:layout_height="wrap_content"
50+
android:layout_weight="1"
51+
app:hintEnabled="true"
52+
android:hint="@string/family_name"
53+
android:layout_marginStart="10dp"
54+
>
55+
<com.google.android.material.textfield.TextInputEditText
56+
android:id="@+id/family_name_edit_text"
57+
android:layout_width="match_parent"
58+
android:layout_height="wrap_content"
59+
/>
60+
</com.google.android.material.textfield.TextInputLayout>
61+
</LinearLayout>
62+
63+
<!-- Patient List -->
2564
<include
2665
android:id="@+id/patient_list_container"
2766
layout="@layout/patient_list_view"

demo/src/main/res/values/strings.xml

+3-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525

2626
<!-- For display observations in brief, for a patient -->
2727
<string name="observation_brief_text">%1$s: %2$s\nEffective: %3$s</string>
28-
<string name="query_hint_patient_search">Find by Patient Name</string>
2928
<string
3029
name="cancel_questionnaire_message"
3130
>Are you sure you want to discard the answers?</string>
@@ -74,4 +73,7 @@
7473
<string name="last_sync_status">Last sync status: %1$s</string>
7574
<string name="last_sync_status_na">Last sync status: Not available</string>
7675
<string name="periodic_sync">Periodic sync</string>
76+
<string name="search_patient_by">Search Patient by</string>
77+
<string name="given_name">Given name</string>
78+
<string name="family_name">Family name</string>
7779
</resources>

0 commit comments

Comments
 (0)