2019/01/25 追記: Expo v32.0.0 でバックグラウンド位置情報使えるようになったっぽいですね。暇があればExpoでの検証記事も書こうかなと思います。
とある事情でバックグラウンドで位置情報を保存する機能が必要なアプリのお手伝いをするようになったので、React Native でその機能を実装した過程を書こうかと思います。書いてる途中でアホほど長くなってしまってもはや最速ではないなと思いました。
スムーズに進めたい方
ベータテスト配布のために Apple Developer Program に登録(11,800円/年)する必要があります。
登録からアクティベーションに約1日かかるので、待つのが面倒な方は先に登録しておくと良いかと思います。
対象
- iOS アプリ作りたい人
- Web エンジニア
- 趣味以上
- App Store 配布未満
今回は、ベータテスト配布やそれなりに将来を見据えた CI による自動化も含めた方法になるので、どうしてもお金がかかります。なので、「ちょっと勉強したいなー」くらいのモチベーションの人には不向きかもしれません。
あとは、単純に今回手伝う範囲に App Store へのリリースが含まれていないので、経験していないものを想像で書くのも良くないと思いベータ版配布までの方法にしました。ただ、リリースもすぐにできる環境は整えているような気がします。
Web エンジニアが Web エンジニア向けに書いているので、間違っているところやアプリ開発経験がある方には少し丁寧すぎるところがあるかもしれません。
実装前の調査
そもそも僕は Web エンジニアで「こういうアプリ作りたいです」と言われて、浅知恵で「PWA とかでできるかなあ」というところから始まって、結局 React Native で react-native-background-geolocation というプラグインを使うに至ったので、その経緯を一応書いておきます。
PWAでは無理
PWA の説明で「バックグラウンドで〜」「位置情報が〜」みたいなリストや表が出てくるせいで何でもできる気がしてくるんですが、思ったよりそんなことないみたいです。
PWAはバックグラウンドでずっと動くわけではない
PWA で言われるバックグラウンド云々はサービスワーカーの機能を指していますが、サービスワーカーは基本何かを契機に動くもので、その処理はブラウザを閉じた状態では数分程度で止まってしまいます。
試しにサービスワーカーの waitUntil() に何時間単位のクソ長い setTimeout
の Promise を渡して、ブラウザを閉じてどのくらい動き続けるか試したところちょうど5分くらいで止まりました(10秒おきにFirebaseに保存する処理を実行していました)。
そもそもサービスワーカー内で位置情報取得できない
PWA!バックグラウンド!位置情報!という並び的に、バックグラウンドでも位置情報アクセスできそうな気がしますが、今の所そんなことはないです。
とかを見る感じ、いつか使えるようになるといいね、くらいにしか思えない雰囲気です。
では React Native でやる、ただ Expo では無理
そうなるともうアプリを作るしかなくなってしまいますが、 アプリ開発経験がない僕が短時間で学べそうなのは React Native だったのでとりあえず React Native にしました。
React Native 開発には Expo という超便利ツール(QRコード読み込んでアプリをテストできたりする)の利用が推奨されているのですが、バックグラウンド処理にはまだ対応していませんでした。
ただ、今年の8月にバックグラウンドでの位置情報トラッキング、つい先月にバックグラウンド処理 の Feature Request が IN PROGRESS
になったので、どれくらい先かは不明ですが Expo で作れるようになる未来はあるっぽいです。
2019/01/25 追記: 使えるようになりました。
こんな感じの過程を経て、Expo なしの React Native で開発をすることが決まりました。
機能
- Google アカウントでログインできる
- 位置情報を移動をしている間だけログインしたアカウントの ID を key に保存する(Firebase)
- バックグラウンドでも動く
環境・利用するもの
- Xcode: Version 10.1 (10B61)
- Node.js: 10.14.0 (11.0.* では出来ませんでした)
- npm: 6.4.1
- React Native: 0.57.7
- React Native Firebase: ^5.1.1
- react-native-background-geolocation: ^2.14.2
- Cloud Functions for Firebase: ^2.1.0 (Node v8 の方で利用)
- Bitrise: CI サービス
- HockeyApp: アプリ配布サービス
React Native プロジェクトの開始
公式のGetting Started の通りに進めます。
ただ最後の react-native run-ios
はビルドが走ってしまい、あとでビルドターゲットを変える段階でビルドエラーが出たりした気がしなくもないので、やらないほうがいいかなあと思います。
やってて思いましたが React Native 開発で総じて言えることは**「余計なことはしない」**です。何かの拍子で歯車が噛み合わなくなるとビルドエラーから抜け出せなくなります。。こまめに git にコミットしてエラーが出たらすぐに戻れるようにしておいておくと良いです。
Expo は利用しないので Quick Start
ではなく、 Building Projects with Native Code
の方で始めます。
最終的に App.js
やら ios/
やら android/
やらが入ったプロジェクトができると思います。
ios/プロジェクト名.xcodeproj
が Xcode で開くファイルになります(あとで ios/プロジェクト名.xcworkspace
を開くように切り替えますが)
iOS アプリ開発の基礎知識
実機で動作確認するステップに入りたいのですが、その前に iOS アプリ開発をやる上で最低限理解しておきたい Provisioning Profile というものがあります。実機でアプリを起動するためのビルドにはこの Provisioning Profile が必須になります。
そもそもなぜこれが必要かというと、要は Apple がやりたいことは
- アプリを正しく識別したい
- アプリの開発元を明らかにしたい
- アプリを起動できる端末を限定したい
かと思います。そのため Provisioning Profile には主に以下の3つが含まれます。
- App Id: そのアプリで利用可能な識別子。ここで指定した ID だけがビルド時の Bundle Identifier に利用できます。同じアプリでも別のアプリとして端末にインストールしたい場合は、Bundle Identifier の末尾を変えてビルドすることがあるので、テスト用途でワイルドカードで指定することもあります。
- Certificates: 証明書。要は「Apple に登録された誰それのアカウントが開発している」ことを証明するもの。
- Devices: デバイスのUUID一覧。App Store からダウンロードされる場合を除き、ここに登録されたデバイスでしか起動できません。
Provisioning Profile は3種類(エンプラは除く)あって用途によって使い分けます。
Development Provisioning Profile: 開発用
主に USB で繋いだデバイスでテストをする場合に使います。Xcode に Apple アカウントでログインしたら自動生成してくれるので、USB 実機テストのときはこの Provisioning Profile を意識することはないと思います。
Development Provisioning Profile の場合、端末側がデベロッパを「信頼」をした場合のみ利用することができます(多分)。信頼は、開発用 Mac と同じネットワークにつないだ状態(?)で iPhone の「設定」 > 「一般」 > 「デバイス管理」から出来ます。
Ad Hoc Provisioning Profile: ベータテスト用
こちらはベータテスト用で
- Certificates
- Devices
- App Id
- ↑の3つを使った Ad Hoc Provisioning Profiles
を Apple Developer の管理画面で作ってダウンロードする必要があります。
ダウンロードした Provisioning Profile でビルド、アーカイブすることで、 .ipa ファイルが生成され任意のデバイスに配布ができるようになりますが、Devices で指定したデバイスでしか起動できません。
※厳密な図はこちらの記事をご覧ください(正確な動作条件も記載されています、とてもわかりやすい)。
App Store Provisioning Profile: App Store 公開用
Ad Hoc と同じような手順で App Store 用の Provisioning Profile を作成します。
Provisioning Profile をダウンロードして同じようにビルドをして、App Store に登録やら申請やらをして無事配信が開始されれば、すべてのデバイスが App Store からダウンロードして起動できるようになります。
実機でまずは動作確認
Provisioning Profile の説明が終わったので、今の状態の React Native プロジェクトを実機で起動してみましょう。
ios/プロジェクト名.xcodeproj
を開いて Running On Device・React Native の通りにやればできるかと思います。以下のような状態になって左上の再生マークのボタンを押すと起動できるかと思います。
「Appが検証できません」と出た場合は、Development Provisioning Profileの項でも書いたように iPhone の「設定」 > 「一般」 > 「デバイス管理」から信頼してください。
成功すると、実機で以下のような画面が出ます。
React Native Firebase の導入
実機確認ができたので、必要なライブラリを導入していきます。やってみた感じ React Native Firebase を最初に入れないとエラーが出てしまいがちだったので、最初に入れます。
Bundle Identifier の設定
その前に Firebase のセットアップで Bundle Identifier を指定するステップがあるので、Xcode で設定しておきます。サイドバーの最上位の「プロジェクト名」を選択して、General タブを押すと出てきます。
ID はこちらのスライドを参考につけます。
Firebase のセットアップ
Firebase でプロジェクトを作成します。作成が終わったら、Add Firebase to your iOS App でセットアップを始めます。
–− React Native Firebase - Simple Firebase integration for React Native
iOS バンドル ID を入力して、設定ファイル(GoogleService-Info.plist
)をダウンロードすれば良いので、ステップ2まで大丈夫です。
react-native-firebase のインストール
ダウンロードが終わったらプロジェクトに戻って react-native-firebase
をインストールします。
$ npm install --save react-native-firebase
あとは、 iOS Installation | React Native Firebase を進めます。注意点は以下です。
- 1.1 Setup GoogleService-Info.plist では、Finder でファイルをフォルダに入れるのではなくちゃんと Xcode 上で追加します
-
1.3 Install Firebase Library では Cocoapods を使います
- どうせ使うので
Podfile
にpod 'Firebase/Auth', '~> 5.11.0'
も追加しておきましょう -
pod install
したあとは.xcodeproj
ではなく.xcworkspace
を開いて Xcode を起動するようにしましょう
- どうせ使うので
-
2. React Native Firebase Installation Recommended installation では
react-native link react-native-firebase
を使います
ちなみに ios/Podfile
は以下のようになります。
platform :ios, '9.0'
target 'プロジェクト名' do
pod 'Firebase/Core', '~> 5.11.0'
pod 'Firebase/Auth', '~> 5.11.0'
target 'プロジェクト名Tests' do
inherit! :search_paths
end
end
target 'プロジェクト名-tvOS' do
target 'プロジェクト名-tvOSTests' do
inherit! :search_paths
end
end
確認
インストールが終わらせたら、 App.js
で匿名認証を試してみます。
import React from 'react';
import { View, Text } from 'react-native';
import firebase from 'react-native-firebase';
class App extends React.Component {
constructor() {
super();
this.state = {
isAuthenticated: false,
};
}
componentDidMount() {
firebase.auth().signInAnonymously()
.then(() => {
this.setState({
isAuthenticated: true,
});
});
}
render() {
if (!this.state.isAuthenticated) {
return null;
}
return (
<View>
<Text>Welcome to my awesome app!</Text>
</View>
);
}
}
export default App;
実行前に Firebase コンソールで匿名認証を有効化しておく必要があります。
Google ログインできるようにする
匿名認証が正常に動作したら、Google ログインを実装します。
React Native Firebase のドキュメントの Google ログインの部分 をそのままやります。
まず、react-native-google-signin をインストールします。
npm install --save react-native-google-signin
そして、iOS guide を参考にインストールを完了します。
冒頭に書いてあるように node_modules/react-native-google-signin
の中にある RNGoogleSignin.podspec
を削除してから
react-native link react-native-google-signin
をしましょう。あとは書いてある通りに進めたら大丈夫です。ちなみに Install Google Sign In SDK は With Cocoapods の方でやりました。
書いてあるステップがすべて終わったら、JavaScript のコードに追加しましょう。GoogleSignIn.js
はドキュメントにあるやつをそのままコピペしてます。
import { GoogleSignin } from "react-native-google-signin";
import firebase from "react-native-firebase";
export const googleLogin = async () => {
try {
await GoogleSignin.configure();
const data = await GoogleSignin.signIn();
const credential = firebase.auth.GoogleAuthProvider.credential(
data.idToken,
data.accessToken
);
const currentUser = await firebase
.auth()
.signInAndRetrieveDataWithCredential(credential);
console.info(JSON.stringify(currentUser.user.toJSON()));
} catch (e) {
console.error(e);
}
};
App.js
でボタンを押してログインできるようにします。
import React from 'react';
import { View, Button } from 'react-native';
import firebase from 'react-native-firebase';
import { googleLogin } from "./GoogleSignIn";
class App extends React.Component {
constructor() {
super();
this.state = {
isAuthenticated: false,
};
}
componentDidMount() {
this.unsubscriber = firebase.auth().onAuthStateChanged(user => {
if (!user) {
this.setState({
isAuthenticated: false
});
return;
}
this.setState({
isAuthenticated: true
});
}
componentWillUnmount() {
if (this.unsubscriber) {
this.unsubscriber();
}
}
_signIn() {
googleLogin();
}
_signOut() {
firebase.auth().signOut();
}
render() {
return (
<View>
{!this.state.isAuthenticated && <Button onPress={this._signIn} title="Sign in" />}
{this.state.isAuthenticated && (
<Button onPress={this._signOut} title="Sign out" />
)}
</View>
);
}
}
export default App;
ボタンを押すと Google アカウントでログインする画面が出てきて、ログインができるようになります。
実行する前に、Firebase のコンソールから Google 認証を有効にしておく必要があります。
react-native-background-geolocation を導入する
react-native-background-geolocation というバックグラウンドで位置情報を送信してくれるプラグインをインストールします。1点制限があって、 Android でリリースビルドするには $299 かかります。Android 版を出さないなら関係ないです。
これとは別に、個人?が作っている react-native-mauron85-background-geolocation というライブラリもあります。ただ、試してみましたがやはり前者が安定している、というかこっちは使い物にならなかったです。
では、インストールをしていきます。
npm install --save react-native-background-geolocation
そして、iOS Manual Instllation を進めます(iOS Installation with react-native link でも良いと思いますが、react-native-google-signin
と同じように node_modules
内にある link するモジュールの .podspec
ファイルを削除してから react-native link
するようにしましょう)。
インストールが完了したら JavaScript のコードを変更します。
import React, { Component } from "react";
import { View, Button } from "react-native";
import firebase from "react-native-firebase";
import BackgroundGeolocation from "react-native-background-geolocation";
import { googleLogin } from "./GoogleSignIn";
export default class App extends Component {
constructor() {
super();
this.state = {
user: null,
inited: false
};
}
componentDidMount() {
this.unsubscriber = firebase.auth().onAuthStateChanged(user => {
if (!user || !user.uid) {
this.setState({ user: null });
BackgroundGeolocation.stop(); // トラッキングを停止する
return;
}
this.setState({ user });
if (this.inited) {
BackgroundGeolocation.start(); // トラッキングを開始する
return;
}
BackgroundGeolocation.ready( // トラッキングの設定をする
{
desiredAccuracy: BackgroundGeolocation.DESIRED_ACCURACY_HIGH, // 正確さの度合い
distanceFilter: 10, // 10m以上移動してトラッキングされる
debug: false, // false だとデバッグ用の通知を OFF にする
stopOnTerminate: false, // false だとアプリを終了させても動くようになる
url: "...あとで Cloud Functions のURLを入れる...",
autoSync: true, // あとで説明
autoSyncThreshold: 5, // あとで説明
batchSync: true, // あとで説明
params: { // POST リクエストに付け足したいパラメータ
userId: user.uid
}
},
state => {
this.setState({ inited: true });
BackgroundGeolocation.start(); // トラッキングを開始する
}
);
});
}
componentWillUnmount() {
if (this.unsubscriber) {
this.unsubscriber();
}
}
_signIn() {
googleLogin();
}
_signOut() {
firebase.auth().signOut();
}
render() {
return (
<View>
{!this.state.user && <Button onPress={this._signIn} title="Sign in" />}
{!!this.state.user && (
<Button onPress={this._signOut} title="Sign out" />
)}
</View>
);
}
}
注意点
このライブラリの思想として、できるだけバッテリーをセーブするために不必要なトラッキングはしないように作られています。
とてもありがたい設計ですが、デバッグをするときに少し困ることがときがあるので注意点をいくつか紹介します。
stationary モードについて
iOS だと同じところにとどまっていると stationary というモードになります。このモードになるとこのモードを抜け出すまでトラッキングが開始しません。
ドキュメントを見る限り、stationaryRadius が最低値の 25 の場合、200m 以上動かないとモードから抜け出さないようです。
トラッキングする間隔
トラッキングする間隔は distanceFilter で指定できます。distanceFilter は距離をメートルで指定できて、指定した距離以上移動しない限りトラッキングしません。
ただ、デフォルトで「移動速度によって distanceFilter を変化させる」設定になっているので、自転車や車に乗ると指定した distanceFilter より長くなることに注意しましょう。
HTTP リクエストを送るタイミング
autoSync を true にすると(デフォルトで true です)トラッキングするたびに位置情報を POST リクエストしてくれます。
ただこれだと結構頻繁にリクエストを送ってしまうので autoSyncThreshold で何回に一回リクエストするかを指定できます。例えば autoSyncThreshold を5に設定すると5回トラッキングされて初めてリクエストします。
もうひとつ、このままだと5回トラッキングされたあと、一気に5回リクエストされます。
そこで、batchSync を true にすると、5つの位置情報を配列にして1回だけリクエストしてくれるようになります。
これまでの話をざっくり図にすると以下のようになります。
Cloud Functions の導入
良い感じに位置情報をリクエストする仕組みまで作ったので、リクエスト先を作ります。
Cloud Functions で HTTPS リクエストを受け付けるようにして、Firestore に保存するようにします。
Cloud Functions のセットアップ
Firebase CLI を入れてセットアップしましょう。
npm install -g firebase-tools
firebase login
プロジェクトにて
firebase init
をやると
❯◯ Database: Deploy Firebase Realtime Database Rules
◯ Firestore: Deploy rules and create indexes for Firestore
◯ Functions: Configure and deploy Cloud Functions
◯ Hosting: Configure and deploy Firebase Hosting sites
◯ Storage: Deploy Cloud Storage security rules
と出てくるので Firestore と Functions を選択して進めましょう。最終的に色々ファイルが生成されます。
Firestore に保存するコードを書く
functions/index.js
で受け取った配列を Firestore に保存するコードを書きます(今回の制約上 userId をトークン化せずに生で渡して処理しているので、userId まわりのセキュリティルールは慎重に設定しましょう)。
なお、location
データのスキーマはこちらにあります。
const functions = require("firebase-functions");
const admin = require("firebase-admin");
const serviceAccount = require("./serviceAccount.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://__YOUR_FIREBASE_APPID__.firebaseio.com"
});
const db = admin.firestore();
const settings = { timestampsInSnapshots: true };
db.settings(settings);
exports.saveLocation = functions.https.onRequest(async (req, res) => {
if (!req.body.location || req.body.location.length < 1 || !req.body.userId) {
res.status(400).send("Bad Request");
return;
}
const { userId, location } = req.body;
try {
for (let i = 0; i < location.length; i++) {
let {
coords: { latitude, longitude },
timestamp
} = location[i];
await db.collection(`users/${userId}/locations`).add({
latitude,
longitude,
timestamp: new Date(timestamp)
});
}
res.send("Success");
} catch (e) {
console.log(e);
res.status(400).send("Bad Request");
}
});
この例では async, await を使っているので、 functions/package.json
で以下の指定をして Node v8 にします。
{
...
"engines": {
"node": "8"
},
...
}
const serviceAccount = require("./serviceAccount.json");
と呼び出しているのは admin 用の認証情報が入ったファイルです。以下でダウンロードして、functions/
に serviceAccount.json
にリネームして置いておく必要があります。公開不可なので .gitignore
に忘れずに入れましょう。
デプロイして確認
最後にデプロイします。
firebase deploy
デプロイが終わるとコンソールに https://us-central1-*****.cloudfunctions.net/saveLocation
のように Functions の URL が表示されるので、先ほどのReact Native 側のコードの url
に指定します。
これでビルドをしてログインすると位置情報が保存されるかと思います。
CI の利用
とりあえずアプリは完成したので、CI の設定に入ります。
CI サービスは評判良さげな Bitrise を使います。
なお、このアプリだとどうしてもビルド時間が無料枠の10分を超えてしまうので、30日のトライアル後は $40/mo 課金して Developer プランを続ける必要があります。
Code Signing について
CI でビルドをするにあたってコードサイニングについて知っておくと良いです。
コードサイニングとは 「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典的に言うと
まぁ「コードサイニング」って単語が出てきたら「コード署名(『改ざんとかされてないよ!』を証明するためにプログラムに付ける電子署名)のことなんだな~」と、お考えください。
とのことです。
iOS アプリ開発では、こちらの記事の画像にもあるように、MacBook にある秘密鍵と Apple Developer で作った証明書に含まれる暗号化された公開鍵を付き合わせてその「改ざんされているかチェック」を行います。
つまり、CI でビルドするとなると CI 上に秘密鍵が必要になります。
こちらの記事を見るとわかりやすいのですが、Certificates から生成できる .p12 ファイルが秘密鍵を含んだものになるので、これを CI にアップロードすることになります。
Certificatesから出力できるファイル。Certificatesは証明書のみ(証明書と、必要な秘密鍵が書かれているのみ。実際の鍵は入っていない。)で、p12ファイルは秘密鍵を含めた証明書なので管理は厳重に。Admin権限をもつことと等価。プッシュサービス(Repro, Parse)などのMBaaSを利用する際にはPush通知用のこのファイルを書き出してサービス側に登録したりすることもある。
Appleの証明書とかまとめ!
Bitrise のセットアップ
新規登録して、アプリを作成しようとするとウィザードが出てくるので進めます。自動でファイルを解析してくれるのでこちらの記事を参考によしなに進めます。
iOS のプロジェクトパスは .xcodeproj
ではなく .xcworkspace
になることに注意しましょう。
セットアップが終わったらテストビルドが走って失敗しますが無視して、アプリの管理画面から Workflow Editor を開きます。
Workflows
Workflows は以下のようにしました。
Activate SSH Key
git にアクセスする ssh key を利用できるようにします。git 最初のセットアップのウィザードで自動で設定でき、デフォルトのままで大丈夫です。
Git Clone Repository
最初のセットアップのウィザードで指定したブランチを取ってきます。こちらもデフォルトのままで大丈夫です。
Set iOS Info.plist - Bundle Version and Bundle Short Version String
アプリのバージョンを更新するステップです。
アプリのバージョンは2種類あって、公開されるバージョン(ユーザーが使う上で機能が変わったときに変える)と内部のバージョン(コードをいじってビルドするたびに変える)があります。バージョン番号とビルド番号という言われ方もするようです。
僕の場合は Git のタグ($BITRISE_GIT_TAG
で呼び出せます)をバージョン番号、Bitrise のビルドナンバー($BITRISE_BUILD_NUMBER
で呼び出せます)をビルド番号にしています。
2つあるのは ~~Tests の方のバージョンも上げているからです。もうひとつの Info.plist file path は以下になります。
Stamp AppIcon with version number
アプリのアイコンに今のバージョン番号とビルド番号を乗っけてくれるやつです。バージョン番号(ビルド番号)
みたいに表示されます。
Info.plist と同様に $BITRISE_GIT_TAG
と $BITRISE_BUILD_NUMBER
を使用します。
Script
任意のスクリプトを実行できるステップです。ここで git のコミットとプッシュをします。
#!/usr/bin/env bash
# fail if any commands fails
set -e
# debug log
set -x
# write your script here
git checkout master
git add ios/__YOUR_PROJECT_NAME__/Info.plist ios/__YOUR_TEST_PROJECT_NAME__/Info.plist
git commit -m "Update Version"
git push origin master
# or run a script from your repository, like:
# bash ./path/to/script.sh
# not just bash, e.g.:
# ruby ./path/to/script.rb
Run npm command
npm コマンドを実行できるステップで React Native のセットアップをすると npm install
するやつがデフォルトでついてくるのでそのまま使います。
Run CocoaPods install
今回は CocoaPods を使っているので pod install
が必要になります。Podfile path は ios/Podfile
と指定する点に注意しましょう。
Certificate and profile installer
こちらはコードサイニング用の秘密鍵と Provisioning Profile をインストールするステップです。
追加して設定は何もいじらなくても良いですが、秘密鍵と Provisioning Profile をアップロードする必要があります(あとで説明します)。
Xcode Archive & Export for iOS
ビルドするステップです。Select method for export だけ、ベータテスト用の ad-hoc
に変更しましょう。
HockeyApp iOS Deploy
ベータテスト配信用のサービス HockeyApp にデプロイするステップになります。HockeyApp でプロジェクトを作成して API TOKEN と APP ID を控えてくる必要があります(あとで説明します)。
Code Signing
コードサイニングについてで述べたように、コードサイニング用のファイルをアップロードします。
その前に Distribution 用の Certificates と Ad Hoc Provisioning Profile を作成して Xcode に入れておく必要があります。以下の記事が簡潔でわかりやすいです。
終わったら、Bitrise にも書いてあるように以下のコマンドで CodeSignDoc というのを使って .p12 ファイルの取り出しと Bitrise へのアップロードを行います。言われるがままに進めれば大丈夫です。
bash -l -c "$(curl -sfL https://raw.githubusercontent.com/bitrise-tools/codesigndoc/master/_scripts/install_wrap.sh)"
HockeyApp のセットアップ
アプリをデプロイするために HockeyApp でセットアップしましょう。
アカウント作成して、 App を作成しようとすると「Upload しろ」みたいなこと言われますが、Create the app manually instead. のリンクをクリックして進めましょう。
Title や Bundle Indentifier などを決めて、作成が完了したら App Id と API KEY を探します。
作成してすぐ見れるアプリの Overview に App Id があります。
API_TOKEN は右上のアカウントのドロップダウンにある Account Settings から行けます。
Secrets
Bitrise では機密性の高い情報は Secrets に入れるようになっています(そうではない情報は Env Vars に入れます)。
ここに $HOCKEY_API_TOKEN
と $HOCKEY_APP_ID
を入れます。
HockeyApp iOS Deploy のステップでプルダウンで選択できるようになるので、それぞれ指定します。
ここまでやって git で適当にタグを打ってビルドを実行すると、うまく行けば HockeyApp にアプリがアップロードされます。
アプリを配布
アップロードされたファイルをテスターに配布しましょう。今回は URLを共有してテスターを募る方法を採用します。
HockeyApp の App 管理画面の Users の Recruitment から設定します。
設定画面は好きなように設定します。以下は一番ガバガバな設定です。
設定が終わると、URL が出てくるのでこれを共有すると招待ができます。
Provisioning Profile に UUID を追加
これですぐにテスターがアプリをダウンロードできるようにはなりません。
Ad Hoc Provisioning Profile で説明したように、Provisioning Profile にデバイスの UUID が入っていないと起動できません。
HockeyApp では追加されたユーザーのデバイスが Provisioning Profile に入ってるか自動的に判断してくれます。
App 管理画面の Users の Devices に行くと、デバイス一覧が見れます。そこにある Export の Unprovisioned Devices を押すと、Provisioning Profile に入ってないデバイス一覧をダウンロードできます。
このファイルは Apple Developer の Devices にファイルごとアップロードして一括追加することができます。
追加が終わったら Provisioning Profile の作成を再度行います。追加したすべてのデバイスを選択して、作成します。作成した Provisioning Profile は Bitrise の Code Signing にドラックアンドドロップすれば大丈夫です。
これで再度ビルド・配布が終われば、テスターがダウンロード・起動できるようになります。
おわりに
React Native といえば「Web エンジニアでも簡単にアプリが作れる!」というイメージがありますが、まともにやろうとすると理解しないといけないことが山ほどあって、今でも理解できているかわからないような状態で苦労しました。アプリエンジニアすごい。
こちらの記事など見て Expo だとだいぶ楽になる印象があり、最初の方に言ったようにバックグラウンド処理も Expo で使えるようになるっぽいので、Expo に期待しているのが現状です。
〇〇使わないの?
選択肢にあったけど今回使わなかったものも紹介します。
react-native-background-geolocation-firebase
react-native-background-geolocation の拡張で react-native-background-geolocation-firebase という、トラッキングのたびに Firestore に直で保存してくれるやつもあります。
これなら Cloud Functions 使わなくてもよくなりますが、これ使うと不要なデータも保存されてしまう上に、最新の位置情報だけ保存したくなったり、Firebase じゃないやつ使いたくなったりしたときに変更の手間が発生するので、臨機応変に対応できる functions に POST するほうがマシかなあと感じています。。
fastlane
fastlane はコードサイニングやビルドを Ruby で書けるやつです。今回 Bitrise でやってることをまるまる Ruby で書くかたちになります。
今回採用しなかった理由は以下です。
- Bitrise で事足りる
- fastlane を使うとなると、コードサイニングで private の Git リポジトリを使う必要があって余計に課金が必要になる
- CI 上のコードサイニングの過程で Apple ID の2段階認証を求められてハマった
fastlane を使うのは、本腰入れて複数人で開発するようになって Apple Developer の Enterprise Edition に登録してからが良いのかなと感じました。
Bitrise Auto Provisioning
Bitrise には Auto Provisioning という Bitrise に秘密鍵さえ置いてしまえば自動的に Provisioning Profile を作ってくれる便利な機能があります。
これを使えば、テスターの端末追加時に Apple Developer にていちいち Provisioning Profile を作り直さなくてよくなるっぽいです。
ただ、今 Apple 側の仕様変更で2段階認証が有効なアカウントでの利用が実質不可能になっていて、チャットお問い合わせしたところ以下のような回答が返ってきました。
To summarise the issue: currently the session provided for 2FA Apple Developer Accounts is only kept alive for about 8 hours(this means that you have to re-authenticate on your profile page every 8 hours)
Auto Provisioning は Bitrise に Apple Id で2段階認証後、そのセッションを保管して使うことで実現していましたが、そのセッションの有効期間が最大8時間になったため8時間ごとに Bitrise 上で2段階認証をしないと使えなくなってしまった、とのことでした。
DeployGate / TestFlight etc.
HockeyApp の代替。単純に身近にいる人が HockeyApp 使ってて馴染みがあったので HockeyApp 使いました。特別な意味はないです。