LoginSignup
19
11

More than 1 year has passed since last update.

【Flutter】スマホのヘルスケアデータをhealthパッケージを使用して取得する

Posted at

はじめに

社内でFlutterでスマホのヘルスケアデータを取得して表示するまでを試す機会があったので、
今回その手順を記録として残していきます。

healthというパッケージがあるのでこちらを利用してデータにアクセスしていきます。
https://pub.dev/packages/health

こちらのパッケージを使用することで、AndroidはGoogle fit(別途すとあからダウンロードが必要)、
iOSはデフォルトで入っているヘルスケアのデータにアクセスが可能となります。

今回はiOSのヘルスケアからデータを取得し、画面に表示させるまでの記録です。

インストール

pubspec.yaml
dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^1.0.2
  health: ^3.0.4  //追加

まずはパッケージのインストールを行います。
私が行った際の最新バージョンは3.0.4でした。

忘れずにpub getを行います。

セットアップ

ヘルスケアデータにアクセスするためにいくつかセットアップをします。

まずはinfo.plistに以下を追加します。

info.plist
<key>NSHealthShareUsageDescription</key>
<string>We will sync your data with the Apple Health app to give you better insights</string>
<key>NSHealthUpdateUsageDescription</key>
<string>We will sync your data with the Apple Health app to give you better insights</string>

  

次に以下のリンクを参考に、xcodeのtargetsのRunnerよりHealthKitのCapabilityを追加します。

  
以上でセットアップは完了です。

実践

https://pub.dev/packages/health
ひとまずコードに関しては、こちらのページのExampleに記載されているコードをそのままペーストし、動作確認を行いました。

main.dart
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:health/health.dart';
import 'package:helth_package_sample/get_step_screen.dart';
import 'package:permission_handler/permission_handler.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Home(),
    );
  }
}

class Home extends StatefulWidget {
  const Home({Key? key}) : super(key: key);

  @override
  _HomeState createState() => _HomeState();
}

enum AppState {
  DATA_NOT_FETCHED,
  FETCHING_DATA,
  DATA_READY,
  NO_DATA,
  AUTH_NOT_GRANTED
}

class _HomeState extends State<Home> {
  List<HealthDataPoint> _healthDataList = [];
  AppState _state = AppState.DATA_NOT_FETCHED;

  @override
  void initState() {
    super.initState();
  }

  Future fetchData() async {
    /// Get everything from midnight until now
    DateTime startDate = DateTime(2021, 6, 01, 0, 0, 0);
    DateTime endDate = DateTime(2021, 6, 02, 23, 59, 59);

    HealthFactory health = HealthFactory();

    /// Define the types to get.
    List<HealthDataType> types = [
      HealthDataType.STEPS,
      HealthDataType.WEIGHT,
      HealthDataType.HEIGHT,
      HealthDataType.BLOOD_GLUCOSE,
      HealthDataType.DISTANCE_WALKING_RUNNING,
    ];

    setState(() => _state = AppState.FETCHING_DATA);

    /// You MUST request access to the data types before reading them
    bool accessWasGranted = await health.requestAuthorization(types);

    int steps = 0;

    if (accessWasGranted) {
      try {
        /// Fetch new data
        List<HealthDataPoint> healthData =
            await health.getHealthDataFromTypes(startDate, endDate, types);

        /// Save all the new data points
        _healthDataList.addAll(healthData);
      } catch (e) {
        print("Caught exception in getHealthDataFromTypes: $e");
      }

      /// Filter out duplicates
      _healthDataList = HealthFactory.removeDuplicates(_healthDataList);

      /// Print the results
      _healthDataList.forEach((x) {
        print("Data point: $x");
        steps += x.value.round();
      });

      print("Steps: $steps");

      /// Update the UI to display the results
      setState(() {
        _state =
            _healthDataList.isEmpty ? AppState.NO_DATA : AppState.DATA_READY;
      });
    } else {
      print("Authorization not granted");
      setState(() => _state = AppState.DATA_NOT_FETCHED);
    }
  }

  Widget _contentFetchingData() {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Container(
            padding: EdgeInsets.all(20),
            child: CircularProgressIndicator(
              strokeWidth: 10,
            )),
        Text('Fetching data...')
      ],
    );
  }

  Widget _contentDataReady() {
    return ListView.builder(
        itemCount: _healthDataList.length,
        itemBuilder: (_, index) {
          HealthDataPoint p = _healthDataList[index];
          return ListTile(
            title: Text("${p.typeString}: ${p.value}"),
            trailing: Text('${p.unitString}'),
            subtitle: Text('${p.dateFrom} - ${p.dateTo}'),
          );
        });
  }

  Widget _contentNoData() {
    return Text('No Data to show');
  }

  Widget _contentNotFetched() {
    return Text('Press the download button to fetch data');
  }

  Widget _authorizationNotGranted() {
    return Text('''Authorization not given.
        For Android please check your OAUTH2 client ID is correct in Google Developer Console.
         For iOS check your permissions in Apple Health.''');
  }

  Widget _content() {
    if (_state == AppState.DATA_READY)
      return _contentDataReady();
    else if (_state == AppState.NO_DATA)
      return _contentNoData();
    else if (_state == AppState.FETCHING_DATA)
      return _contentFetchingData();
    else if (_state == AppState.AUTH_NOT_GRANTED)
      return _authorizationNotGranted();

    return _contentNotFetched();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
          actions: <Widget>[
            IconButton(
              icon: Icon(Icons.file_download),
              onPressed: () async {
                await Permission.activityRecognition.request().isGranted;
                await fetchData();
              },
            ),
          ],
        ),
        body: Center(
          child: _content(),
        ));
  }
}

  
以下の部分で取得する日の範囲を指定できます。
Exampleのコードではかなりの範囲のデータを取得するようになっていましたので、ひとまず6/1から6/2までの二日間のデータを取得するように変更しました。

 Future fetchData() async {
    /// Get everything from midnight until now
    DateTime startDate = DateTime(2021, 6, 01, 0, 0, 0);
    DateTime endDate = DateTime(2021, 6, 02, 23, 59, 59);

  

こちらのListの中身では取得するデータの種類を定義しています。

 /// Define the types to get.
    List<HealthDataType> types = [
      HealthDataType.STEPS, //歩数
      HealthDataType.WEIGHT, //体重
      HealthDataType.HEIGHT, //身長
      HealthDataType.BLOOD_GLUCOSE, //血糖値
      HealthDataType.DISTANCE_WALKING_RUNNING, //歩行距離
    ];

この他にも取得できるデータはあり、適宜取得したいデータタイプを入れてあげます。
その他のデータタイプはhealthのReadmeに一覧があります。

  

アプリを実行すると最初にこの画面が表示されました。
IMG_0402.PNG

右上のボタンを押すとデータを取得して表示されます。
こちらは歩数の取得結果
IMG_0403.PNG

  
こちらは歩行距離の取得結果
IMG_0404.PNG

  

おわりに

歩数はリアルタイムにデータを取得することも可能でした。
Android側も同時進行で進めていたのですが、認証周りで手こずってしまい今回は自力でデータを取得することは出来ませんでしたが、
Androidでも問題なくデータの取得はできるようです。

19
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
11