Share
Embed
2022年ヘルスケアアプリのつくり方 / 2022 A Healthcare App Odyssey
DroidKaigi2022 Day.2 Room:Cards 14:45-15:10
https://droidkaigi.jp/2022/timetable/365018
gyamoto
October 06, 2022
Transcript
49 slides
-
ࣗݾհ Kohei Yamamoto farundorl gyamoto Android application engineer Mobility Technologies
(2021.08 - , Main job) Upmind (2022.01 - , Side job) 2 Name : Twitter : GitHub : Job : -
ϔϧεέΞΞϓϦ͕ѻ͏σʔλ ϔϧεέΞΞϓϦͰϢʔβͷ݈߁ʹؔ͢Δ༷ʑͳσʔλ͕ѻΘΕ͍ͯΔɻ ϔϧεέΞΞϓϦ͕ѻ͏σʔλͷྫ • Ϣʔβࢦඪ → ɺମॏɺ৺ഥɺମԹɺମࢷɺݺٵɺͳͲ • ਭ →
ਭ࣌ؒɺ࣌ؒ͋ͨΓͷਭͷਂ͞ • ϥϯχϯά → ӡಈ࣌ؒɺ࣌ؒ͋ͨΓͷ • ΤΫααΠζ → ࣮ࢪͨ͠ӡಈɺӡಈ࣌ؒɺ࣌ؒ͋ͨΓͷ৺ഥ 8 -
ϔϧεέΞΞϓϦͷϓϥοτϑΥʔϜଟ͗͢ 2022ݱࡏɺଟ͘ͷϔϧεέΞΞϓϦͷϓϥοτϑΥʔϜ͕ఏڙ͞Ε͍ͯΔɻ ΞϓϦʹΑͬͯ࿈ܞ͍ͯ͠ΔϓϥοτϑΥʔϜ͕ҟͳΔ߹ɺϢʔβͷσʔλ͕ ϓϥοτϑΥʔϜͷนͰஅͯ͠͠·͏ɻ அͯ͠͠·ͬͨྫ • าߦɺ৺ഥ → SamsungHealth •
ਭ࣌ؒ → Fitbit • ମॏ → OMRON connect → Google Fit 10 -
৽͍͠ϓϥοτϑΥʔϜ HealthConnect ϓϥοτϑΥʔϜଟ͗͢Λղܾ͢ΔͨΊʹɺGoogle I/O 2022 Ͱൃද͞Εͨ ৽͍͠ϓϥοτϑΥʔϜɻ ͦΕͧΕϓϥοτϑΥʔϜΛ࣋ͭGoogleͱSamsung͕։ൃɻ ࠓळʹGoogleFit, Fitbit,
SamsungHealth͕HealthConnectʹରԠ༧ఆɻ ଞʹMyFitnessPal, LeapFitness, Withingsͱଟ͘ͷϔϧεέΞαʔϏε͕։ൃʹ ڠྗ͍ͯ͠Δɻ 11 -
σʔλཧ HealthConnect σόΠεத৺ͷσʔλཧ → σόΠε͔Βফ͑Δͱ෮׆ෆՄ 14 GoogleFit αʔόத৺ͷσʔλཧ → GoogleΞΧϯτ͋Ε෮׆
HealthConnectͱGoogleFitͷൺֱ -
ݖݶ੍ޚ HealthConnect σʔλछผຖͷΞΫηεݖ ʢಡΈऔΓɺॻ͖ࠐΈʣͷݖݶ੍ޚ → ࣮ෳࡶ → Ϣʔβͱͯ͠خ͍͠ 15 GoogleFit
ΞϓϦ୯ҐͷΞΫηεݖʢෳσʔλ छผΛ·ͱΊͨʣͷOAuthݖݶ੍ޚ → ࣮γϯϓϧ → ΤϯδχΞͱͯ͠خ͍͠ HealthConnectͱGoogleFitͷൺֱ -
OSόʔδϣϯ HealthConnect Android 9.0Ҏ߱ → 9.0Ҏ߱ͷϢʔβ͕େ෦ΛΊΔ → 8.1ະຬͷϢʔβ࣮֬ʹͬͯ ɹ͍ΔͷͰɺ·͍͠… 16
GoogleFit Android 4.1Ҏ߱ → 10લͷOSͰಈ͘ʂʂ HealthConnectͱGoogleFitͷൺֱ -
҆ఆੑͱকདྷੑ HealthConnect ΞϧϑΝ൛ طଘϓϥοτϑΥʔϜରԠ༧ఆ → ·ͩAPIʹഁյతมߋ͋Γͦ͏ → GoogleFitҎ֎ͷϓϥοτϑΥʔϜ ɹͱ࿈ܞͰ͖Δʂʂ 17
GoogleFit 20225݄͔Βඇਪ 2024ʹαϙʔτऴྃ༧ఆ → ಈ࡞҆ఆ͍ͯ͠Δ → ৽͘͠ಋೖ͢Δͷආ͚͍ͨ HealthConnectͱGoogleFitͷൺֱ -
HealthConnectͱGoogleFitͷൺֱ 18 HealthConnect GoogleFit ɹσʔλཧ ̋ σόΠεத৺ ˕ αʔόத৺ ɹݖݶ੍ޚ
˕ σʔλͱΞΫηεݖ୯Ґ ˚ ΞϓϦ୯Ґ ɹOSόʔδϣϯ ˚ Android 9.0~ ̋ Android 4.1~ ɹ҆ఆੑ ˚ ·ͩΞϧϑΝ൛ ̋ ҆ఆ൛ ɹকདྷੑ ˕ طଘϓϥοτϑΥʔϜ͕ରԠ༧ఆ ˚ Android͚APIඇਪ -
UpmindͰѻ͏σʔλͷϑϩʔΛߟ͑Δ σʔλͷ׆༻ վળίϯςϯπʢᛉɺ ϤΨɺετϨονʣ 25 σʔλͷอଘ ΞϓϦαʔόʹࣗਆ ܦείΞΛอଘ
ϓϥοτϑΥʔϜ࿈ܞ าɺਭɺফඅΧϩ ϦʔΛऔಘ σʔλͷೖྗ ࣗਆܦείΞʢ৺ഥͷ ΏΒ͗ɺ৺ഥʣΛܭଌ ϓϥοτϑΥʔϜ࿈ܞ ৺ഥͷॻ͖ࠐΈ -
UpmindͰѻ͏σʔλͷϑϩʔΛߟ͑Δ σʔλͷ׆༻ վળίϯςϯπʢᛉɺ ϤΨɺετϨονʣ 26 Ϣʔβମݧ վળίϯςϯπΛࢹ ௌɺମݧ
ϓϥοτϑΥʔϜ࿈ܞ վળίϯςϯπͷ࣮ࢪ Λॻ͖ࠐΈ σʔλͷอଘ ΞϓϦαʔόʹվળίϯ ςϯπͷࢹௌྃΛอଘ -
HealthConnectͷಋೖ AndroidStudio : HealthConnect : UI impl : Async impl
: Gradle script : 28 Dolphin | 2021.3.1 1.0.0-alpha05 Jetpack Compose 1.2.1 Kotlin Coroutines 1.6.0 Groovy AndroidΞϓϦ͔ΒHealthConnectͷݖݶऔಘͱσʔλͷಡΈॻ͖Λ࣮͢Δɻ ಈ࡞ڥͱόʔδϣϯ -
I. HealthConnectͷϥΠϒϥϦಋೖͱϓϩδΣΫτઃఆ 2. AndroidManifest.xml ʹHealthConnectͷݖݶ༻ํ๏Λઆ໌͢ΔActivityΛՃ ͢Δ 31 <activity android:name=".PermissionsRationaleActivity"> <intent-filter>
<action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" /> </intent-filter> <!-- TODO: σʔλछผͱΞΫηεݖΛ health_permission.xml ʹఆٛ͢Δ --> <meta-data android:name="health_permissions" android:resource="@array/health_permissions" /> -
I. HealthConnectͷϥΠϒϥϦಋೖͱϓϩδΣΫτઃఆ 3. AndroidManifest.xml ʹHealthConnectΞϓϦͱͷ࿈ܞઃఆΛՃ͢Δ 32 <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.upmind.ui">
<queries> <!-- HealthConnectΞϓϦ͕Πϯετʔϧ͞Ε͍ͯΔ͔֬ೝ͢Δ --> <package android:name="com.google.android.apps.healthdata" /> </queries> -
II. HealthConnectͷσʔλछผΛܾΊΔ 1. iOSͷHealthKitͱಉ༷ͷσʔλछผΛHealthConnectͰݕ౼͢Δ 33 let writeTypes = Set([ HKObjectType.quantityType(forIdentifier:
HKQuantityTypeIdentifier.heartRate)!, HKObjectType.categoryType(forIdentifier: HKCategoryTypeIdentifier.mindfulSession)! ]) let readTypes = Set([ HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount)!, HKObjectType.categoryType(forIdentifier: HKCategoryTypeIdentifier.sleepAnalysis)!, HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.activeEnergyBurned)! ]) -
II. HealthConnectͷσʔλछผΛܾΊΔ 2. HealthConnectͷσʔλछผʢRecordΠϯλʔϑΣʔεʣʹఆٛ͞Εͨ ৺ഥɺϚΠϯυϑϧωεɺาɺਭɺΤωϧΪʔফඅྔͷΫϥεΛ୳͢… 34 ActiveCaloriesBurnedRecord, BasalBodyTemperatureRecord, BasalMetabolicRateRecord, BloodGlucoseRecord,
BloodPressureRecord, BodyFatRecord, BodyTemperatureRecord, BoneMassRecord, CervicalMucusRecord, CyclingPedalingCadenceRecord, DistanceRecord, ElevationGainedRecord, ExerciseEventRecord, ExerciseLapRecord, ExerciseRepetitionsRecord, ExerciseSessionRecord, FloorsClimbedRecord, HeartRateRecord, HeightRecord, HipCircumferenceRecord, HydrationRecord, LeanBodyMassRecord, MenstruationFlowRecord, NutritionRecord, OvulationTestRecord, OxygenSaturationRecord, PowerRecord, RespiratoryRateRecord, RestingHeartRateRecord, SexualActivityRecord, SleepSessionRecord, SleepStageRecord, SpeedRecord, StepsCadenceRecord, StepsRecord, SwimmingStrokesRecord, TotalCaloriesBurnedRecord, Vo2MaxRecord, WaistCircumferenceRecord, WeightRecord, WheelchairPushesRecord -
ActiveCaloriesBurnedRecord, BasalBodyTemperatureRecord, BasalMetabolicRateRecord, BloodGlucoseRecord, BloodPressureRecord, BodyFatRecord, BodyTemperatureRecord, BoneMassRecord, CervicalMucusRecord, CyclingPedalingCadenceRecord,
DistanceRecord, ElevationGainedRecord, ExerciseEventRecord, ExerciseLapRecord, ExerciseRepetitionsRecord, ExerciseSessionRecord, FloorsClimbedRecord, HeartRateRecord, HeightRecord, HipCircumferenceRecord, HydrationRecord, LeanBodyMassRecord, MenstruationFlowRecord, NutritionRecord, OvulationTestRecord, OxygenSaturationRecord, PowerRecord, RespiratoryRateRecord, RestingHeartRateRecord, SexualActivityRecord, SleepSessionRecord, SleepStageRecord, SpeedRecord, StepsCadenceRecord, StepsRecord, SwimmingStrokesRecord, TotalCaloriesBurnedRecord, Vo2MaxRecord, WaistCircumferenceRecord, WeightRecord, WheelchairPushesRecord II. HealthConnectͷσʔλछผΛܾΊΔ 2. HealthConnectͷσʔλछผʢRecordΠϯλʔϑΣʔεʣʹఆٛ͞Εͨ ৺ഥɺϚΠϯυϑϧωεɺาɺਭɺΤωϧΪʔফඅྔͷΫϥεΛ୳͢… 35 ActiveCaloriesBurnedRecord HeartRateRecord SleepSessionRecord SleepStageRecord StepsRecord TotalCaloriesBurnedRecord -
II. HealthConnectͷσʔλछผΛܾΊΔ 36 3. UpmindΞϓϦͰѻ͏σʔλΛཧղͯ͠ɺHealthConnectͷσʔλछผΛ୳͢ ɾ৺ഥ → 1ؒͷ৺ഥ ɾϚΠϯυϑϧωε →
Ϣʔβ͕࣮ࢪͨ͠վળίϯςϯπʢᛉɺϤΨɺετϨονʣ ɾา → 1ͷา ɾਭ → 1ͷਭ࣌ؒʢਭͷਂ͞আ͘ʣ ɾΤωϧΪʔফඅྔ → Ϣʔβ͕1׆ಈͯ͠ফඅͨ͠ΤωϧΪʔ -
II. HealthConnectͷσʔλछผΛܾΊΔ 37 ɾ৺ഥ → HeartRateRecord ɾϚΠϯυϑϧωε → ExerciseSessionRecord ɹɹɹɹɹɹɹɹɹɹ(
ExerciseType.MEDITATION / YOGA / STRETCHING ) ɾา → StepsRecord ɾਭ → SleepSessionRecord, SleepStageRecord ɾΤωϧΪʔফඅྔ → ActiveCaloriesBurnedRecord, TotalCaloriesBurnedRecord -
II. HealthConnectͷσʔλछผΛܾΊΔ 38 Android / HealthConnect iOS / HealthKit ɹ৺ഥ
HeartRateRecord HKQuantityTypeIdentifier. heartRate ɹ࣮ࢪͨ͠ ɹվળίϯςϯπ ExerciseSessionRecord HKCategoryTypeIdentifier. mindfulSession ɹา StepsRecord HKQuantityTypeIdentifier. stepCount ɹਭ SleepSessionRecord HKCategoryTypeIdentifier. sleepAnalysis ɹফඅΧϩϦʔ ActiveCaloriesBurnedRecord HKQuantityTypeIdentifier. activeEnergyBurned -
II. HealthConnectͷσʔλछผΛܾΊΔ 4. ΞϓϦͰ༻͢ΔݖݶΛ res/values/health_permissions.xml ʹఆٛ͢Δ 39 <resources> <!-- ΞϓϦ͔ΒΞΫηε͢ΔHealthConnectͷσʔλछผͱݖݶ
--> <array name=“health_permissions”> <item>androidx.health.permission.HeartRate.WRITE</item> <item>androidx.health.permission.ActivitySession.WRITE</item> <item>androidx.health.permission.Steps.READ</item> <item>androidx.health.permission.SleepSession.READ</item> <item>androidx.health.permission.ActiveCaloriesBurned.READ</item> </array> </resources> -
III. HealthConnectͷར༻Մ൱ఆ HealthConnectͷݖݶऔಘલʹɺHealthConnectΞϓϦͷར༻Մೳఆ͕ඞཁ • Androidόʔδϣϯ͕9.0Ҏ্͔ → Android 9.0ະຬͷ߹ϔϧεέΞ࿈ܞͰ͖ͳ͍ͷͰɺ࿈ܞUIΛඇදࣔ • HealthConnectΞϓϦ͕ΠϯετʔϧࡁΈ͔
→ ະΠϯετʔϧͷ߹ɺGooglePlayͰΞϓϦμϯϩʔυΛҊ͢Δ ※ ະΠϯετʔϧͷ߹ʹ HealthConnectClient Λੜ͢ΔͱΫϥογϡ͢Δ 40 -
IV. HealthConnectͷݖݶऔಘ 2. ݖݶΛཁٻ͢ΔIntentͷLauncherΛੜ͢Δ 41 val permissionContract: ActivityResultContract< Set<HealthPermission>, Set<HealthPermission>
> = PermissionController.createRequestPermissionResultContract() // JetpackComposeͷ `@Composable` ϝιου val launcher = rememberLauncherForActivityResult(permissionContract) { // TODO: ݖݶͷཁٻ݁Ռ `Set<HealthPermission>` Λݕূ͢Δ } -
IV. HealthConnectͷݖݶऔಘ 3. ݖݶΛཁٻ͢Δը໘ભҠΛ͓͜ͳ͏ 42 val PERMISSIONS = setOf( HealthPermission.createWritePermission(HeartRateRecord::class),
HealthPermission.createWritePermission(ExerciseSessionRecord::class), HealthPermission.createReadPermission(StepsRecord::class), HealthPermission.createReadPermission(SleepSessionRecord::class), HealthPermission.createReadPermission(ActiveCaloriesBurnedRecord::class), ) launcher.launch(PERMISSIONS) -
IV. HealthConnectͷݖݶऔಘ 44 5. ཁٻ݁Ռ͔Βݖݶ͕ڐՄ͞Ε͔ͨݕূ͢Δ val client: HealthConnectClient = HealthConnectClient.getOrCreate(context)
/** @return [PERMISSIONS]ͷݖݶ͕ͯ͢ڐՄ͞Ε͔ͨ */ override suspend fun isAllGranted(): Boolean { return PERMISSION == client.permissionController.getGrantedPermissions(PERMISSION) } /** @return [permissions]ͷݖݶ͕ڐՄ͞Ε͔ͨ */ override suspend fun isGranted(permission: Set<HealthPermission>): Boolean { return permission == client.permissionController.getGrantedPermissions(permission) } -
V. HealthConnectͷσʔλಡΈऔΓ 45 ಡΈऔΓϦΫΤετΛੜͯ͠ɺσʔλΛऔಘ͢Δ /** @return 1ʢ[date]ʣͷา */ suspend fun
readSteps(date: LocalDate): List<StepsRecord> { val start = date.atStartOfDay().truncatedTo(ChronoUnit.HOURS) val end = start.plusDays(1L).minusMinutes(1L) val timeRange = TimeRangeFilter.between(start, end) val request = ReadRecordsRequest( recordType = StepsRecord::class, timeRangeFilter = timeRange, ) return client.readRecords(request).records } -
VI. HealthConnectͷσʔλॻ͖ࠐΈ 46 ॻ͖ࠐΈϦΫΤετΛੜͯ͠ɺσʔλΛऔಘ͢Δ /** ࣮ࢪͨ͠ᛉΛॻ͖ࠐΉ */ suspend fun writeMeditation(
title: String, startTime: LocalDateTime, durationMinutes: Long ) { val zoneOffset = OffsetDateTime.now().offset val session = ExerciseSessionRecord( title = title, exerciseType = ExerciseSessionRecord.ExerciseType.MEDITATION, startTime = startTime.minusMinutes(durationMinutes).toInstant(zoneOffset), startZoneOffset = zoneOffset, endTime = startTime.toInstant(zoneOffset), endZoneOffset = zoneOffset, ) client.insertRecords(listOf(session)) }