Skip to content

Commit

Permalink
Merge branch 'master' into mn/503-language-switcher
Browse files Browse the repository at this point in the history
  • Loading branch information
ndegwamartin committed Nov 23, 2021
2 parents 809b722 + 6084de7 commit c0b966c
Show file tree
Hide file tree
Showing 60 changed files with 84,234 additions and 337 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ jobs:
if: matrix.api-level == 29 # Only upload coverage on API level 29
uses: codecov/codecov-action@v2
with:
files: engine/build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml,datacapture/build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml
files: engine/build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml,datacapture/build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml,common/build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml
fail_ci_if_error: true
verbose: true

Expand Down
30 changes: 22 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Android FHIR SDK (Early Access) [![master](https://github.com/google/android-fhir/workflows/CI/badge.svg?branch=master)](https://github.com/google/android-fhir/actions?query=workflow%3ACI) [![codecov](https://codecov.io/gh/google/android-fhir/branch/master/graph/badge.svg?token=PDSC4WRDTQ)](https://codecov.io/gh/google/android-fhir/branch/master)
# Android FHIR SDK (Pre-beta release) [![master](https://github.com/google/android-fhir/workflows/CI/badge.svg?branch=master)](https://github.com/google/android-fhir/actions?query=workflow%3ACI) [![codecov](https://codecov.io/gh/google/android-fhir/branch/master/graph/badge.svg?token=PDSC4WRDTQ)](https://codecov.io/gh/google/android-fhir/branch/master)

The Android FHIR SDK (the SDK) is an Android library for building
offline-capable, mobile-first healthcare applications using
Expand All @@ -7,11 +7,6 @@ simplify the process of incorporating support for FHIR into new or existing
mobile solutions and to accelerate the adoption of FHIR standards as part of
broader interoperability efforts in healthcare.

## Status

This is currently in **Early Access** for Developers ONLY and is NOT
production-ready. **Do NOT use in production**.

## Usage

The SDK is designed to support Android 21 (lollipop) and above. Android Studio
Expand All @@ -20,8 +15,14 @@ desugaring](https://developer.android.com/studio/preview/features#j8-desugar).

## Libraries

The repository is organised into two main libraries, *engine* and *structured
data capture*.
The repository is organised into the following libraries:

| Library | Status | Notes |
| -------------------- | -------------- | -------------------------------------------------------- |
| Data Capture Library | Stable | Approaching beta release |
| FHIR Engine | Mostly stable | Finalizing sync API subject to change. Other APIs stable |
| Workflow Library | In development | Pending alpha release |


### FHIR engine library [![Google Maven](https://badgen.net/maven/v/metadata-url/dl.google.com/dl/android/maven2/com/google/android/fhir/engine/maven-metadata.xml)](https://maven.google.com/web/index.html?#com.google.android.fhir:engine)

Expand All @@ -47,6 +48,19 @@ To use this library in your Android application, see [Structured Data Capture
Library User's
Guide](https://github.com/google/android-fhir/wiki/Structured-Data-Capture-Library-User's-Guide).

### Workflow library

This library provides APIs that use digital clinical guidelines to support decision making and analytics in clinical workflows.

It supports the following operations:

| Operation | Status | Notes |
| -------------------------- | -------------- | -------------------------------------------------------------------- |
| Measure/$evaluate-measure | Alpha | See https://www.hl7.org/fhir/measure-operation-evaluate-measure.html |
| PlanDefinition/$apply | In development | See https://www.hl7.org/fhir/plandefinition-operation-apply.html |

Future features of the library will provide support for Tasking and other Workflow related requirements

## Sample Applications

Two sample applications are provided that demonstrate different features of the
Expand Down
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/SpotlessConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ fun Project.configureSpotless() {
}
// Creates one off SpotlessApply task for generated files
com.diffplug.gradle.spotless.KotlinExtension(this).apply {
target("**/*Generated.kt")
target("**/*_Generated.kt")
ktlint(ktlintVersion).userData(ktlintOptions)
ktfmt().googleStyle()
licenseHeaderFile(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ object SearchParameterRepositoryGenerator {

private const val indexPackage = "com.google.android.fhir.index"
private const val hapiPackage = "org.hl7.fhir.r4.model"
private const val generatedClassName = "SearchParameterRepositoryGenerated"
private const val generatedClassName = "SearchParameterRepository_Generated"
private const val indexTestPackage = "com.google.android.fhir.index"
private const val generatedTestHelperClassName = "SearchParameterRepositoryGeneratedTestHelper"
private const val generatedTestHelperClassName = "SearchParameterRepositoryTestHelper_Generated"
private const val generatedComment =
"This File is Generated from com.google.android.fhir.codegen.SearchParameterRepositoryGenerator all changes to this file must be made through the aforementioned file only"

Expand All @@ -63,7 +63,7 @@ object SearchParameterRepositoryGenerator {
val searchParameter = entry.resource as SearchParameter
if (searchParameter.expression.isNullOrEmpty()) continue

for (path in getResourceToPathMap(searchParameter.expression)) {
for (path in getResourceToPathMap(searchParameter)) {
val hashMapKey = path.key
if (!searchParamMap.containsKey(hashMapKey)) {
searchParamMap[hashMapKey] = CodeBlock.builder().add("%S -> listOf(", hashMapKey)
Expand Down Expand Up @@ -100,14 +100,12 @@ object SearchParameterRepositoryGenerator {
try {
Class.forName(resourceClass.reflectionName())
} catch (e: ClassNotFoundException) {
// TODO handle alias and name (InsurancePlan)
// https://github.com/google/android-fhir/issues/921
println("Class not found $resource ")
continue
}
function.addCode(searchParamMap[resource]!!.add(")\n").build())

if (resource != "Resource" && resource != "InsurancePlan") {
if (resource != "Resource") {
testHelperFunctionCodeBlock.add("%T(),\n", resourceClass)
}
}
Expand All @@ -131,21 +129,28 @@ object SearchParameterRepositoryGenerator {
}

/**
* @return the resource names mapped to their respective paths in the expression.
* @return the resource names mapped to their respective paths in the expression of [searchParam]
*
* @param expression an expression that contains the paths of a given search param
* @param searchParam the search parameter that needs to be mapped
*
* This is necessary because the path expressions are not necessarily grouped by resource type
*
* For example an expression of "AllergyIntolerance.code | AllergyIntolerance.reaction.substance |
* Condition.code" will return "AllergyIntolerance" -> "AllergyIntolerance.code |
* AllergyIntolerance.reaction.substance" , "Condition" -> "Condition.code"
*/
private fun getResourceToPathMap(expression: String): Map<String, String> {
return expression
.split("|")
.groupBy { splitString -> splitString.split(".").first().trim().removePrefix("(") }
.mapValues { it.value.joinToString(" | ") { join -> join.trim() } }
private fun getResourceToPathMap(searchParam: SearchParameter): Map<String, String> {
// the if block is added because of the issue https://jira.hl7.org/browse/FHIR-22724 and can be
// removed once the issue is resolved
return if (searchParam.base.size == 1) {
mapOf(searchParam.base.single().valueAsString to searchParam.expression)
} else {
searchParam
.expression
.split("|")
.groupBy { splitString -> splitString.split(".").first().trim().removePrefix("(") }
.mapValues { it.value.joinToString(" | ") { join -> join.trim() } }
}
}

private fun String.toHapiName() = if (this == "List") "ListResource" else this
Expand Down
37 changes: 31 additions & 6 deletions common/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,11 +1,36 @@
plugins {
id(Plugins.BuildPlugins.javaLibrary)
id(Plugins.BuildPlugins.kotlin)
id(Plugins.BuildPlugins.androidLib)
id(Plugins.BuildPlugins.kotlinAndroid)
jacoco
}

java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
createJacocoTestReportTask()

android {
compileSdk = Sdk.compileSdk
buildToolsVersion = Plugins.Versions.buildTools

defaultConfig {
minSdk = Sdk.minSdk
targetSdk = Sdk.targetSdk
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions { jvmTarget = JavaVersion.VERSION_1_8.toString() }
configureJacocoTestOptions()
}

dependencies { implementation(Dependencies.fhirUcum) }
configurations { all { exclude(module = "xpp3") } }

dependencies {
api(Dependencies.HapiFhir.structuresR4)

implementation(Dependencies.fhirUcum)

testImplementation(Dependencies.AndroidxTest.core)
testImplementation(Dependencies.junit)
testImplementation(Dependencies.robolectric)
testImplementation(Dependencies.truth)
}
22 changes: 22 additions & 0 deletions common/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
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.
-->
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.fhir.common"
>

</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,33 @@
* limitations under the License.
*/

package com.google.android.fhir.datacapture.validation
package com.google.android.fhir

import java.util.Calendar
import java.util.Date
import org.hl7.fhir.r4.model.Coding
import org.hl7.fhir.r4.model.Type

/**
* Returns whether two instances of the [Type] class are equal.
*
* Note this is not an operator because it is not possible to overload the equality operator as an
* extension.
*/
fun equals(a: Type, b: Type): Boolean {
if (a::class != b::class) return false

if (a === b) return true

if (a.isPrimitive) return a.primitiveValue() == b.primitiveValue()

// Codes with the same system and code values are considered equal even if they have different
// display values.
if (a is Coding && b is Coding) return a.system == b.system && a.code == b.code

throw NotImplementedError("Comparison for type ${a::class.java} not supported.")
}

operator fun Type.compareTo(value: Type?): Int {
if (value != null) {
if (!this.fhirType().equals(value.fhirType())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,19 @@
* limitations under the License.
*/

package com.google.android.fhir.datacapture.validation
package com.google.android.fhir

import android.os.Build
import com.google.common.truth.Truth.assertThat
import java.util.Calendar
import org.hl7.fhir.r4.model.Attachment
import org.hl7.fhir.r4.model.BooleanType
import org.hl7.fhir.r4.model.Coding
import org.hl7.fhir.r4.model.DateType
import org.hl7.fhir.r4.model.DecimalType
import org.hl7.fhir.r4.model.IntegerType
import org.hl7.fhir.r4.model.Quantity
import org.hl7.fhir.r4.model.Reference
import org.junit.Assert.assertThrows
import org.junit.Test
import org.junit.runner.RunWith
Expand All @@ -32,6 +37,77 @@ import org.robolectric.annotation.Config
@Config(sdk = [Build.VERSION_CODES.P])
class MoreTypesTest {

@Test
fun equals_differentTypes_shouldReturnFalse() {
assertThat(equals(BooleanType(true), DecimalType(1.1))).isFalse()
}

@Test
fun equals_sameObject_shouldReturnTrue() {
val value = BooleanType(true)
assertThat(equals(value, value)).isTrue()
}

@Test
fun equals_samePrimitiveValue_shouldReturnTrue() {
assertThat(equals(DecimalType(1.1), DecimalType(1.1))).isTrue()
}

@Test
fun equals_differentPrimitiveValues_shouldReturnFalse() {
assertThat(equals(DecimalType(1.1), DecimalType(1.2))).isFalse()
}

@Test
fun equals_coding_differentSystems_shouldReturnFalse() {
assertThat(
equals(Coding("system", "code", "display"), Coding("otherSystem", "code", "display"))
)
.isFalse()
}

@Test
fun equals_coding_differentCodes_shouldReturnFalse() {
assertThat(
equals(Coding("system", "code", "display"), Coding("system", "otherCode", "display"))
)
.isFalse()
}

@Test
fun equals_coding_differentDisplays_shouldReturnTrue() {
assertThat(
equals(Coding("system", "code", "display"), Coding("system", "code", "otherDisplay"))
)
.isTrue()
}

@Test
fun equals_attachment_shouldThrowException() {
val exception =
assertThrows(NotImplementedError::class.java) { equals(Attachment(), Attachment()) }

assertThat(exception.message)
.isEqualTo("Comparison for type ${Attachment::class.java} not supported.")
}

@Test
fun equals_quantity_shouldThrowException() {
val exception = assertThrows(NotImplementedError::class.java) { equals(Quantity(), Quantity()) }

assertThat(exception.message)
.isEqualTo("Comparison for type ${Quantity::class.java} not supported.")
}

@Test
fun equals_reference_shouldThrowException() {
val exception =
assertThrows(NotImplementedError::class.java) { equals(Reference(), Reference()) }

assertThat(exception.message)
.isEqualTo("Comparison for type ${Reference::class.java} not supported.")
}

@Test
fun compareTo_int_shouldReturnPositiveValue() {
val integerValue = IntegerType()
Expand Down
1 change: 1 addition & 0 deletions datacapture/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ dependencies {
implementation(Dependencies.Lifecycle.viewModelKtx)
implementation(Dependencies.material)
implementation(Dependencies.flexBox)
implementation(project(":common"))

testImplementation(Dependencies.AndroidxTest.core)
testImplementation(Dependencies.junit)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.TextView
import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.view.get
import androidx.core.view.isVisible
import androidx.test.annotation.UiThreadTest
import androidx.test.ext.junit.runners.AndroidJUnit4
Expand Down Expand Up @@ -270,4 +271,31 @@ class QuestionnaireItemAutoCompleteViewHolderFactoryInstrumentedTest {
assertThat(viewHolder.itemView.findViewById<TextInputLayout>(R.id.textInputLayout).error)
.isNull()
}

@Test
@UiThreadTest
fun bind_readOnly_shouldDisableView() {
viewHolder.bind(
QuestionnaireItemViewItem(
Questionnaire.QuestionnaireItemComponent().apply {
readOnly = true
addAnswerOption(
Questionnaire.QuestionnaireItemAnswerOptionComponent().apply {
value = Coding().apply { display = "readOnly" }
}
)
},
QuestionnaireResponse.QuestionnaireResponseItemComponent()
) {}
.apply {
singleAnswerOrNull =
(QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent().apply {
value = answerOption.first { it.displayString == "readOnly" }.valueCoding
})
}
)

assertThat(viewHolder.itemView.findViewById<ViewGroup>(R.id.flexboxLayout)[0].isEnabled)
.isFalse()
}
}
Loading

0 comments on commit c0b966c

Please sign in to comment.