##はじめに
Expoでユーザーの歩数を取得するPedometer APIを検証しているのですが、いくつか留意が必要なようなのでまとめてみます。
##使用方法
機能自体は非常にシンプルで、以下の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.json
のexpo.ios.infoPlist.NSMotionUsageDescription
を追加してください。
Expo Clientでは反映されませんが、ビルドして確認するとこんな感じに説明文が表示されます。
{
"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の取得に関する検証の際にはこの方法で使用しています。
"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
は実装されていません。
ソースを比べてみたい方はこちらをどうぞ。
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
getStepCountAsync
はGetting 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をインポートし、ドキュメントのソースをちょっと変更して画面を作ります。
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で確認。このような画面になります。
「INIT」ボタンを押し、isAvailableAsync
が呼ばれると、
このようなGoogleの承認画面(「OAuth 同意画面」)が表示され、「Allow」ボタンで許可すると、歩数データの取得が可能になります。
上の画面をみて推察できる通り、Expo Clientを使っている状態だとこの時のAndroid OAuth Client登録や同意画面の設定はExpo Clientのものが使われています。
これを実際にスタンドアロンアプリとしてビルドしPlay Storeに申請する際には各自での設定が必要になります。
ちなみにこの時点で、以下のような不具合があるのがわかりました。
- 同意画面をキャンセルした時に
isAvailableAsync
のPromise(返り値)が終了しない - 故にキャンセルされたことが検知できず、再度
isAvailableAsync
を呼んでも何も起きない - 端末の設定からExpoアプリの「Physical activity(ボディーセンサー)」権限を不許可にすると、同意画面の際にアプリがクラッシュする
-
watchStepCount
で取得できる歩数が差分の歩数ではなく累算された値になっている
おそらくSDK34以降にGoogle Fitnessでの実装を取りやめているのはこのような不安定さからでしょう。
繰り返しになってしまいますがgetStepCountAsync
が使えず内部的な実装方法が変わっているため、上記の問題はSDK36現在では発生しません。
2. スタンドアロンアプリでビルド
不具合は一旦置いておき、スタンドアロンアプリでビルドしてみます。
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です。
Fitness APIを使用するための設定の流れを自動的に案内してくれるリンクがあります。これをクリックし、
Googleプロジェクトを選択します。この時点でアプリに使用するGoogleプロジェクトが存在していない場合は新たに作成してください。
Firebaseで作成したプロジェクトもここに表示され、選択することができます。
「プロジェクトへの認証情報の追加」画面になったら、「Fitness API」を選択されているのを確認し、
- 「APIを呼び出す場所」 → 「Android」
- アクセスするデータの種類 → 「ユーザーデータ」
を選択して次に進みます。
この時点で、Googleプロジェクトに紐づかれたOAuth 同意画面が無い場合は設定が求められます。
App Storeに申請する際はアイコン、連絡先、ホームページURL、プライバシーポリシーURL等の情報を入力する必要があるようです。その際はこの同意画面をGoogleへ「確認のため送信」します。
同意画面の設定が終わったら(検証のみであればアプリ名くらいでよいはず。「確認のため送信」はせず「保存」だけします。)、クライアントIDの作成画面に進みます。
このような画面になったら、クライアントの名前(アプリ名等)、先ほど取得した指紋の文字列、app.json
で設定したパッケージ名を入力して「作成」します。
同意画面の確認申請・公開までにログインが100回までに制限されるとのことです。
このようにプロジェクトの認証情報にOAuth 2.0 クライアントIDが追加されたら成功です。
このクライアントIDはExpoのGoogle認証機能でも使用することになります。
Pedometer(SDK33以下)の場合はこのIDをアプリ側のソースでパラメータに使用するといったことはなく、パッケージ名で紐づかれていれば問題ありません。
4. スタンドアロンアプリで確認
端末をUSBで繋いで、ADBコマンドを使ってインストールしてみます。
$ adb install <ダウンロードしたapkファイルのパス>.apk
起動し「INIT」ボタンを押してisAvailableAsync
が呼ばれると、まずはGoogleアカウントの選択画面が表示されます。
端末の言語設定を英語にしているので英語になっていますが、あとで日本語にして確認してみます。
ここでexpo-pedometer-example
となっているのはapp.json
のexpo.name
で設定されたアプリ名(デフォルトではプロジェクトIDとイコール)です。
アプリアイコンはassets
ディレクトリに入っているデフォルトのアイコンが表示されています。
続いて権限を承認する画面になります。ここで歩数計テスト
となっているのは、「OAuth 同意画面」の設定で入力された「アプリケーション名」です。
続いて最終確認の画面。「Allow」をタップすると、
getStepCountAsync
を使用して24時間以内の歩数が取得できました。
アプリ名・アイコン・日本語設定で確認
app.json
でアプリ名を設定(OAuth 同意画面の設定に合わせ「歩数計テスト」)し、アイコンを独自のものに変更、端末の言語設定を日本語にした状態で再度確認しました。
設定は問題なく反映されています。
以上でOAuth Client登録について大体把握できました。
まとめ
基本動作の他、iOSではNSMotionUsageDescription
の設定を、AndroidではGoogle OAuth Client登録の方法を確認しました。
現状、iOS・Android共に満足に使用できるのはアプリ起動中に歩数を監視する機能のみとなっていて、それ以上の機能を求める場合には難しいようでした。
ExpoアプリのGoogle OAuth Client登録についてについては基本的に必要ありませんが、SDK33と同様の機能をAndroidで実装するにはGoogle Fitを使うしかない(と思われる)ので、Expoの今後のバージョンアップによって登録が必要になる可能性があります。