Jen Looper
Developer Advocate at ProgressSW
NativeScript、Firebase、Angular 2 という強力な組み合わせを活用すれば、アプリの構築を一気に加速させることができます。クリスマス休暇の期間中は、アプリの開発をスピードアップさせて、プレゼントを贈りたいという家族のニーズに応えなければならないため、特にその点が重要になります。ちょうどよいことに、Angular 2 ベースの NativeScript アプリで Firebase を使う方法のデモを皆さんに
プレゼント することができます(以下をご覧ください 🎁)。これは、Eddy Verbruggen 氏による有名な
NativeScript-Firebase プラグイン のいくつかのコンポーネントを使ったものです。
このチュートリアルでは、よく使われている次の 4 つの Firebase 機能を NativeScript アプリで使用する方法を説明します。4 つの機能には、ログインとユーザー登録の機能を提供する
Authentication 、リアルタイムにアップデートされるデータ ストレージの
Realtime Database 、リモートでアプリを変更できる
Remote Config 、写真を保存する
Storage があります。説明するにあたって、以前に Ionic で書いた
Giftler アプリ を書き直すことにしました。
実際のプロジェクトで作業に着手する前に、
ドキュメント に目を通し、以下の前提事項について確認することをお勧めします。
ローカルマシンに NativeScript がインストールされており、CLI が問題なく動作していることを確認します。
お好みの IDE で、NativeScript と Angular の開発環境を設定します。TypeScript が必要になるので、トランスパイル プロセスが動作することを確認しておきます。Visual Studio 、Visual Studio Code 、Jetbrains 対応の IDE などで利用できる優れた NativeScript プラグインがありますが、中でも Visual Studio Code には、開発を加速させる便利なスニペット があります。
Firebase アカウントにログインし、コンソール を開きます。
Firebase コンソールで新しいプロジェクトを作成します。ここでは、「Giftler」という名前にしました。さらに、Firebase コンソールで iOS と Android のアプリを作成します。その際に、GoogleServices-Info.plist ファイルと google-services.json ファイルをダウンロードします。後ほど必要になるので、ファイルを配置した場所は覚えておいてください。
依存モジュールのインストール
Giftler は、認証が必要な NativeScript アプリのサンプルとして作成しました。このアプリでは、ユーザーがクリスマス休暇に受け取りたいギフトを、写真や説明とともに一覧表示できます。現時点で、このアプリには iOS 版と Android 版があり、以下の機能があります。
ログイン、ログアウト、ユーザー登録、および「パスワードを忘れた」場合の処理の実行
ユーザーによるリストへのギフト項目の登録
ユーザーによるリストからのギフト項目の削除
ユーザーによるリストの各ギフト項目の編集(説明や写真の追加)
バックエンドですぐに変更できる Firebase の Remote Config サービスによるメッセージング
それでは、
Giftler のソースコード をフォークしましょう。これは、問題なく動作している完全版のアプリです。アプリをクローンできたら、アプリを作成した際にダウンロードした、現在の Firebase 関連のアプリのファイルを置き換えます。
Firebase からダウンロードした google.services.json ファイルを /app/App_Resources/Android folder
に配置します。
同様に、Firebase からダウンロードした GoogleService-Info.plist ファイルを /app/App_Resources/iOS folder
に配置します。
これらのファイルは、アプリで Firebase を初期化し、関連する外部サービスに接続するために必要です。
次に、アプリのルートにある
package.json
を確認します。このファイルには、アプリで使用するプラグインが含まれています。ここでは、NativeScript に関連するプラグインに注目してください。
"nativescript-angular":"1.2.0",
"nativescript-camera": "^0.0.8",
"nativescript-iqkeyboardmanager": "^1.0.1",
"nativescript-plugin-firebase": "^3.8.4",
"nativescript-theme-core": "^1.0.2",
NativeScript-Angular プラグインは、Angular と NativeScript を統合するプラグインです。Camera プラグインを使うと、少しカメラを管理しやすくなります。IQKeyboardManager は iOS 専用のプラグインで、厄介な iOS のキーボードを管理してくれます。Theme プラグインは、スキンを完全に自作しなくてもデフォルトのスタイルをアプリに追加できるすばらしいプラグインです。最後に、このアプリでもっとも重要なのが Firebase プラグインです。
以上の依存モジュールを配置して、プラグインをインストールする準備ができたら、iOS と Android のそれぞれのコードが格納された
platforms
フォルダを作成し、Firebase プラグインやその他の npm ベースのプラグインを初期化して、アプリを構築できるようにします。NativeScript CLI を使ってクローンしたアプリのルートに移動し、
tns
run ios
または
tns run android
を実行します。これによってプラグインをビルドするルーチンが開始されます。中でも、Firebase プラグインのさまざまなパーツのインストールが始まることが確認できるはずです。インストール スクリプトが実行されると、さまざまな Firebase サービスを統合するためのコンポーネントをインストールするかどうかの確認が表示されます。ここでは、Messaging とソーシャル認証を除くすべてを選択します。ここでの大きな特徴は、firebase.nativescript.json ファイルがアプリのルートにインストールされることです。そのため、今後プラグインの新しいパーツをインストールしたい場合、このファイルを編集するだけでプラグインを再インストールできます。
ここで、
tns livesync ios --watch
または
tns livesync android
--watch
を実行し、エミュレータでアプリが起動して変更が監視されることを確認すると、アプリが実行されて新しくログインする準備ができていることがわかります。ただし、ログインを行う前に、Firebase コンソールの [Authentication] タブからメール / パスワードによるログインを有効にして、Firebase がこのタイプのログインを処理できるようにする必要があります。
この裏で何が起こっているかを確認するために、少しばかり内部の仕組みを見てみましょう。Firebase にログインするには、インストールした Firebase サービスを初期化しておく必要があります。
app/main.ts
には、いくつか興味深い点があります。
// this import should be first in order to load some required settings (like globals and reflect-metadata)
import { platformNativeScriptDynamic } from "nativescript-angular/platform";
import { AppModule } from "./app.module";
import { BackendService } from "./services/backend.service";
import firebase = require("nativescript-plugin-firebase");
firebase.init({
//persist should be set to false as otherwise numbers aren't returned during
livesync
persist: false,
storageBucket: 'gs://giftler-f48c4.appspot.com',
onAuthStateChanged: (data: any) => {
console.log(JSON.stringify(data))
if (data.loggedIn) {
BackendService.token = data.user.uid;
}
else {
BackendService.token = "";
}
}
}).then(
function (instance) {
console.log("firebase.init done");
},
function (error) {
console.log("firebase.init error: " + error);
}
);
platformNativeScriptDynamic().bootstrapModule(AppModule);
最初に、プラグインから firebase をインポートし、.init() を呼び出します。次に storageBucket プロパティを編集し、Firebase コンソールの [Storage] タブに表示されている値を貼り付けます。
これで、お使いの Firebase アカウントに合わせてアプリがカスタマイズされ、アプリで新しいユーザーの登録とログインが可能になるはずです。必要に応じて、
app/login/login.component.ts
ファイルの user.email と password 変数を編集して、デフォルトのログイン認証情報を
[email protected]
からご自身のログインとパスワードに変更することもできます。
iOS と Android のログイン画面
注: iOS では、Xcode シミュレータを使うとすぐにアプリをエミュレートできます。ただし Android では、アプリをエミュレータにインストールするために、Google Services の有効化など、追加でいくつかの手順が必要になる場合があります。
コードの構造と認証
Angular 2 のデザイン パターンでは、コードをモジュール化する必要があります。そのため、以下のコード構造に従うものとします。
—login
login.component.ts
login.html
login.module.ts
login.routes.ts
—list …
—list-detail …
—models
gift.model.ts
user.model.ts
index.ts
—services
backend.service.ts
firebase.service.ts
utils.service.ts
index.ts
app.component.ts
app.css
app.module.ts
app.routes.ts
auth-guard.service.ts
main.ts
Firebase の認証が Angular 2 の auth-guard.service と連携する方法に注目してください。前述のように、アプリの
app/main.ts
で Firebase が初期化されます。その際に、
onAuthStateChanged
関数が呼び出されます。
onAuthStateChanged: (data: any) => {
console.log(JSON.stringify(data))
if (data.loggedIn) {
BackendService.token = data.user.uid;
}
else {
BackendService.token = "";
}
}
アプリが起動する際に、Firebase から返されるデータを文字列化したものをコンソールで確認してください。そのユーザーに
loggedIn
フラグが立っている場合、Firebase から送り返された userId を
token
として設定します。NativeScript アプリケーション設定モジュールを使って、この userId を保存するかつ、これから作成するデータと関連付けます。このモジュールは、localStorage のような機能を持つモジュールです。このトークンとトークンを使う認証テストは
app/services/backend.service.ts
ファイルで管理されており、
app/auth-guard.service.ts
ファイルで利用できます。auth-guard ファイルは、アプリのログインおよびログアウト状態を適切に管理する方法を提供しています。
AuthGuard クラスは、Angular Router モジュールの CanActivate インターフェースを実装しています。
export class AuthGuard implements CanActivate {
constructor(private router:Router) { }
canActivate() {
if (BackendService.isLoggedIn()) {
return true;
}
else {
this.router.navigate(["/login"]);
return false;
}
}
基本的には、前述のログイン ルーチンでトークンが設定され、かつ BackendService.isLoggedIn 関数が true を返す場合、アプリでデフォルトのルートであるウィッシュリストへのナビゲーションが許可されます。しかし、それ以外の場合、ユーザーはログイン画面に戻されます。
const listRoutes:Routes = [
{ path: "", component:ListComponent, canActivate: [AuthGuard] },
];
これで、Firebase を使う NativeScript アプリを初期化できました。次は、アプリにデータを読み込み、Firebase のすばらしいリアルタイム性を活用して、アップデートされるデータベースの内容を監視する方法について学んでゆきましょう。
リストの作成とダブルチェック
ウィッシュリストのベースとなっている
app/list/list.html
から説明します。ここには、テキスト項目と空のリストが表示されているはずです。では、サンタさんに欲しいものを伝えてみましょう。項目がデータベースに送信され、リアルタイムにリストに追加されます。これをどうやって実現しているかを見てみましょう。
app/list/list.component.ts
では、最初にギフトのリストを保持するための監視可能オブジェクトを設定しています。
public gifts$: Observable;
次に、コンポーネントの初期化の際に、データベースからリストを読み込みます。
ngOnInit(){
this.gifts$ = this.firebaseService.getMyWishList();
}
おもしろいのは firebaseService ファイルです。この関数では、リスナーを追加して Firebase Database の Gifts コレクションの変更を監視する監視可能オブジェクト rxjs を返しています。この方法に注目してください。
getMyWishList():Observable {
return new Observable((observer: any) => {
let path = 'Gifts';
let onValueEvent = (snapshot: any) => {
this.ngZone.run(() => {
let results = this.handleSnapshot(snapshot.value);
console.log(JSON.stringify(results))
observer.next(results);
});
};
firebase.addValueEventListener(onValueEvent, `/${path}`);
}).share();
}
このクエリの結果は、次に示す
handleSnapshot
関数で処理されます。この関数は、ユーザーがフィルタリングしたデータを _allItems 配列に設定しています。
handleSnapshot(data: any) {
//empty array, then refill and filter
this._allItems = [];
if (data) {
for (let id in data) {
let result = (Object).assign({id: id}, data[id]);
if(BackendService.token === result.UID){
this._allItems.push(result);
}
}
this.publishUpdates();
}
return this._allItems;
}
最後に、publishUpdates が呼び出されます。この関数は、新しい項目が先頭に表示されるように、日付でデータを並び替えています。
publishUpdates() {
// here, we sort must emit a *new* value (immutability!)
this._allItems.sort(function(a, b){
if(a.date < b.date) return -1;
if(a.date > b.date) return 1;
return 0;
})
this.items.next([...this._allItems]);
}
監視可能オブジェクト $gifts にデータが設定されると、要素の編集や削除を行うたびにリスナーが呼ばれ、フロントエンドが適切にアップデートされます。getMyWishList メソッド内の onValueEvent 関数で
ngZone が使われていることに注意してください。これによって、非同期に行われるデータのアップデートに応じて UI が適切にアップデートされます。NativeScript アプリの ngZone の概要については、
こちら をご覧ください。
メッセージのリモート設定
もう 1 つの優れた Firebase サービスに「Remote Config」があります。これは、Firebase のバックエンドからアプリをアップデートできる機能です。Remote Config を使うと、アプリの機能のオン、オフを切り替えたり、UI を変更したりできます。ここでは、サンタさんからメッセージを送る機能を追加してみましょう。
app/list/list.html
には、次のメッセージ ボックスがあります。
<Label class="gold card" textWrap="true" [text]="message$ | async"></Label>
監視可能オブジェクト
message$
は、データリストとほぼ同じ方法で組み込まれていますが、ここではアプリが新しく初期化されるたびに変更が適用されます。
ngOnInit(){
this.message$ = this.firebaseService.getMyMessage();
}
秘密は、次に示すサービスレイヤ(
app/services/firebase.service.ts
)にあります。
getMyMessage():Observable{
return new Observable((observer:any) => {
firebase.getRemoteConfig({
developerMode: false,
cacheExpirationSeconds: 300,
properties: [{
key: "message",
default:"Happy Holidays!"
}]
}).then(
function (result) {
console.log("Fetched at " + result.lastFetch + (result.throttled ? " (throttled)" : ""));
for (let entry in result.properties)
{
observer.next(result.properties[entry]);
}
}
);
}).share();
}
好きなだけ新しいメッセージを発行可能
注: アプリから必要のない高頻度で Remote Config の値を取得すると、Remote Config の利用が制限されてしまう場合がありますので、慎重に開発してください。
写真を撮る
このプロジェクトのおもしろいところは、選んだプレゼントの写真を撮って Firebase Storage に格納できる点でしょう。前述のように、この部分には Camera プラグインを使いました。これでハードウェアの管理が少し楽になります。最初に、
app/list-detail/list-detail.component.ts
の ngOnInit() メソッドでパーミッション セットを取得し、アプリがカメラデバイスにアクセスできるようにします。
ngOnInit() {
camera.requestPermissions();
...
}
ユーザーが詳細画面の [Photo] ボタンをクリックすると、イベント チェーンが始まります。最初に呼ばれるのは次のコードです。
takePhoto() {
let options = {
width:300,
height:300,
keepAspectRatio: true,
saveToGallery: true
};
camera.takePicture(options)
.then(imageAsset => {
imageSource.fromAsset(imageAsset).then(res => {
this.image = res;
//save the source image to a file, then send that file path to firebase
this.saveToFile(this.image);
})
}).catch(function (err) {
console.log("Error -> " + err.message);
});
}
カメラで写真が撮影され、その写真が imageAsset として保存されて画面に表示されます。このイメージはファイルとしてローカルに保存されます。その際に、日付のタイムスタンプに従った名前が付けられます。また、今後の利用に備えて、パスも保存されます。
saveToFile(res){
let imgsrc = res;
this.imagePath =
this.utilsService.documentsPath(`photo-${Date.now()}.png`);
imgsrc.saveToFile(this.imagePath, enums.ImageFormat.png);
}
[Save] ボタンが押されると、イメージはローカルパス経由で Firebase に送信され、ストレージ モジュール内に保存されます。アプリに返される Firebase のフルパスは、
/Gifts
データベース コレクションに保存されます。
editGift(id: string){
if(this.image){
//upload the file, then save all
this.firebaseService.uploadFile(this.imagePath).then((uploadedFile: any) =>
{
this.uploadedImageName = uploadedFile.name;
//get downloadURL and store it as a full path;
this.firebaseService.getDownloadUrl(this.uploadedImageName).then((downloadUrl: string) => {
this.firebaseService.editGift(id,this.description,downloadUrl).then((result:any) => {
alert(result)
}, (error: any) => {
alert(error);
});
})
}, (error: any) => {
alert('File upload error: ' + error);
});
}
else {
//just edit the description
this.firebaseService.editDescription(id,this.description).then((result:any) => {
alert(result)
}, (error: any) => {
alert(error);
});
}
}
このイベント チェーンは一見複雑そうですが、最終的に実行されるのは、Firebase サービス ファイルの中の数行です。
uploadFile(localPath: string, file?: any): Promise {
let filename = this.utils.getFilename(localPath);
let remotePath = `${filename}`;
return firebase.uploadFile({
remoteFullPath: remotePath,
localFullPath: localPath,
onProgress: function(status) {
console.log("Uploaded fraction: " + status.fractionCompleted);
console.log("Percentage complete: " + status.percentageCompleted);
}
});
}
getDownloadUrl(remoteFilePath: string): Promise {
return firebase.getDownloadUrl({
remoteFullPath: remoteFilePath})
.then(
function (url:string) {
return url;
},
function (errorMessage:any) {
console.log(errorMessage);
});
}
editGift(id:string, description: string, imagepath: string){
this.publishUpdates();
return firebase.update("/Gifts/"+id+"",{
description: description,
imagepath: imagepath})
.then(
function (result:any) {
return 'You have successfully edited this gift!';
},
function (errorMessage:any) {
console.log(errorMessage);
});
}
最終的な結果では、ウィッシュリストに表示するギフトの写真と説明の両方がうまく取得できています。これでもうサンタさんは、カイリーのどのアイライナーを買えばいいのかわからないという言い訳はできません。強力な NativeScript と Angular を組み合わせれば、数分で iOS と Android のネイティブ アプリを作ることができます。これに Firebase を追加すれば、アプリのユーザー、イメージ、データを保存する強力な機能を利用でき、さらに端末間でリアルタイムにデータをアップデートすることも可能になります。すばらしいと思いませんか。このアプリは、次のようになります。
ここまで、充実したウィッシュリスト管理アプリの作成に向けて順調に進んできました。しかし、サンタさんに願いごとを伝える最高の方法はまだ模索中と言えるでしょう。次のステップとしては、Mailgun のメールとの統合やプッシュ通知の利用が考えられます。それまでの間、すばらしいクリスマス休暇をお過ごしください。皆さんが Firebase を利用してすてきな NativeScript アプリを作れるようにお祈りします。
NativeScript についてさらに詳しく学びたい方は、
http://www.nativescript.org をご覧ください。質問がある方は、
こちら から NativeScript の Slack チャンネルに参加してください。
Posted by
Khanh LeViet - Developer Relations Team
この記事はゲスト ブロガー Jen Looper による The Firebase Blog の記事 "The Firebase Blog: Building a NativeScript + Angular 2 Mobile App using Firebase " を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。
Jen Looper
Developer Advocate at ProgressSW
NativeScript、Firebase、Angular 2 という強力な組み合わせを活用すれば、アプリの構築を一気に加速させることができます。クリスマス休暇の期間中は、アプリの開発をスピードアップさせて、プレゼントを贈りたいという家族のニーズに応えなければならないため、特にその点が重要になります。ちょうどよいことに、Angular 2 ベースの NativeScript アプリで Firebase を使う方法のデモを皆さんにプレゼント することができます(以下をご覧ください 🎁)。これは、Eddy Verbruggen 氏による有名な NativeScript-Firebase プラグイン のいくつかのコンポーネントを使ったものです。
このチュートリアルでは、よく使われている次の 4 つの Firebase 機能を NativeScript アプリで使用する方法を説明します。4 つの機能には、ログインとユーザー登録の機能を提供する Authentication 、リアルタイムにアップデートされるデータ ストレージの Realtime Database 、リモートでアプリを変更できる Remote Config 、写真を保存する Storage があります。説明するにあたって、以前に Ionic で書いた Giftler アプリ を書き直すことにしました。
実際のプロジェクトで作業に着手する前に、ドキュメント に目を通し、以下の前提事項について確認することをお勧めします。
ローカルマシンに NativeScript がインストールされており、CLI が問題なく動作していることを確認します。
お好みの IDE で、NativeScript と Angular の開発環境を設定します。TypeScript が必要になるので、トランスパイル プロセスが動作することを確認しておきます。Visual Studio 、Visual Studio Code 、Jetbrains 対応の IDE などで利用できる優れた NativeScript プラグインがありますが、中でも Visual Studio Code には、開発を加速させる便利なスニペット があります。
Firebase アカウントにログインし、コンソール を開きます。
Firebase コンソールで新しいプロジェクトを作成します。ここでは、「Giftler」という名前にしました。さらに、Firebase コンソールで iOS と Android のアプリを作成します。その際に、GoogleServices-Info.plist ファイルと google-services.json ファイルをダウンロードします。後ほど必要になるので、ファイルを配置した場所は覚えておいてください。
依存モジュールのインストール
Giftler は、認証が必要な NativeScript アプリのサンプルとして作成しました。このアプリでは、ユーザーがクリスマス休暇に受け取りたいギフトを、写真や説明とともに一覧表示できます。現時点で、このアプリには iOS 版と Android 版があり、以下の機能があります。
ログイン、ログアウト、ユーザー登録、および「パスワードを忘れた」場合の処理の実行
ユーザーによるリストへのギフト項目の登録
ユーザーによるリストからのギフト項目の削除
ユーザーによるリストの各ギフト項目の編集(説明や写真の追加)
バックエンドですぐに変更できる Firebase の Remote Config サービスによるメッセージング
それでは、Giftler のソースコード をフォークしましょう。これは、問題なく動作している完全版のアプリです。アプリをクローンできたら、アプリを作成した際にダウンロードした、現在の Firebase 関連のアプリのファイルを置き換えます。
Firebase からダウンロードした google.services.json ファイルを /app/App_Resources/Android folder
に配置します。
同様に、Firebase からダウンロードした GoogleService-Info.plist ファイルを /app/App_Resources/iOS folder
に配置します。
これらのファイルは、アプリで Firebase を初期化し、関連する外部サービスに接続するために必要です。
次に、アプリのルートにある package.json
を確認します。このファイルには、アプリで使用するプラグインが含まれています。ここでは、NativeScript に関連するプラグインに注目してください。
"nativescript-angular":"1.2.0",
"nativescript-camera": "^0.0.8",
"nativescript-iqkeyboardmanager": "^1.0.1",
"nativescript-plugin-firebase": "^3.8.4",
"nativescript-theme-core": "^1.0.2",
NativeScript-Angular プラグインは、Angular と NativeScript を統合するプラグインです。Camera プラグインを使うと、少しカメラを管理しやすくなります。IQKeyboardManager は iOS 専用のプラグインで、厄介な iOS のキーボードを管理してくれます。Theme プラグインは、スキンを完全に自作しなくてもデフォルトのスタイルをアプリに追加できるすばらしいプラグインです。最後に、このアプリでもっとも重要なのが Firebase プラグインです。
以上の依存モジュールを配置して、プラグインをインストールする準備ができたら、iOS と Android のそれぞれのコードが格納された platforms
フォルダを作成し、Firebase プラグインやその他の npm ベースのプラグインを初期化して、アプリを構築できるようにします。NativeScript CLI を使ってクローンしたアプリのルートに移動し、tns
run ios
または tns run android
を実行します。これによってプラグインをビルドするルーチンが開始されます。中でも、Firebase プラグインのさまざまなパーツのインストールが始まることが確認できるはずです。インストール スクリプトが実行されると、さまざまな Firebase サービスを統合するためのコンポーネントをインストールするかどうかの確認が表示されます。ここでは、Messaging とソーシャル認証を除くすべてを選択します。ここでの大きな特徴は、firebase.nativescript.json ファイルがアプリのルートにインストールされることです。そのため、今後プラグインの新しいパーツをインストールしたい場合、このファイルを編集するだけでプラグインを再インストールできます。
ここで、tns livesync ios --watch
または tns livesync android
--watch
を実行し、エミュレータでアプリが起動して変更が監視されることを確認すると、アプリが実行されて新しくログインする準備ができていることがわかります。ただし、ログインを行う前に、Firebase コンソールの [Authentication] タブからメール / パスワードによるログインを有効にして、Firebase がこのタイプのログインを処理できるようにする必要があります。
この裏で何が起こっているかを確認するために、少しばかり内部の仕組みを見てみましょう。Firebase にログインするには、インストールした Firebase サービスを初期化しておく必要があります。app/main.ts
には、いくつか興味深い点があります。
// this import should be first in order to load some required settings (like globals and reflect-metadata)
import { platformNativeScriptDynamic } from "nativescript-angular/platform";
import { AppModule } from "./app.module";
import { BackendService } from "./services/backend.service";
import firebase = require("nativescript-plugin-firebase");
firebase.init({
//persist should be set to false as otherwise numbers aren't returned during
livesync
persist: false,
storageBucket: 'gs://giftler-f48c4.appspot.com',
onAuthStateChanged: (data: any) => {
console.log(JSON.stringify(data))
if (data.loggedIn) {
BackendService.token = data.user.uid;
}
else {
BackendService.token = "";
}
}
}).then(
function (instance) {
console.log("firebase.init done");
},
function (error) {
console.log("firebase.init error: " + error);
}
);
platformNativeScriptDynamic().bootstrapModule(AppModule);
最初に、プラグインから firebase をインポートし、.init() を呼び出します。次に storageBucket プロパティを編集し、Firebase コンソールの [Storage] タブに表示されている値を貼り付けます。
これで、お使いの Firebase アカウントに合わせてアプリがカスタマイズされ、アプリで新しいユーザーの登録とログインが可能になるはずです。必要に応じて、app/login/login.component.ts
ファイルの user.email と password 変数を編集して、デフォルトのログイン認証情報を user@nativescript.org
からご自身のログインとパスワードに変更することもできます。
iOS と Android のログイン画面
注: iOS では、Xcode シミュレータを使うとすぐにアプリをエミュレートできます。ただし Android では、アプリをエミュレータにインストールするために、Google Services の有効化など、追加でいくつかの手順が必要になる場合があります。
コードの構造と認証
Angular 2 のデザイン パターンでは、コードをモジュール化する必要があります。そのため、以下のコード構造に従うものとします。
—login
login.component.ts
login.html
login.module.ts
login.routes.ts
—list …
—list-detail …
—models
gift.model.ts
user.model.ts
index.ts
—services
backend.service.ts
firebase.service.ts
utils.service.ts
index.ts
app.component.ts
app.css
app.module.ts
app.routes.ts
auth-guard.service.ts
main.ts
Firebase の認証が Angular 2 の auth-guard.service と連携する方法に注目してください。前述のように、アプリの app/main.ts
で Firebase が初期化されます。その際に、onAuthStateChanged
関数が呼び出されます。
onAuthStateChanged: (data: any) => {
console.log(JSON.stringify(data))
if (data.loggedIn) {
BackendService.token = data.user.uid;
}
else {
BackendService.token = "";
}
}
アプリが起動する際に、Firebase から返されるデータを文字列化したものをコンソールで確認してください。そのユーザーに loggedIn
フラグが立っている場合、Firebase から送り返された userId を token
として設定します。NativeScript アプリケーション設定モジュールを使って、この userId を保存するかつ、これから作成するデータと関連付けます。このモジュールは、localStorage のような機能を持つモジュールです。このトークンとトークンを使う認証テストは app/services/backend.service.ts
ファイルで管理されており、app/auth-guard.service.ts
ファイルで利用できます。auth-guard ファイルは、アプリのログインおよびログアウト状態を適切に管理する方法を提供しています。
AuthGuard クラスは、Angular Router モジュールの CanActivate インターフェースを実装しています。
export class AuthGuard implements CanActivate {
constructor(private router:Router) { }
canActivate() {
if (BackendService.isLoggedIn()) {
return true;
}
else {
this.router.navigate(["/login"]);
return false;
}
}
基本的には、前述のログイン ルーチンでトークンが設定され、かつ BackendService.isLoggedIn 関数が true を返す場合、アプリでデフォルトのルートであるウィッシュリストへのナビゲーションが許可されます。しかし、それ以外の場合、ユーザーはログイン画面に戻されます。
const listRoutes:Routes = [
{ path: "", component:ListComponent, canActivate: [AuthGuard] },
];
これで、Firebase を使う NativeScript アプリを初期化できました。次は、アプリにデータを読み込み、Firebase のすばらしいリアルタイム性を活用して、アップデートされるデータベースの内容を監視する方法について学んでゆきましょう。
リストの作成とダブルチェック
ウィッシュリストのベースとなっている app/list/list.html
から説明します。ここには、テキスト項目と空のリストが表示されているはずです。では、サンタさんに欲しいものを伝えてみましょう。項目がデータベースに送信され、リアルタイムにリストに追加されます。これをどうやって実現しているかを見てみましょう。
app/list/list.component.ts
では、最初にギフトのリストを保持するための監視可能オブジェクトを設定しています。
public gifts$: Observable;
次に、コンポーネントの初期化の際に、データベースからリストを読み込みます。
ngOnInit(){
this.gifts$ = this.firebaseService.getMyWishList();
}
おもしろいのは firebaseService ファイルです。この関数では、リスナーを追加して Firebase Database の Gifts コレクションの変更を監視する監視可能オブジェクト rxjs を返しています。この方法に注目してください。
getMyWishList():Observable {
return new Observable((observer: any) => {
let path = 'Gifts';
let onValueEvent = (snapshot: any) => {
this.ngZone.run(() => {
let results = this.handleSnapshot(snapshot.value);
console.log(JSON.stringify(results))
observer.next(results);
});
};
firebase.addValueEventListener(onValueEvent, `/${path}`);
}).share();
}
このクエリの結果は、次に示す handleSnapshot
関数で処理されます。この関数は、ユーザーがフィルタリングしたデータを _allItems 配列に設定しています。
handleSnapshot(data: any) {
//empty array, then refill and filter
this._allItems = [];
if (data) {
for (let id in data) {
let result = (Object).assign({id: id}, data[id]);
if(BackendService.token === result.UID){
this._allItems.push(result);
}
}
this.publishUpdates();
}
return this._allItems;
}
最後に、publishUpdates が呼び出されます。この関数は、新しい項目が先頭に表示されるように、日付でデータを並び替えています。
publishUpdates() {
// here, we sort must emit a *new* value (immutability!)
this._allItems.sort(function(a, b){
if(a.date < b.date) return -1;
if(a.date > b.date) return 1;
return 0;
})
this.items.next([...this._allItems]);
}
監視可能オブジェクト $gifts にデータが設定されると、要素の編集や削除を行うたびにリスナーが呼ばれ、フロントエンドが適切にアップデートされます。getMyWishList メソッド内の onValueEvent 関数で ngZone が使われていることに注意してください。これによって、非同期に行われるデータのアップデートに応じて UI が適切にアップデートされます。NativeScript アプリの ngZone の概要については、こちら をご覧ください。
メッセージのリモート設定
もう 1 つの優れた Firebase サービスに「Remote Config」があります。これは、Firebase のバックエンドからアプリをアップデートできる機能です。Remote Config を使うと、アプリの機能のオン、オフを切り替えたり、UI を変更したりできます。ここでは、サンタさんからメッセージを送る機能を追加してみましょう。
app/list/list.html
には、次のメッセージ ボックスがあります。
<Label class="gold card" textWrap="true" [text]="message$ | async"></Label>
監視可能オブジェクト message$
は、データリストとほぼ同じ方法で組み込まれていますが、ここではアプリが新しく初期化されるたびに変更が適用されます。
ngOnInit(){
this.message$ = this.firebaseService.getMyMessage();
}
秘密は、次に示すサービスレイヤ(app/services/firebase.service.ts
)にあります。
getMyMessage():Observable{
return new Observable((observer:any) => {
firebase.getRemoteConfig({
developerMode: false,
cacheExpirationSeconds: 300,
properties: [{
key: "message",
default:"Happy Holidays!"
}]
}).then(
function (result) {
console.log("Fetched at " + result.lastFetch + (result.throttled ? " (throttled)" : ""));
for (let entry in result.properties)
{
observer.next(result.properties[entry]);
}
}
);
}).share();
}
好きなだけ新しいメッセージを発行可能
注: アプリから必要のない高頻度で Remote Config の値を取得すると、Remote Config の利用が制限されてしまう場合がありますので、慎重に開発してください。
写真を撮る
このプロジェクトのおもしろいところは、選んだプレゼントの写真を撮って Firebase Storage に格納できる点でしょう。前述のように、この部分には Camera プラグインを使いました。これでハードウェアの管理が少し楽になります。最初に、app/list-detail/list-detail.component.ts
の ngOnInit() メソッドでパーミッション セットを取得し、アプリがカメラデバイスにアクセスできるようにします。
ngOnInit() {
camera.requestPermissions();
...
}
ユーザーが詳細画面の [Photo] ボタンをクリックすると、イベント チェーンが始まります。最初に呼ばれるのは次のコードです。
takePhoto() {
let options = {
width:300,
height:300,
keepAspectRatio: true,
saveToGallery: true
};
camera.takePicture(options)
.then(imageAsset => {
imageSource.fromAsset(imageAsset).then(res => {
this.image = res;
//save the source image to a file, then send that file path to firebase
this.saveToFile(this.image);
})
}).catch(function (err) {
console.log("Error -> " + err.message);
});
}
カメラで写真が撮影され、その写真が imageAsset として保存されて画面に表示されます。このイメージはファイルとしてローカルに保存されます。その際に、日付のタイムスタンプに従った名前が付けられます。また、今後の利用に備えて、パスも保存されます。
saveToFile(res){
let imgsrc = res;
this.imagePath =
this.utilsService.documentsPath(`photo-${Date.now()}.png`);
imgsrc.saveToFile(this.imagePath, enums.ImageFormat.png);
}
[Save] ボタンが押されると、イメージはローカルパス経由で Firebase に送信され、ストレージ モジュール内に保存されます。アプリに返される Firebase のフルパスは、/Gifts
データベース コレクションに保存されます。
editGift(id: string){
if(this.image){
//upload the file, then save all
this.firebaseService.uploadFile(this.imagePath).then((uploadedFile: any) =>
{
this.uploadedImageName = uploadedFile.name;
//get downloadURL and store it as a full path;
this.firebaseService.getDownloadUrl(this.uploadedImageName).then((downloadUrl: string) => {
this.firebaseService.editGift(id,this.description,downloadUrl).then((result:any) => {
alert(result)
}, (error: any) => {
alert(error);
});
})
}, (error: any) => {
alert('File upload error: ' + error);
});
}
else {
//just edit the description
this.firebaseService.editDescription(id,this.description).then((result:any) => {
alert(result)
}, (error: any) => {
alert(error);
});
}
}
このイベント チェーンは一見複雑そうですが、最終的に実行されるのは、Firebase サービス ファイルの中の数行です。
uploadFile(localPath: string, file?: any): Promise {
let filename = this.utils.getFilename(localPath);
let remotePath = `${filename}`;
return firebase.uploadFile({
remoteFullPath: remotePath,
localFullPath: localPath,
onProgress: function(status) {
console.log("Uploaded fraction: " + status.fractionCompleted);
console.log("Percentage complete: " + status.percentageCompleted);
}
});
}
getDownloadUrl(remoteFilePath: string): Promise {
return firebase.getDownloadUrl({
remoteFullPath: remoteFilePath})
.then(
function (url:string) {
return url;
},
function (errorMessage:any) {
console.log(errorMessage);
});
}
editGift(id:string, description: string, imagepath: string){
this.publishUpdates();
return firebase.update("/Gifts/"+id+"",{
description: description,
imagepath: imagepath})
.then(
function (result:any) {
return 'You have successfully edited this gift!';
},
function (errorMessage:any) {
console.log(errorMessage);
});
}
最終的な結果では、ウィッシュリストに表示するギフトの写真と説明の両方がうまく取得できています。これでもうサンタさんは、カイリーのどのアイライナーを買えばいいのかわからないという言い訳はできません。強力な NativeScript と Angular を組み合わせれば、数分で iOS と Android のネイティブ アプリを作ることができます。これに Firebase を追加すれば、アプリのユーザー、イメージ、データを保存する強力な機能を利用でき、さらに端末間でリアルタイムにデータをアップデートすることも可能になります。すばらしいと思いませんか。このアプリは、次のようになります。
ここまで、充実したウィッシュリスト管理アプリの作成に向けて順調に進んできました。しかし、サンタさんに願いごとを伝える最高の方法はまだ模索中と言えるでしょう。次のステップとしては、Mailgun のメールとの統合やプッシュ通知の利用が考えられます。それまでの間、すばらしいクリスマス休暇をお過ごしください。皆さんが Firebase を利用してすてきな NativeScript アプリを作れるようにお祈りします。
NativeScript についてさらに詳しく学びたい方は、http://www.nativescript.org をご覧ください。質問がある方は、こちら から NativeScript の Slack チャンネルに参加してください。
Posted by Khanh LeViet - Developer Relations Team
デバイスのセットアップとローカルでの書き込み
これを実現するために、
Intel Edison をLinux ベースのコンピューティング プラットフォームとして使い、これに
Sparkfun の Edison Blocks を加えました。なお、デバイスの作成には
Intel Edison 、
Base Block 、
Battery Block 、
Hardwareパック がそれぞれ 1 つずつ必要です。
Edison の開発は
Intel XDK IDE を使えば簡単です。JavaScript でシンプルな Node.js アプリケーションを作っていきます。今回、3 つのライブラリ(データベース接続には
Firebase 、Wi-Fiネットワークの取得には
wireless-tools/iwlist 、デバイスのMAC 取得には
macaddress )を使用しました。実装手順はそれぞれのリンク先のページを参照してください。
ステップ 1: デバイスの MAC アドレスを取得して Firebase に接続する
function initialize() {
macaddress.one('wlan0', function (err, mac) {
mac_address = mac;
if (mac === null) {
console.log('exiting due to null mac Address');
process.exit(1);
}
firebase.initializeApp({
serviceAccount: '/node_app_slot/<service-account-key>.json',
databaseURL: 'https://<project-id>.firebaseio.com/'
});
var db = firebase.database();
ref_samples = db.ref('/samples');
locationSample();
});
}
上記のコードに含まれている 2 つのプレースホルダーは次のとおりです。
service-account-key は Firebase コンソールで作成するプライベート キーです。コンソールの左上部にある歯車アイコンから「settings」を選択して、Generate New Private Key をクリックします。このキーを Edison の /node_app_slot/ ディレクトリに入れます。詳しくは、Firebase のドキュメント をご覧ください。
データベース URLの中の project-id は、Google プロジェクトを Firebase と連携させた後、Firebase コンソールのデータベース ページに表示されます。
ステップ 2: 10秒毎に Wi-Fiネットワークをスキャンしてローカルに書き込む
function locationSample() {
var t = new Date();
iwlist.scan('wlan0', function(err, networks) {
if(err === null) {
ref_samples.push({
mac: mac_address,
t_usec: t.getTime(),
t_locale_string: t.toLocaleString(),
networks: networks,
});
} else {
console.log(err);
}
});
setTimeout(locationSample, 10000);
}
クラウドへの書き込み
上記の locationSample() は、検出可能なWi-Fi ネットワークを Firebase のデータベースに書き込む関数で、データベースはネットワーク接続時にクラウドと同期します。
補足説明 : Firebase へのアクセス権と認証の設定では、デバイスを「サーバー」として設定しました。この設定方法については
Firebase ウェブサイト をご覧ください。今回、認証情報の保存に関しデバイスのセキュリティは十分であると仮定しましたが、皆さんが実装する際にこの点が該当しない場合は、
クライアント JavaScript SDK の設定手順に従ってください。
データベースではワークロード管理に 3 つのキュー(Wi-Fi サンプル キュー、ジオロケーション結果キュー、視覚化データ キュー)を使用します。ワークフローは次のとおりです。デバイスからのサンプルがサンプル キューに入り、これを使ってジオロケーションが生成され、生成されたジオロケーションはジオロケーション キューに入れられます。ジオロケーションは取得されて表示用にフォーマット化され、デバイスによって整理された後、出力が視覚化バケットに保管され、フロントエンドのウェブサイトがこれを使用するという流れです。
以下に、Firebase Database コンソール上に表示されるサンプル、ジオロケーション、デバイスによって書き込まれた視覚化データの例を示します。
データを Google App Engine で処理する
サンプルデータの処理の実行には、長期実行中の Google App Engine Backend Module と
Java Client for Google Maps Services のカスタム版を使用しました。
補足説明: Firebase と App Engine を併用する場合は、必ず手動スケーリングを使用してください。Firebase はバックグラウンド スレッドを使って変更をリッスンしていて、App Engine は手動でスケーリングされたバックエンド インスタンスに対する長期のバックグラウンド スレッドしか許可しません。
Java Client for Google Maps Services は Google Maps API を利用する際に必要な大量の通信コードを扱い、エラー処理について
公開されているベスト プラクティス およびレート制限に従ったリトライ方針に従います。次に示す GeolocateWifiSample() 関数は、イベント リスナーとしてFirebase に登録されています。この関数はデバイスから報告される各ネットワークをループし、それをジオロケーション リクエストに組み込みます。
private void GeolocateWifiSample(DataSnapshot sample, Firebase db_geolocations, Firebase db_errors) {
// initalize the context and request
GeoApiContext context = new GeoApiContext(new GaeRequestHandler()).setApiKey("");
GeolocationApiRequest request = GeolocationApi.newRequest(context)
.ConsiderIp(false);
// for every network that was reported in this sample...
for (DataSnapshot wap : sample.child("networks").getChildren()) {
// extract the network data from the database so it’s easier to work with
String wapMac = wap.child("address").getValue(String.class);
int wapSignalToNoise = wap.child("quality").getValue(int.class);
int wapStrength = wap.child("signal").getValue(int.class);
// include this network in our request
request.AddWifiAccessPoint(new WifiAccessPoint.WifiAccessPointBuilder()
.MacAddress(wapMac)
.SignalStrength(wapStrength)
.SignalToNoiseRatio(wapSignalToNoise)
.createWifiAccessPoint());
}
...
try {
// call the api
GeolocationResult result = request.CreatePayload().await();
...
// write results to the database and remove the original sample
} catch (final NotFoundException e) {
...
} catch (final Throwable e) {
...
}
}
GeolocateWifiSample() 関数をイベント ハンドラーとして登録します。ジオロケーションの結果を処理して視覚化データを生成するそれ以外のリスナーも、同じようなパターンで作成します。
ChildEventListener samplesListener = new ChildEventListener() {
@Override
public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
// geolocate and write to new location
GeolocateWifiSample(dataSnapshot, db_geolocations, db_errors);
}
...
};
db_samples.addChildEventListener(samplesListener);
データの視覚化
デバイスの位置の視覚化には、Google App Engine を使います。保存されたデータを Firebase から渡して、Google Maps JavaScript API を使って結果表示用のシンプルなウェブページを作成しました。index.html ページの中身は空の <div> とID “map” です。この <div> を初期化して Google Map オブジェクトを入れました。また、イベントハンドラーの “child_added” と “child_removed” を追加して、時間の経過に伴ってデータが変化したときにマップを更新するようにしています。
function initMap() {
// attach listeners
firebase.database().ref('/visualization').on('child_added', function(data) {
...
data.ref.on('child_added', function(vizData) {
circles[vizData.key]= new CircleRoyale(map,
vizData.val().lat,
vizData.val().lng,
vizData.val().accuracy,
color);
set_latest_position(data.key, vizData.val().lat, vizData.val().lng);
});
data.ref.on('child_removed', function(data) {
circles[data.key].removeFromMap();
});
});
// create the map
map = new google.maps.Map(document.getElementById('map'), {
center: get_next_device(),
zoom: 20,
scaleControl: true,
});
...
}
API は位置だけでなく精度表示も返すため、精度のコンポーネントを示すカスタム マーカー(半径内の範囲が脈動する)を作成しました。
2 個のデバイス(赤と青)と判明した位置の直近 5 か所
次に進むには
この記事では、インターネットに接続されたデバイス(ロボットからウェアラブル端末まで)を追跡するために、Google Maps Geolocation API を使った IoT デバイスの構築方法を紹介しました。App Engine の処理モジュールを拡張し、他の
Google Maps API を使って
ルート案内 や
標高 、
プレイス 、
タイムゾーン 情報などの地図データを提供することもできます。皆さん、ぜひ作成してみてください。
さらに、Firebase の代わりに Google Cloud Platform を使って同様のソリューションを実現することも可能です。手順は
こちらの記事 をご覧ください。
Posted by 丸山 智康 (Tomoyasu Maruyama) - Google Maps Solution Architect, Google Maps
[この記事は、Ken Nevarez, Industry Solutions Lead at Google による Geo Developers Blog の記事 "Geolocation and Firebase for the Internet of Things " を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。]
GPS は位置情報サービスの要ですが、GPS ハードウェアのコストや消費電力を抑えたいケースや、市街地や建物内など GPS の精度に欠ける場所にあるデバイスを探すといったケースも存在します。
近年、アセットの追跡や盗難防止、使用状況の最適化、アセットの保守点検などを目的として、GPS の代わりに Google Maps Geolocation API を用いた IoT(モノのインターネット)アプリケーションが増えています。そこで、Industry Solutions プロジェクトの一環として、周辺の Wi-Fi ネットワークと Google Maps Geolocation API を使って自身の位置を特定できる IoT デバイスのプロトタイプ作成に取り組みました。この記事では、実装した機能の一部を紹介すると共に、皆さんがご自分でもプロトタイプを作成できるよう、その方法を簡単に説明します。
作成した IoT デバイスは、周辺の Wi-Fi を探してその結果(Wi-Fi ホットスポットとその信号強度)を Firebase Realtime Database へ書き込みます。次に、バックエンド サービスがこのデータを読み出し、Google Maps Geolocation API を使ってそのデータから実際の位置を導き出します。導き出された位置は地図上にプロットすることができます。
デバイスのセットアップとローカルでの書き込み
これを実現するために、Intel Edison をLinux ベースのコンピューティング プラットフォームとして使い、これに Sparkfun の Edison Blocks を加えました。なお、デバイスの作成には Intel Edison 、Base Block 、Battery Block 、Hardwareパック がそれぞれ 1 つずつ必要です。
Edison の開発は Intel XDK IDE を使えば簡単です。JavaScript でシンプルな Node.js アプリケーションを作っていきます。今回、3 つのライブラリ(データベース接続には Firebase 、Wi-Fiネットワークの取得には wireless-tools/iwlist 、デバイスのMAC 取得には macaddress )を使用しました。実装手順はそれぞれのリンク先のページを参照してください。
ステップ 1: デバイスの MAC アドレスを取得して Firebase に接続する
function initialize() {
macaddress.one('wlan0', function (err, mac) {
mac_address = mac;
if (mac === null) {
console.log('exiting due to null mac Address');
process.exit(1);
}
firebase.initializeApp({
serviceAccount: '/node_app_slot/<service-account-key>.json',
databaseURL: 'https://<project-id>.firebaseio.com/'
});
var db = firebase.database();
ref_samples = db.ref('/samples');
locationSample();
});
}
上記のコードに含まれている 2 つのプレースホルダーは次のとおりです。
service-account-key は Firebase コンソールで作成するプライベート キーです。コンソールの左上部にある歯車アイコンから「settings」を選択して、Generate New Private Key をクリックします。このキーを Edison の /node_app_slot/ ディレクトリに入れます。詳しくは、Firebase のドキュメント をご覧ください。
データベース URLの中の project-id は、Google プロジェクトを Firebase と連携させた後、Firebase コンソールのデータベース ページに表示されます。
ステップ 2: 10秒毎に Wi-Fiネットワークをスキャンしてローカルに書き込む
function locationSample() {
var t = new Date();
iwlist.scan('wlan0', function(err, networks) {
if(err === null) {
ref_samples.push({
mac: mac_address,
t_usec: t.getTime(),
t_locale_string: t.toLocaleString(),
networks: networks,
});
} else {
console.log(err);
}
});
setTimeout(locationSample, 10000);
}
クラウドへの書き込み
上記の locationSample() は、検出可能なWi-Fi ネットワークを Firebase のデータベースに書き込む関数で、データベースはネットワーク接続時にクラウドと同期します。
補足説明 : Firebase へのアクセス権と認証の設定では、デバイスを「サーバー」として設定しました。この設定方法については Firebase ウェブサイト をご覧ください。今回、認証情報の保存に関しデバイスのセキュリティは十分であると仮定しましたが、皆さんが実装する際にこの点が該当しない場合は、クライアント JavaScript SDK の設定手順に従ってください。
データベースではワークロード管理に 3 つのキュー(Wi-Fi サンプル キュー、ジオロケーション結果キュー、視覚化データ キュー)を使用します。ワークフローは次のとおりです。デバイスからのサンプルがサンプル キューに入り、これを使ってジオロケーションが生成され、生成されたジオロケーションはジオロケーション キューに入れられます。ジオロケーションは取得されて表示用にフォーマット化され、デバイスによって整理された後、出力が視覚化バケットに保管され、フロントエンドのウェブサイトがこれを使用するという流れです。
以下に、Firebase Database コンソール上に表示されるサンプル、ジオロケーション、デバイスによって書き込まれた視覚化データの例を示します。
データを Google App Engine で処理する
サンプルデータの処理の実行には、長期実行中の Google App Engine Backend Module と Java Client for Google Maps Services のカスタム版を使用しました。
補足説明: Firebase と App Engine を併用する場合は、必ず手動スケーリングを使用してください。Firebase はバックグラウンド スレッドを使って変更をリッスンしていて、App Engine は手動でスケーリングされたバックエンド インスタンスに対する長期のバックグラウンド スレッドしか許可しません。
Java Client for Google Maps Services は Google Maps API を利用する際に必要な大量の通信コードを扱い、エラー処理について公開されているベスト プラクティス およびレート制限に従ったリトライ方針に従います。次に示す GeolocateWifiSample() 関数は、イベント リスナーとしてFirebase に登録されています。この関数はデバイスから報告される各ネットワークをループし、それをジオロケーション リクエストに組み込みます。
private void GeolocateWifiSample(DataSnapshot sample, Firebase db_geolocations, Firebase db_errors) {
// initalize the context and request
GeoApiContext context = new GeoApiContext(new GaeRequestHandler()).setApiKey("");
GeolocationApiRequest request = GeolocationApi.newRequest(context)
.ConsiderIp(false);
// for every network that was reported in this sample...
for (DataSnapshot wap : sample.child("networks").getChildren()) {
// extract the network data from the database so it’s easier to work with
String wapMac = wap.child("address").getValue(String.class);
int wapSignalToNoise = wap.child("quality").getValue(int.class);
int wapStrength = wap.child("signal").getValue(int.class);
// include this network in our request
request.AddWifiAccessPoint(new WifiAccessPoint.WifiAccessPointBuilder()
.MacAddress(wapMac)
.SignalStrength(wapStrength)
.SignalToNoiseRatio(wapSignalToNoise)
.createWifiAccessPoint());
}
...
try {
// call the api
GeolocationResult result = request.CreatePayload().await();
...
// write results to the database and remove the original sample
} catch (final NotFoundException e) {
...
} catch (final Throwable e) {
...
}
}
GeolocateWifiSample() 関数をイベント ハンドラーとして登録します。ジオロケーションの結果を処理して視覚化データを生成するそれ以外のリスナーも、同じようなパターンで作成します。
ChildEventListener samplesListener = new ChildEventListener() {
@Override
public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
// geolocate and write to new location
GeolocateWifiSample(dataSnapshot, db_geolocations, db_errors);
}
...
};
db_samples.addChildEventListener(samplesListener);
データの視覚化
デバイスの位置の視覚化には、Google App Engine を使います。保存されたデータを Firebase から渡して、Google Maps JavaScript API を使って結果表示用のシンプルなウェブページを作成しました。index.html ページの中身は空の <div> とID “map” です。この <div> を初期化して Google Map オブジェクトを入れました。また、イベントハンドラーの “child_added” と “child_removed” を追加して、時間の経過に伴ってデータが変化したときにマップを更新するようにしています。
function initMap() {
// attach listeners
firebase.database().ref('/visualization').on('child_added', function(data) {
...
data.ref.on('child_added', function(vizData) {
circles[vizData.key]= new CircleRoyale(map,
vizData.val().lat,
vizData.val().lng,
vizData.val().accuracy,
color);
set_latest_position(data.key, vizData.val().lat, vizData.val().lng);
});
data.ref.on('child_removed', function(data) {
circles[data.key].removeFromMap();
});
});
// create the map
map = new google.maps.Map(document.getElementById('map'), {
center: get_next_device(),
zoom: 20,
scaleControl: true,
});
...
}
API は位置だけでなく精度表示も返すため、精度のコンポーネントを示すカスタム マーカー(半径内の範囲が脈動する)を作成しました。
2 個のデバイス(赤と青)と判明した位置の直近 5 か所
次に進むには
この記事では、インターネットに接続されたデバイス(ロボットからウェアラブル端末まで)を追跡するために、Google Maps Geolocation API を使った IoT デバイスの構築方法を紹介しました。App Engine の処理モジュールを拡張し、他の Google Maps API を使ってルート案内 や標高 、プレイス 、タイムゾーン 情報などの地図データを提供することもできます。皆さん、ぜひ作成してみてください。
さらに、Firebase の代わりに Google Cloud Platform を使って同様のソリューションを実現することも可能です。手順はこちらの記事 をご覧ください。
Posted by 丸山 智康 (Tomoyasu Maruyama) - Google Maps Solution Architect, Google Maps