Help us understand the problem. What is going on with this article?

Expo Pedometer(歩数計)を使うための設定方法や不具合について

はじめに

Expoでユーザーの歩数を取得するPedometer APIを検証しているのですが、いくつか留意が必要なようなのでまとめてみます。

使用方法

機能自体は非常にシンプルで、以下の3つのみです。

  1. 使用可能かどうかのチェック
  2. 期間を指定して歩数を取得
  3. 歩数のアップデート(差分)を監視

まずはexpo-sensorsをinstall/importします。

import { Pedometer } from "expo-sensors";

1. 使用可能かどうかのチェック

const isAvailable = await Pedometer.isAvailableAsync(); // Boolean

デバイスで歩数計が使用可能かどうかを非同期でチェックします。
iOSの場合はこの時にダイアログが表示されます。

2. 期間を指定して歩数を取得

const end = new Date(); // end: 現在時間
const start = new Date();
start.setDate(end.getDate() - 1); // start: 24時間前

try {
  const { steps } = await Pedometer.getStepCountAsync(start, end);
  console.log(steps); // Number
} catch (e) {
  console.log(e);
}

Date型のstartとendを指定してその間の歩数を非同期で取得します。

3. 歩数のアップデート(差分)を監視

Pedometer.watchStepCount(({ steps }) => {
  console.log(steps); // Number
});

アプリ起動中に端末を持って歩くと、その都度差分の歩数が通知されます。
傾向としては一歩ごとではなくある程度まとまった時間でカウントされるようです。
また、アプリがBackgroundで起動中(他のアプリを起動していたり、ホーム画面に戻ったり)の場合にもカウントされつつ、Foregroundに戻った瞬間に指定したコールバック関数が呼ばれ、Backgroundの状態での歩数が通知されます。

[iOS]歩数の取得制限について

Expoの内部的な実装の話になりますが、iOSネイティブにおいて歩数を取得する際は下記のような2通りの方法があります。

  • HealthKitから取得する

  • CMPedometerを使う

HealthKitの場合はHealthStoreに永続化されたユーザーのデータを取得することができるのですが、ExpoのPedometerでは後者のCMPedometerを使用しています。
この場合、取得することができる過去の歩数データは最大7日間までになっており、Expoにおいても同様の制限があります。

Expoでは現状HealthKitを使用できないため、もしPedometer(CMPedometer)以上の機能が必要な場合はEject後、HealthKitやGoogle Fitを使ったReactNative用パッケージを導入することを検討してください。

パッケージ例(こちらは未検証)
https://github.com/terrillo/rn-apple-healthkit
https://github.com/OvalMoney/react-native-fitness

CMPedometerの7日制限に関するドキュメントはこちら
しかしこの7日という数字も怪しくて、Expoで検証したところ少なくとも私の場合は過去10日間くらい取得可能でした。
それ以前の歩数の取得はエラーにはならず{ steps: 0 }が返ります。

[iOS]権限確認ダイアログについて

iOSではisAvailableAsyncの際に権限の確認ダイアログが表示されます。
App Storeにリリースする際は、このダイアログに表示される説明文を適切に設定する必要があります。(この辺り、詳しくはこちら
app.jsonexpo.ios.infoPlist.NSMotionUsageDescriptionを追加してください。

Expo Clientでは反映されませんが、ビルドして確認するとこんな感じに説明文が表示されます。
PNGイメージ 43.png

app.json(部分)
{
  "expo": {
    "ios": {
      "supportsTablet": true,
      "bundleIdentifier": "バンドル名",
      "infoPlist": {
        "NSMotionUsageDescription": "[テスト文言]歩数を取得するためにモーション情報を使用します"
      }
    }
  }
}

[Android]getStepCountAsyncが使用できない

ExpoのSDK34以降、AndroidにおいてgetStepCountAsyncが使用できない問題(というか変更)が発生しています。
Githubのissueだとこの辺り。

[expo-sensors] Pedometer unable to query steps, documentation unclear/contradictory
https://github.com/expo/expo/issues/4895

現時点では完全かつ安全に解決する方法は無いようですが、上記のissueでも提案されている通り、SDK33のexpoパッケージからPedometerをimportして無理矢理使用することが可能です。
後述するGoogle OAuth Client IDの取得に関する検証の際にはこの方法で使用しています。

package.json(部分)
"dependencies": {
  "expo": "~36.0.0",
  "expo-legacy": "npm:expo@33.0.0",

SDK33以前ではexpo-sensorsに分離されていません。
このようにSDK33のexpoを別名でインストールし、Pedometerをインポートします。

import { Pedometer } from "expo-legacy";

const end = new Date();
const start = new Date();
start.setDate(end.getDate() - 1);

try {
  const { steps } = await Pedometer.getStepCountAsync(start, end);
  console.log(steps);
} catch (e) {
  console.log(e);
}

なぜSDK34以降使用できなくなっているのか

SDK33→SDK34において、Androidでの歩数の取得方法が変更されているようです。
SDK33ではGoogle Fitness(Google Fit)からユーザーのデータを取得しています。
このGoogle Fitでの実装に何かしら問題があったのかわかりませんが、SDK34以降のバージョンではAndroidのSensorEventを使用して歩数をwatchのみしており、getStepCountAsyncは実装されていません。

ソースを比べてみたい方はこちらをどうぞ。

SDK33
https://github.com/expo/expo/blob/4311483ed8d2f12a5401dc1420e1503821681013/android/versioned-abis/expoview-abi33_0_0/src/main/java/abi33_0_0/host/exp/exponent/modules/api/PedometerModule.java

SDK36(Latest)
https://github.com/expo/expo/blob/4311483ed8d2f12a5401dc1420e1503821681013/android/versioned-abis/expoview-abi36_0_0/src/main/java/abi36_0_0/expo/modules/sensors/modules/PedometerModule.java
getStepCountAsyncGetting step count for date range is not supported on Android yet.というエラーメッセージを返すようになっています。

[2020/04/03追記]
SDK37現在でも変わらずです。

[Android]スタンドアロンアプリでの設定方法

上述した通りSDK33まではAndroidではGoogle Fitを使用してユーザーに紐づかれた歩数を取得しているわけですが、このデータの使用には公式ドキュメントに書かれているとおり、Android OAuth Clientとしての登録が必要になります。
ここでは、Android OAuth Clientとしての設定方法について説明します。

なお、今現在はGoogle Fitを使わない仕様になっているので、このドキュメントに今もこの説明が入っているのは誤りです。
そもそもgetStepCountAsyncが使えないので嬉しくないですが、通常この設定は必要ありません

上述した方法で無理矢理getStepCountAsyncを使う場合や、将来的(希望的観測)にPedometerがGoogle Fitを使ってちゃんと実装し直された場合のために設定を検証してみます。

1. Expo Client上で確認

先ほどの方法でSDK33からPedometerをインポートし、ドキュメントのソースをちょっと変更して画面を作ります。

App.js
import React from "react";
import { Pedometer } from "expo-legacy";
import { StyleSheet, Text, View, Button } from "react-native";

export default class App extends React.Component {
  state = {
    isPedometerAvailable: "checking",
    pastStepCount: 0,
    currentStepCount: 0
  };

  componentWillUnmount() {
    this._subscription && this._subscription.remove();
    delete this._subscription;
  }

  init = async () => {
    let isPedometerAvailable = false;
    try {
      isPedometerAvailable = await Pedometer.isAvailableAsync();
      this.setState({ isPedometerAvailable: String(isPedometerAvailable) });
    } catch ({ message }) {
      this.setState({ isPedometerAvailable: `Could not get isPedometerAvailable: ${message}` });
    }
    if (isPedometerAvailable) {
      this._subscription = Pedometer.watchStepCount(result => {
        this.setState({
          currentStepCount: result.steps
        });
      });
      const end = new Date();
      const start = new Date();
      start.setDate(end.getDate() - 1);
      try {
        const { steps } = await Pedometer.getStepCountAsync(start, end);
        this.setState({ pastStepCount: steps });
      } catch ({ message }) {
        this.setState({ pastStepCount: `Could not get stepCount: ${message}` });
      }
    }
  };

  render() {
    return (
      <View style={styles.container}>
        <Text>
          Pedometer.isAvailableAsync(): {this.state.isPedometerAvailable}
        </Text>
        <Text>
          Steps taken in the last 24 hours: {this.state.pastStepCount}
        </Text>
        <Text>Walk! And watch this go up: {this.state.currentStepCount}</Text>
        <Button title="INIT" onPress={this.init} />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: 15,
    alignItems: "center",
    justifyContent: "center"
  }
});

Android端末のExpo Clientで確認。このような画面になります。
android
「INIT」ボタンを押し、isAvailableAsyncが呼ばれると、
android
android
このようなGoogleの承認画面(「OAuth 同意画面」)が表示され、「Allow」ボタンで許可すると、歩数データの取得が可能になります。
android

上の画面をみて推察できる通り、Expo Clientを使っている状態だとこの時のAndroid OAuth Client登録や同意画面の設定はExpo Clientのものが使われています。
これを実際にスタンドアロンアプリとしてビルドしPlay Storeに申請する際には各自での設定が必要になります。

ちなみにこの時点で、以下のような不具合があるのがわかりました。

  • 同意画面をキャンセルした時にisAvailableAsyncのPromise(返り値)が終了しない
  • 故にキャンセルされたことが検知できず、再度isAvailableAsyncを呼んでも何も起きない
  • 端末の設定からExpoアプリの「Physical activity(ボディーセンサー)」権限を不許可にすると、同意画面の際にアプリがクラッシュする
  • watchStepCountで取得できる歩数が差分の歩数ではなく累算された値になっている

おそらくSDK34以降にGoogle Fitnessでの実装を取りやめているのはこのような不安定さからでしょう。
繰り返しになってしまいますがgetStepCountAsyncが使えず内部的な実装方法が変わっているため、上記の問題はSDK36現在では発生しません。

2. スタンドアロンアプリでビルド

不具合は一旦置いておき、スタンドアロンアプリでビルドしてみます。
app.jsonでパッケージ名を設定してください。確認なので適当でいいですが実際は独自ドメインのものを使用します。

app.json(部分)
{
  "expo": {
    "android": {
      "package": "パッケージ名"
    }
  }
}
$ expo build:android

Expo Cliの流れに沿ってビルドします。

Google Fitを使用するためのAndroid OAuth Clientの登録には、アプリの証明書に含まれるSHA-1の指紋(Fingerprint)情報が必要です。

公式ドキュメント:Get an OAuth 2.0 Client ID
https://developers.google.com/fit/android/get-api-key

Expoでのビルドの場合は証明書をお任せで作成してもらうことになるので、ビルド後にapkファイルの中から指紋を抽出するという流れになります。
apkファイルをダウンロードしたら、下記のコマンドを使用します。
JDKに含まれるkeytoolコマンドがない場合は適宜インストールしてください。

$ cd ~/Downloads
$ keytool -list -printcert -jarfile <ダウンロードしたapkファイルのパス>.apk | grep SHA1 | awk '{ print $2 }'
BB:0D:AC:74:D3:21:E1:43:67:71:9B:62:91:AF:A1:66:6E:44:5D:75

こんな感じの文字列が表示されたら、コピーしておきます。
証明書は一つのExpoプロジェクトをビルドする際に最初に作成され、その後の同アプリのビルドでは同じものが使われます。
指紋情報も同アプリで一度取得したら変更は無いはずです。

3. OAuth Clientの設定

あとは上記のドキュメントに沿って設定をすればOKです。
Get_an_OAuth_2_0_Client_ID_ _ _Google_Fit_ _ _Google_Developers.png
Fitness APIを使用するための設定の流れを自動的に案内してくれるリンクがあります。これをクリックし、
Googleプロジェクトを選択します。この時点でアプリに使用するGoogleプロジェクトが存在していない場合は新たに作成してください。
Firebaseで作成したプロジェクトもここに表示され、選択することができます。
API_を有効にする_-_Google_API_コンソール.png
認証情報ウィザード_-_Test_Project_-_Google_API_コンソール.png
「プロジェクトへの認証情報の追加」画面になったら、「Fitness API」を選択されているのを確認し、

  • 「APIを呼び出す場所」 → 「Android」
  • アクセスするデータの種類 → 「ユーザーデータ」

を選択して次に進みます。
この時点で、Googleプロジェクトに紐づかれたOAuth 同意画面が無い場合は設定が求められます。
同意画面_-_Test_Project_-_Google_API_コンソール.png
同意画面_-_Test_Project_-_Google_API_コンソール.png

App Storeに申請する際はアイコン、連絡先、ホームページURL、プライバシーポリシーURL等の情報を入力する必要があるようです。その際はこの同意画面をGoogleへ「確認のため送信」します。

同意画面の設定が終わったら(検証のみであればアプリ名くらいでよいはず。「確認のため送信」はせず「保存」だけします。)、クライアントIDの作成画面に進みます。
OAuth_クライアント_ID_の作成_-_Test_Project_-_Google_API_コンソール.png
このような画面になったら、クライアントの名前(アプリ名等)、先ほど取得した指紋の文字列、app.jsonで設定したパッケージ名を入力して「作成」します。
認証情報_–_API_とサービス_–_Test_Project_–_Google_API_Console.png
同意画面の確認申請・公開までにログインが100回までに制限されるとのことです。
認証情報_–_API_とサービス_–_Test_Project_–_Google_API_Console.png
このようにプロジェクトの認証情報にOAuth 2.0 クライアントIDが追加されたら成功です。

このクライアントIDはExpoのGoogle認証機能でも使用することになります。
Pedometer(SDK33以下)の場合はこのIDをアプリ側のソースでパラメータに使用するといったことはなく、パッケージ名で紐づかれていれば問題ありません。

4. スタンドアロンアプリで確認

端末をUSBで繋いで、ADBコマンドを使ってインストールしてみます。

$ adb install <ダウンロードしたapkファイルのパス>.apk

起動し「INIT」ボタンを押してisAvailableAsyncが呼ばれると、まずはGoogleアカウントの選択画面が表示されます。
端末の言語設定を英語にしているので英語になっていますが、あとで日本語にして確認してみます。
android
ここでexpo-pedometer-exampleとなっているのはapp.jsonexpo.nameで設定されたアプリ名(デフォルトではプロジェクトIDとイコール)です。
アプリアイコンはassetsディレクトリに入っているデフォルトのアイコンが表示されています。
android
続いて権限を承認する画面になります。ここで歩数計テストとなっているのは、「OAuth 同意画面」の設定で入力された「アプリケーション名」です。
android
続いて最終確認の画面。「Allow」をタップすると、
android
getStepCountAsyncを使用して24時間以内の歩数が取得できました。

アプリ名・アイコン・日本語設定で確認

app.jsonでアプリ名を設定(OAuth 同意画面の設定に合わせ「歩数計テスト」)し、アイコンを独自のものに変更、端末の言語設定を日本語にした状態で再度確認しました。
android
android
android
設定は問題なく反映されています。
以上でOAuth Client登録について大体把握できました。

まとめ

基本動作の他、iOSではNSMotionUsageDescriptionの設定を、AndroidではGoogle OAuth Client登録の方法を確認しました。
現状、iOS・Android共に満足に使用できるのはアプリ起動中に歩数を監視する機能のみとなっていて、それ以上の機能を求める場合には難しいようでした。
ExpoアプリのGoogle OAuth Client登録についてについては基本的に必要ありませんが、SDK33と同様の機能をAndroidで実装するにはGoogle Fitを使うしかない(と思われる)ので、Expoの今後のバージョンアップによって登録が必要になる可能性があります。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした