diff --git a/reference/build.gradle.kts b/reference/build.gradle.kts
index be26c232eb..9284053742 100644
--- a/reference/build.gradle.kts
+++ b/reference/build.gradle.kts
@@ -2,6 +2,7 @@ plugins {
id(Plugins.BuildPlugins.application)
id(Plugins.BuildPlugins.kotlinAndroid)
id(Plugins.BuildPlugins.kotlinKapt)
+ id(Plugins.BuildPlugins.navSafeArgs)
}
android {
diff --git a/reference/src/main/AndroidManifest.xml b/reference/src/main/AndroidManifest.xml
index c4c8dbbc61..36881c7cd0 100644
--- a/reference/src/main/AndroidManifest.xml
+++ b/reference/src/main/AndroidManifest.xml
@@ -30,7 +30,7 @@
android:theme="@style/AppTheme"
>
@@ -40,17 +40,6 @@
-
-
-
diff --git a/reference/src/main/java/com/google/android/fhir/reference/MainActivity.kt b/reference/src/main/java/com/google/android/fhir/reference/MainActivity.kt
new file mode 100644
index 0000000000..1ec37cb6e0
--- /dev/null
+++ b/reference/src/main/java/com/google/android/fhir/reference/MainActivity.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2020 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.reference
+
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import androidx.appcompat.widget.Toolbar
+import androidx.work.Constraints
+import com.google.android.fhir.reference.data.FhirPeriodicSyncWorker
+import com.google.android.fhir.sync.PeriodicSyncConfiguration
+import com.google.android.fhir.sync.RepeatInterval
+import com.google.android.fhir.sync.Sync
+import java.util.concurrent.TimeUnit
+
+const val MAX_RESOURCE_COUNT = 20
+
+class MainActivity : AppCompatActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+
+ val toolbar = findViewById(R.id.toolbar)
+ setSupportActionBar(toolbar)
+ toolbar.title = title
+
+ Sync.periodicSync(
+ this,
+ PeriodicSyncConfiguration(
+ syncConstraints = Constraints.Builder().build(),
+ repeat = RepeatInterval(interval = 1, timeUnit = TimeUnit.MINUTES)
+ )
+ )
+ }
+}
diff --git a/reference/src/main/java/com/google/android/fhir/reference/PatientDetailActivity.kt b/reference/src/main/java/com/google/android/fhir/reference/PatientDetailActivity.kt
deleted file mode 100644
index f5f021f944..0000000000
--- a/reference/src/main/java/com/google/android/fhir/reference/PatientDetailActivity.kt
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright 2020 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.reference
-
-import android.content.Intent
-import android.os.Bundle
-import android.view.MenuItem
-import androidx.appcompat.app.AppCompatActivity
-
-/** An activity representing a single Patient detail screen. */
-class PatientDetailActivity : AppCompatActivity() {
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_patient_detail)
- setSupportActionBar(findViewById(R.id.detail_toolbar))
-
- // Show the Up button in the action bar.
- supportActionBar?.setDisplayHomeAsUpEnabled(true)
-
- if (savedInstanceState == null) {
- // Create the detail fragment and add it to the activity
- // using a fragment transaction.
- val fragment =
- PatientDetailFragment().apply {
- arguments =
- Bundle().apply {
- putString(
- PatientDetailFragment.ARG_ITEM_ID,
- intent.getStringExtra(PatientDetailFragment.ARG_ITEM_ID)
- )
- }
- }
-
- supportFragmentManager
- .beginTransaction()
- .add(R.id.patient_detail_container, fragment)
- .commit()
- }
- }
-
- override fun onOptionsItemSelected(item: MenuItem) =
- when (item.itemId) {
- android.R.id.home -> {
- navigateUpTo(Intent(this, PatientListActivity::class.java))
- true
- }
- else -> super.onOptionsItemSelected(item)
- }
-}
diff --git a/reference/src/main/java/com/google/android/fhir/reference/PatientDetailFragment.kt b/reference/src/main/java/com/google/android/fhir/reference/PatientDetailFragment.kt
deleted file mode 100644
index 4cceb83fa2..0000000000
--- a/reference/src/main/java/com/google/android/fhir/reference/PatientDetailFragment.kt
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2020 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.reference
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.TextView
-import androidx.core.text.HtmlCompat
-import androidx.fragment.app.Fragment
-import androidx.recyclerview.widget.RecyclerView
-
-/**
- * A fragment representing a single Patient detail screen. This fragment is contained in a
- * [PatientDetailActivity].
- */
-class PatientDetailFragment : Fragment() {
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- val rootView = inflater.inflate(R.layout.patient_detail, container, false)
-
- val recyclerView: RecyclerView = rootView.findViewById(R.id.observation_list)
- val adapter = ObservationItemRecyclerViewAdapter()
- recyclerView.adapter = adapter
-
- var patient: PatientListViewModel.PatientItem? = null
- setupPatientData(rootView, patient)
-
- return rootView
- }
-
- private fun setupPatientData(view: View, patient: PatientListViewModel.PatientItem?) {
- if (patient != null) {
- view.findViewById(R.id.patient_detail).text =
- HtmlCompat.fromHtml(patient.html, HtmlCompat.FROM_HTML_MODE_LEGACY)
- view.findViewById(R.id.name).text = patient.name
- view.findViewById(R.id.dob).text = patient.dob
- view.findViewById(R.id.gender).text = patient.phone
- }
- }
-
- companion object {
- /** The fragment argument representing the patient item ID that this fragment represents. */
- const val ARG_ITEM_ID = "patient_item_id"
- }
-}
diff --git a/reference/src/main/java/com/google/android/fhir/reference/PatientDetailsFragment.kt b/reference/src/main/java/com/google/android/fhir/reference/PatientDetailsFragment.kt
new file mode 100644
index 0000000000..132e11109e
--- /dev/null
+++ b/reference/src/main/java/com/google/android/fhir/reference/PatientDetailsFragment.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2020 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.reference
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.text.HtmlCompat
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.ViewModelProvider
+import androidx.navigation.fragment.NavHostFragment
+import androidx.navigation.fragment.navArgs
+import androidx.recyclerview.widget.RecyclerView
+import com.google.android.fhir.FhirEngine
+
+/**
+ * A fragment representing a single Patient detail screen. This fragment is contained in a
+ * [MainActivity].
+ */
+class PatientDetailsFragment : Fragment() {
+ private lateinit var fhirEngine: FhirEngine
+ private lateinit var patientDetailsViewModel: PatientDetailsViewModel
+ private val args: PatientDetailsFragmentArgs by navArgs()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setHasOptionsMenu(true)
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? = inflater.inflate(R.layout.patient_detail, container, false)
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ val recyclerView: RecyclerView = view.findViewById(R.id.observation_list)
+ val adapter = ObservationItemRecyclerViewAdapter()
+ recyclerView.adapter = adapter
+ fhirEngine = FhirApplication.fhirEngine(requireContext())
+ patientDetailsViewModel =
+ ViewModelProvider(
+ this,
+ PatientDetailsViewModelFactory(requireActivity().application, fhirEngine, args.patientId)
+ )
+ .get(PatientDetailsViewModel::class.java)
+ patientDetailsViewModel.livePatientData.observe(viewLifecycleOwner) {
+ setupPatientData(view, it)
+ }
+ patientDetailsViewModel.livePatientObservation.observe(viewLifecycleOwner) {
+ adapter.submitList(it)
+ }
+ }
+
+ private fun setupPatientData(view: View, patient: PatientListViewModel.PatientItem?) {
+ patient?.let {
+ view.findViewById(R.id.patient_detail).text =
+ HtmlCompat.fromHtml(it.html, HtmlCompat.FROM_HTML_MODE_LEGACY)
+ view.findViewById(R.id.name).text = it.name
+ view.findViewById(R.id.dob).text = it.dob
+ view.findViewById(R.id.gender).text = it.phone
+
+ (requireActivity() as AppCompatActivity).supportActionBar?.apply {
+ title = it.name
+ setDisplayHomeAsUpEnabled(true)
+ }
+ }
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ return when (item.itemId) {
+ android.R.id.home -> {
+ NavHostFragment.findNavController(this).navigateUp()
+ true
+ }
+ else -> super.onOptionsItemSelected(item)
+ }
+ }
+
+ companion object {
+ /** The fragment argument representing the patient item ID that this fragment represents. */
+ const val ARG_ITEM_ID = "patient_item_id"
+ }
+}
diff --git a/reference/src/main/java/com/google/android/fhir/reference/PatientDetailsViewModel.kt b/reference/src/main/java/com/google/android/fhir/reference/PatientDetailsViewModel.kt
new file mode 100644
index 0000000000..4bc6b37b8a
--- /dev/null
+++ b/reference/src/main/java/com/google/android/fhir/reference/PatientDetailsViewModel.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2020 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.reference
+
+import android.app.Application
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.liveData
+import com.google.android.fhir.FhirEngine
+import com.google.android.fhir.search.search
+import org.hl7.fhir.r4.model.Observation
+import org.hl7.fhir.r4.model.Patient
+
+/**
+ * The ViewModel helper class for PatientItemRecyclerViewAdapter, that is responsible for preparing
+ * data for UI.
+ */
+class PatientDetailsViewModel(
+ application: Application,
+ private val fhirEngine: FhirEngine,
+ private val patientId: String
+) : AndroidViewModel(application) {
+
+ val livePatientData = liveData { emit(getPatient()) }
+ val livePatientObservation = liveData { emit(getPatientObservations()) }
+
+ private suspend fun getPatient(): PatientListViewModel.PatientItem {
+ val patient = fhirEngine.load(Patient::class.java, patientId)
+ return patient.toPatientItem(0)
+ }
+
+ private suspend fun getPatientObservations(): List {
+ val observations: MutableList = mutableListOf()
+ fhirEngine
+ .search { filter(Observation.SUBJECT) { value = "Patient/$patientId" } }
+ .take(MAX_RESOURCE_COUNT)
+ .mapIndexed { index, fhirPatient -> createObservationItem(index + 1, fhirPatient) }
+ .let { observations.addAll(it) }
+ return observations
+ }
+
+ /** Creates ObservationItem objects with displayable values from the Fhir Observation objects. */
+ private fun createObservationItem(
+ position: Int,
+ observation: Observation
+ ): PatientListViewModel.ObservationItem {
+ // val observation: Observation = getObservationDetails(position)
+ val observationCode = observation.code.text
+
+ // Show nothing if no values available for datetime and value quantity.
+ val dateTimeStr =
+ if (observation.hasEffectiveDateTimeType()) observation.effectiveDateTimeType.asStringValue()
+ else "No effective DateTime"
+ val value =
+ if (observation.hasValueQuantity()) observation.valueQuantity.value.toString()
+ else "No ValueQuantity"
+ val valueUnit = if (observation.hasValueQuantity()) observation.valueQuantity.unit else ""
+ val valueStr = "$value $valueUnit"
+
+ return PatientListViewModel.ObservationItem(
+ position.toString(),
+ observationCode,
+ dateTimeStr,
+ valueStr
+ )
+ }
+}
+
+class PatientDetailsViewModelFactory(
+ private val application: Application,
+ private val fhirEngine: FhirEngine,
+ private val patientId: String
+) : ViewModelProvider.Factory {
+ override fun create(modelClass: Class): T {
+ require(modelClass.isAssignableFrom(PatientDetailsViewModel::class.java)) {
+ "Unknown ViewModel class"
+ }
+ return PatientDetailsViewModel(application, fhirEngine, patientId) as T
+ }
+}
diff --git a/reference/src/main/java/com/google/android/fhir/reference/PatientListActivity.kt b/reference/src/main/java/com/google/android/fhir/reference/PatientListActivity.kt
deleted file mode 100644
index bfea362939..0000000000
--- a/reference/src/main/java/com/google/android/fhir/reference/PatientListActivity.kt
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright 2020 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.reference
-
-import android.annotation.SuppressLint
-import android.content.Intent
-import android.os.Bundle
-import android.util.Log
-import android.view.Menu
-import android.view.MenuInflater
-import android.widget.TextView
-import androidx.appcompat.app.AppCompatActivity
-import androidx.appcompat.view.menu.MenuBuilder
-import androidx.appcompat.widget.SearchView
-import androidx.appcompat.widget.Toolbar
-import androidx.lifecycle.ViewModelProvider
-import androidx.recyclerview.widget.RecyclerView
-import androidx.work.Constraints
-import com.google.android.fhir.FhirEngine
-import com.google.android.fhir.reference.FhirApplication.Companion.fhirEngine
-import com.google.android.fhir.reference.data.FhirPeriodicSyncWorker
-import com.google.android.fhir.sync.PeriodicSyncConfiguration
-import com.google.android.fhir.sync.RepeatInterval
-import com.google.android.fhir.sync.Sync
-import java.util.concurrent.TimeUnit
-
-/** An activity representing a list of Patients. */
-class PatientListActivity() : AppCompatActivity() {
- private lateinit var fhirEngine: FhirEngine
- private lateinit var patientListViewModel: PatientListViewModel
- private lateinit var searchView: SearchView
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- Log.d("PatientListActivity", "onCreate() called")
- setContentView(R.layout.activity_patient_list)
-
- Sync.periodicSync(
- this,
- PeriodicSyncConfiguration(
- syncConstraints = Constraints.Builder().build(),
- repeat = RepeatInterval(interval = 1, timeUnit = TimeUnit.MINUTES)
- )
- )
-
- val toolbar = findViewById(R.id.toolbar)
- setSupportActionBar(toolbar)
- toolbar.title = title
-
- fhirEngine = fhirEngine(this)
-
- patientListViewModel =
- ViewModelProvider(
- this,
- PatientListViewModel.PatientListViewModelFactory(this.application, fhirEngine)
- )
- .get(PatientListViewModel::class.java)
- val recyclerView: RecyclerView = findViewById(R.id.patient_list)
-
- val adapter = PatientItemRecyclerViewAdapter(this::onPatientItemClicked)
- recyclerView.adapter = adapter
-
- patientListViewModel.liveSearchedPatients.observe(
- this,
- {
- Log.d("PatientListActivity", "Submitting ${it.count()} patient records")
- adapter.submitList(it)
- }
- )
-
- patientListViewModel.patientCount.observe(this, { Log.d("PatientListActivity", "$it Patient") })
-
- patientListViewModel.patientCount.observe(
- this,
- { findViewById(R.id.patient_count).text = " Patient(s)" }
- )
-
- searchView = findViewById(R.id.search)
- searchView.setOnQueryTextListener(
- object : SearchView.OnQueryTextListener {
- override fun onQueryTextChange(newText: String): Boolean {
- patientListViewModel.searchPatientsByName(newText)
- return true
- }
-
- override fun onQueryTextSubmit(query: String): Boolean {
- patientListViewModel.searchPatientsByName(query)
- return true
- }
- }
- )
- }
-
- // Click handler to help display the details about the patients from the list.
- private fun onPatientItemClicked(patientItem: PatientListViewModel.PatientItem) {
- val intent =
- Intent(this.applicationContext, PatientDetailActivity::class.java).apply {
- putExtra(PatientDetailFragment.ARG_ITEM_ID, patientItem.id)
- }
- this.startActivity(intent)
- }
-
- // To suppress the warning. Seems to be an issue with androidx library.
- // "MenuBuilder.setOptionalIconsVisible can only be called from within the same library group
- // prefix (referenced groupId=androidx.appcompat with prefix androidx from groupId=fhir-engine"
- @SuppressLint("RestrictedApi")
- override fun onCreateOptionsMenu(menu: Menu?): Boolean {
- val inflater: MenuInflater = menuInflater
- inflater.inflate(R.menu.list_options_menu, menu)
- // To ensure that icons show up in the overflow options menu. Icons go missing without this.
- if (menu is MenuBuilder) {
- menu.setOptionalIconsVisible(true)
- }
- return true
- }
-
- override fun onBackPressed() {
- if (searchView.query.isNotEmpty()) searchView.setQuery("", true) else super.onBackPressed()
- }
-}
diff --git a/reference/src/main/java/com/google/android/fhir/reference/PatientListFragment.kt b/reference/src/main/java/com/google/android/fhir/reference/PatientListFragment.kt
new file mode 100644
index 0000000000..03b51be33f
--- /dev/null
+++ b/reference/src/main/java/com/google/android/fhir/reference/PatientListFragment.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2020 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.reference
+
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.activity.OnBackPressedCallback
+import androidx.appcompat.app.AppCompatActivity
+import androidx.appcompat.widget.SearchView
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.ViewModelProvider
+import androidx.navigation.fragment.findNavController
+import androidx.recyclerview.widget.RecyclerView
+import com.google.android.fhir.FhirEngine
+import com.google.android.fhir.reference.PatientListViewModel.PatientListViewModelFactory
+
+class PatientListFragment : Fragment() {
+ private lateinit var fhirEngine: FhirEngine
+ private lateinit var patientListViewModel: PatientListViewModel
+ private lateinit var searchView: SearchView
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ return inflater.inflate(R.layout.fragment_patient_list, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ (requireActivity() as AppCompatActivity).supportActionBar?.apply {
+ title = requireActivity().title
+ setDisplayHomeAsUpEnabled(false)
+ }
+ fhirEngine = FhirApplication.fhirEngine(requireContext())
+ patientListViewModel =
+ ViewModelProvider(
+ this,
+ PatientListViewModelFactory(requireActivity().application, fhirEngine)
+ )
+ .get(PatientListViewModel::class.java)
+ val recyclerView: RecyclerView = view.findViewById(R.id.patient_list)
+ val adapter = PatientItemRecyclerViewAdapter(this::onPatientItemClicked)
+ recyclerView.adapter = adapter
+
+ patientListViewModel.liveSearchedPatients.observe(
+ viewLifecycleOwner,
+ {
+ Log.d("PatientListActivity", "Submitting ${it.count()} patient records")
+ adapter.submitList(it)
+ }
+ )
+ patientListViewModel.patientCount.observe(
+ viewLifecycleOwner,
+ { Log.d("PatientListActivity", "$it Patient") }
+ )
+
+ patientListViewModel.patientCount.observe(
+ viewLifecycleOwner,
+ { view.findViewById(R.id.patient_count).text = "$it Patient(s)" }
+ )
+ searchView = view.findViewById(R.id.search)
+ searchView.setOnQueryTextListener(
+ object : SearchView.OnQueryTextListener {
+ override fun onQueryTextChange(newText: String): Boolean {
+ patientListViewModel.searchPatientsByName(newText)
+ return true
+ }
+
+ override fun onQueryTextSubmit(query: String): Boolean {
+ patientListViewModel.searchPatientsByName(query)
+ return true
+ }
+ }
+ )
+ requireActivity()
+ .onBackPressedDispatcher
+ .addCallback(
+ viewLifecycleOwner,
+ object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ if (searchView.query.isNotEmpty()) {
+ searchView.setQuery("", true)
+ } else {
+ isEnabled = false
+ activity?.onBackPressed()
+ }
+ }
+ }
+ )
+ }
+
+ private fun onPatientItemClicked(patientItem: PatientListViewModel.PatientItem) {
+ findNavController()
+ .navigate(PatientListFragmentDirections.navigateToProductDetail(patientItem.resourceId))
+ }
+}
diff --git a/reference/src/main/java/com/google/android/fhir/reference/PatientListViewModel.kt b/reference/src/main/java/com/google/android/fhir/reference/PatientListViewModel.kt
index b8fd8f3757..61ef34a83e 100644
--- a/reference/src/main/java/com/google/android/fhir/reference/PatientListViewModel.kt
+++ b/reference/src/main/java/com/google/android/fhir/reference/PatientListViewModel.kt
@@ -24,7 +24,6 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.liveData
import androidx.lifecycle.viewModelScope
import com.google.android.fhir.FhirEngine
-import com.google.android.fhir.reference.data.SamplePatients
import com.google.android.fhir.search.Order
import com.google.android.fhir.search.StringFilterModifier
import com.google.android.fhir.search.count
@@ -39,7 +38,6 @@ import org.hl7.fhir.r4.model.Patient
class PatientListViewModel(application: Application, private val fhirEngine: FhirEngine) :
AndroidViewModel(application) {
- private val samplePatients = SamplePatients()
val liveSearchedPatients = MutableLiveData>()
val patientCount = liveData { emit(count()) }
@@ -65,8 +63,9 @@ class PatientListViewModel(application: Application, private val fhirEngine: Fhi
}
private suspend fun getSearchResults(nameQuery: String = ""): List {
- val searchResults: List =
- fhirEngine.search {
+ val patients: MutableList = mutableListOf()
+ fhirEngine
+ .search {
if (nameQuery.isNotEmpty())
filter(Patient.NAME) {
modifier = StringFilterModifier.CONTAINS
@@ -76,7 +75,10 @@ class PatientListViewModel(application: Application, private val fhirEngine: Fhi
count = 100
from = 0
}
- return samplePatients.getPatientItems(searchResults)
+ .take(MAX_RESOURCE_COUNT)
+ .mapIndexed { index, fhirPatient -> fhirPatient.toPatientItem(index + 1) }
+ .let { patients.addAll(it) }
+ return patients
}
/** The Patient's details for display purposes. */
@@ -114,3 +116,23 @@ class PatientListViewModel(application: Application, private val fhirEngine: Fhi
}
}
}
+
+internal fun Patient.toPatientItem(position: Int): PatientListViewModel.PatientItem {
+ val name = name[0].nameAsSingleString
+
+ // Show nothing if no values available for gender and date of birth.
+ val gender = if (hasGenderElement()) genderElement.valueAsString else ""
+ val dob = if (hasBirthDateElement()) birthDateElement.valueAsString else ""
+ val html: String = if (hasText()) text.div.valueAsString else ""
+ val phone: String = if (hasTelecom()) telecom[0].value else ""
+
+ return PatientListViewModel.PatientItem(
+ position.toString(),
+ name,
+ gender,
+ dob,
+ html,
+ phone,
+ idElement.idPart
+ )
+}
diff --git a/reference/src/main/java/com/google/android/fhir/reference/data/SamplePatients.kt b/reference/src/main/java/com/google/android/fhir/reference/data/SamplePatients.kt
deleted file mode 100644
index ffe43c2c88..0000000000
--- a/reference/src/main/java/com/google/android/fhir/reference/data/SamplePatients.kt
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright 2020 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.reference.data
-
-import ca.uhn.fhir.context.FhirContext
-import ca.uhn.fhir.parser.IParser
-import com.google.android.fhir.reference.PatientListViewModel
-import org.hl7.fhir.r4.model.Bundle
-import org.hl7.fhir.r4.model.Observation
-import org.hl7.fhir.r4.model.Patient
-
-private const val MAX_RESOURCE_COUNT = 20
-
-/**
- * Helper class for loading a list of sample Fhir Patient Resource objects.
- *
- * Parses and loads Patient data from the passed JSON String into items that could be used by
- * PatientListViewModel.
- */
-class SamplePatients {
- // The resource bundle with Patient objects.
- private var fhirBundle: Bundle? = null
-
- companion object {
- val tag = "SamplePatient"
-
- val fhirJsonParser: IParser = FhirContext.forR4().newJsonParser()
- }
- /** Returns list of PatientItem objects based on patients from the json string. */
- fun getPatientItems(jsonString: String): List {
- val patients: MutableList = mutableListOf()
-
- fhirBundle = fhirJsonParser.parseResource(Bundle::class.java, jsonString) as Bundle
-
- // Create a list of PatientItems from fhirPatients. The display index is 1 based.
- fhirBundle
- ?.entry
- ?.take(MAX_RESOURCE_COUNT)
- ?.mapIndexed { index, entry -> createPatientItem(index + 1, entry.resource as Patient) }
- ?.let { patients.addAll(it) }
-
- return patients
- }
-
- fun getPatientItems(fhirPatients: List): List {
- val patients: MutableList = mutableListOf()
-
- // Create a list of PatientItems from fhirPatients. The display index is 1 based.
- fhirPatients
- .take(MAX_RESOURCE_COUNT)
- ?.mapIndexed { index, fhirPatient -> createPatientItem(index + 1, fhirPatient) }
- ?.let { patients.addAll(it) }
-
- // Return a cloned List
- return patients
- }
-
- /** Creates PatientItem objects with displayable values from the Fhir Patient objects. */
- private fun createPatientItem(position: Int, patient: Patient): PatientListViewModel.PatientItem {
- val name = patient.name[0].nameAsSingleString
-
- // Show nothing if no values available for gender and date of birth.
- val gender = if (patient.hasGenderElement()) patient.genderElement.valueAsString else ""
- val dob = if (patient.hasBirthDateElement()) patient.birthDateElement.valueAsString else ""
- val html: String = if (patient.hasText()) patient.text.div.valueAsString else ""
- val phone: String = if (patient.hasTelecom()) patient.telecom[0].value else ""
-
- return PatientListViewModel.PatientItem(
- position.toString(),
- name,
- gender,
- dob,
- html,
- phone,
- patient.idElement.idPart
- )
- }
-
- /** Returns list of ObservationItem objects based on observations from the json string. */
- fun getObservationItems(jsonString: String): MutableList {
- val observations = ArrayList()
-
- fhirBundle = fhirJsonParser.parseResource(Bundle::class.java, jsonString) as Bundle
-
- // Create a list of ObservationItems from fhirObservations. The display index is 1 based.
- fhirBundle
- ?.entry
- ?.take(MAX_RESOURCE_COUNT)
- ?.mapIndexed { index, entry ->
- createObservationItem(index + 1, entry.resource as Observation)
- }
- ?.let { observations.addAll(it) }
-
- return observations
- }
-
- /** Creates ObservationItem objects with displayable values from the Fhir Observation objects. */
- private fun createObservationItem(
- position: Int,
- observation: Observation
- ): PatientListViewModel.ObservationItem {
- // val observation: Observation = getObservationDetails(position)
- val observationCode = observation.code.text
-
- // Show nothing if no values available for datetime and value quantity.
- val dateTimeStr =
- if (observation.hasEffectiveDateTimeType()) observation.effectiveDateTimeType.asStringValue()
- else "No effective DateTime"
- val value =
- if (observation.hasValueQuantity()) observation.valueQuantity.value.toString()
- else "No ValueQuantity"
- val valueUnit = if (observation.hasValueQuantity()) observation.valueQuantity.unit else ""
- val valueStr = "$value $valueUnit"
-
- return PatientListViewModel.ObservationItem(
- position.toString(),
- observationCode,
- dateTimeStr,
- valueStr
- )
- }
-}
diff --git a/reference/src/main/res/layout/activity_patient_list.xml b/reference/src/main/res/layout/activity_main.xml
similarity index 66%
rename from reference/src/main/res/layout/activity_patient_list.xml
rename to reference/src/main/res/layout/activity_main.xml
index f157a17730..3b34cc8dcf 100644
--- a/reference/src/main/res/layout/activity_patient_list.xml
+++ b/reference/src/main/res/layout/activity_main.xml
@@ -21,7 +21,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
- tools:context=".PatientListActivity"
+ tools:context=".MainActivity"
>
-
-
-
-
-
-
diff --git a/reference/src/main/res/layout/activity_patient_detail.xml b/reference/src/main/res/layout/activity_patient_detail.xml
deleted file mode 100644
index f1edc07b9c..0000000000
--- a/reference/src/main/res/layout/activity_patient_detail.xml
+++ /dev/null
@@ -1,77 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/reference/src/main/res/layout/fragment_patient_list.xml b/reference/src/main/res/layout/fragment_patient_list.xml
new file mode 100644
index 0000000000..34499ff288
--- /dev/null
+++ b/reference/src/main/res/layout/fragment_patient_list.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
diff --git a/reference/src/main/res/layout/observation_list.xml b/reference/src/main/res/layout/observation_list.xml
index 9e7e21a16d..2eb2207bcf 100644
--- a/reference/src/main/res/layout/observation_list.xml
+++ b/reference/src/main/res/layout/observation_list.xml
@@ -25,6 +25,6 @@
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
app:layoutManager="LinearLayoutManager"
- tools:context=".PatientListActivity"
+ tools:context=".MainActivity"
tools:listitem="@layout/observation_list_item"
/>
diff --git a/reference/src/main/res/layout/patient_detail.xml b/reference/src/main/res/layout/patient_detail.xml
index 72fa105d0c..be15d3a725 100644
--- a/reference/src/main/res/layout/patient_detail.xml
+++ b/reference/src/main/res/layout/patient_detail.xml
@@ -32,10 +32,8 @@
android:padding="16dp"
>
diff --git a/reference/src/main/res/layout/patient_list_item.xml b/reference/src/main/res/layout/patient_list_item.xml
index 9304cee35e..25e94665b2 100644
--- a/reference/src/main/res/layout/patient_list_item.xml
+++ b/reference/src/main/res/layout/patient_list_item.xml
@@ -24,9 +24,8 @@
android:id="@+id/id_patient_number"
style="@android:style/TextAppearance.Large"
android:layout_width="wrap_content"
- android:layout_height="fill_parent"
- android:layout_alignParentTop="true"
- android:layout_alignParentBottom="true"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
android:layout_marginStart="@dimen/text_margin"
android:layout_marginTop="@dimen/text_margin"
android:layout_marginEnd="@dimen/text_margin"
@@ -55,7 +54,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/name"
- android:layout_alignParentBottom="true"
android:layout_marginStart="@dimen/small_text_margin"
android:layout_marginTop="@dimen/small_text_margin"
android:layout_marginEnd="@dimen/text_margin"
@@ -72,7 +70,6 @@
android:layout_height="wrap_content"
android:layout_below="@id/name"
android:layout_alignParentEnd="true"
- android:layout_alignParentBottom="true"
android:layout_marginStart="@dimen/text_margin"
android:layout_marginTop="@dimen/small_text_margin"
android:layout_marginEnd="@dimen/small_text_margin"
diff --git a/reference/src/main/res/navigation/reference_nav_graph.xml b/reference/src/main/res/navigation/reference_nav_graph.xml
new file mode 100644
index 0000000000..8a1ed35f25
--- /dev/null
+++ b/reference/src/main/res/navigation/reference_nav_graph.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/reference/src/main/res/values/strings.xml b/reference/src/main/res/values/strings.xml
index a27660396c..821115494b 100644
--- a/reference/src/main/res/values/strings.xml
+++ b/reference/src/main/res/values/strings.xml
@@ -23,4 +23,5 @@
%1$s: %2$s\nEffective: %3$s
+ Find by Patient Name