この記事は、Xamarin Advent Calendar 2018の5日目です。
Firebaseすごく便利です。データベース、ストレージ、認証などなど、バックエンドで必要な機能が簡単に使えてしまいます。Xamarinでもライブラリが用意されており、利用することが可能です。ただ、そのライブラリというのが、iOS用とAndroid用に分かれており、Xamarin.Formsで使うには、DependencyServiceを使うなどしなければならず、ちょっと面倒でした。そこで、Xamarin.Formsでそのまま使えるようなライブラリを作成しました。
作成したライブラリ
Firebaseの5つの機能のライブラリを作成しました。NuGetで公開済みなのですぐに利用できます。
使い方は、それぞれのGitHubをみてください。(詳しい機能の説明はしていないので、公式のドキュメントも参照してください)
NuGet
- Plugin.CloudFirestore
- Plugin.FirebaseAuth
- Plugin.FirebaseStorage
- Plugin.FirebaseAnalytics
- Plugin.FirebaseCrashlytics
GitHub
- f-miyu/Plugin.CloudFirestore
- f-miyu/Plugin.FirebaseAuth
- f-miyu/Plugin.FirebaseStorage
- f-miyu/Plugin.FirebaseAnalytics
- f-miyu/Plugin.FirebaseCrashlytics
Plugin.CloudFirestore
Cloud Firestoreを利用するためのライブラリです。
データベースから取得した時のデータの型は、当然iOSとAndroidでは異なるので、共通の型にマッピングする機能を備えています。また、リアルタイムでのデータの更新は、Rxが利用できるようにしています。
Plugin.FirebaseAuth
Authenticationを利用するためのライブラリです。
Plugin.CloudFirestoreやPlugin.FirebaseStorageとは、セットで使うことになります。
また、各プロバイダーへの認証の機能はないので、Xamarin.Auhtなどを使ってください。
Plugin.FirebaseStorage
Storageを利用するためのライブラリです。
進捗のモニタリングやキャンセル、一時停止にも対応しています。
Plugin.FirebaseAnalytics
アナリティクスを利用するためのライブラリです。
Plugin.FirebaseCrashlytics
Crashlyticsを利用するためのライブラリです。
昨日の記事にも書かれてあるように、通常色々やらないといけないのをこれを入れるだけで大丈夫なようにもしています。
サンプルアプリ
これらのライブラリを利用したサンプルアプリも作成しました。画像を投稿して、リストに表示されて、いいねができる、ちょっとしたSNSアプリです。
メールアドレスとGoogleでのログインができるようにしています。
リストは、リアルタイム監視をしているので、誰かが投稿やいいねをすると自動で表示が更新されます。
サンプルアプリを動かすには
サンプルアプリのGitHubには、GoogleService-Info.plist
やgoogle-services.json
を含めていないので、動かすには各自でFirebaseの設定を行なってください。
以下で設定の仕方を説明します。
Firebaseプロジェクトの作成
Firebaseのコンソールで、[プロジェクトの追加]をクリックします。
プロジェクトの追加画面で、プロジェクト名を入力し、アナリティクスの地域を日本に設定、同意に
チェックをして、[プロジェクトを作成]をクリックします。
トップの画面の、[iOS]ボタンをクリックします。
iOSのバンドルIDを入力して、[アプリの登録]をクリックします。
[GoogleService-Info.plistをダウンロード]をクリックします。
GoogleService-Info.plistがダウンロードできたら、XamarinFirebaseSample.iOSプロジェクトのルートに追加します。また、ビルドアクションがBundleResource
になっていることを確認してください。
次にAndroidの登録を行います。[アプリを追加]をクリックして、Androidを選択します。
Androidのパッケージ名を入力します。また、サンプルアプリでは、Googleログインを行なっているので、SHA-1の入力も必要です。
Xamarin.Androidで使用する、debug.keystoreのSHA-1フィンガープリントを取得するには、ここを参考にしてください。
入力したら、[次へ]をクリックします。
[google-services.jsonをダウンロード]をクリックします。
google-services.jsonがダウンロードできたら、XamarinFirebaseSample.Androidプロジェクトのルートに追加します。また、ビルドアクションがGoogleServicesJson
になっていることを確認してください。
Cloud Firestoreの設定
[Database]セクションで、[データベースの作成]をクリックします。
[有効にする]をクリックします。
セキュリティルールの変更
認証しなければ、読み込みも書き込みもできないようにします。
[ルール]タブで、以下のように記述して、[公開]をクリックします。
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth.uid != null;
}
}
}
複合インデックスの作成
サンプルアプリでは、自分の投稿を新しい順に取得しているため、複合インデックスの作成が必要です。
[インデックス]タブで、[手動でインデックスを作成]をクリックします。
複合インデックスの作成画面で以下のように設定して、[インデックスの作成]をクリックします。
対象となるコレクション
items
フィールド
順番 | フィールドのパス | モード |
---|---|---|
1 | OwnerId | Ascending |
2 | Timestamp | Descending |
今回は、手動で設定しましたが、まだ設定していない時に、複合インデックスが必要なクエリを行なったら、以下のような例外が発生します。そこのメッセージにあるアドレスを叩くことでも設定ができます。
Authenticationの設定
[Authentication]セクションで、[ログイン方法]タブを選択します。
[メール/パスワード]を有効にして、[保存]をクリックします。
[Google]を有効にし、メールアドレスを選択して、[保存]をクリックします。
Storageの設定
[Storage]セクションで、[スタートガイド]をクリックします。
セキュリティルールが表示されるので[OK]をクリックします。
Crashlyticsの設定
[Crashlytics]セクションで、[Crashlytics を設定]をクリックします。
[このアプリではCrashlyticsを初めて使用します]を選択して、[次へ]をクリックします。
この設定は、iOS、Androidのアプリごとに行う必要があります。
プログラム側の設定
AppConstans
XamarinFirebaseSampleプロジェクトのHelpers
フォルダのAppConstants.cs
を修正します。
ここには、Googleログインで使用するクライアントIDを設定します。
public static class AppConstans
{
public const string IosGoogleClientId = "";
public const string IosReversedGoogleClientId = "";
public const string AndroidGoogleClientId = "";
public const string AndroidReversedGoogleClientId = "";
}
iOSの設定
iOSは、先ほどダウンロードしたGoogleService-Info.plist
で確認できます。
IosGoogleClientId
は、CLIENT_ID
キーの値を、IosReversedGoogleClientId
には、REVERSED_CLIENT_ID
の値を設定します。
Androidの設定
Androidは、APIコンソールの認証情報ページで確認できます。OAuth 2.0 クライアント IDにあるタイプがAndroidであるクライアントIDをコピーして、AndroidGoogleClientId
に設定します。AndroidReversedGoogleClientId
には、その逆ドメインを設定します。
iOSのURLスキームの設定
iOSプロジェクトのinfo.pist
を開き、[詳細設定]画面にします。
[URLの種類を追加]をクリックして、[URL Schenes:]に先ほども利用したGoogleService-Info.plist
のREVERSED_CLIENT_ID
の値を設定します。
これで全ての設定が完了です。ビルドして動かしてみてください。
解説
サンプルアプリについて少し解説を行います。
Services
データの取得やログイン処理などのロジックは、Servicesフォルダ内のクラスで行なっています。ライブラリの使い方は、これらのクラスを見てもらえればいいです。
以下がその一覧です。
クラス | 機能 | 利用Firebase | 備考 |
---|---|---|---|
AccountService | アカウントの管理 | CloudFirestore, Auth | ユーザーデータの監視も行なっています |
AuthService | 認証 | なし | Xamarin.Authを利用した認証処理を行なっています |
ContributionService | アイテムの投稿 | CloudFirestore | StorageServiceを利用して画像アップロードをしています |
EmailLoginService | メールアドレスでのログイン | なし | AccountServiceを利用してログイン処理を行なっています |
GoogleLoginService | Googleログイン | なし | AccountService、AuthServiceを利用してログイン処理を行なっています |
ItemListService | アイテムリストの取得 | CloudFirestore | アイテムデータの監視を行なっています |
ItemService | アイテムデータの取得、更新 | CloudFirestore, Analytics | データの取得後、Analyticsでview_itemイベントを送信しています |
SignupService | サインアップ | なし | AccountServiceを利用してサインアップ処理を行なっています |
StorageService | 画像のアップロード | Storage | 画像をアップロードして、ダウンロードURLの取得を行なっています |
UserItemListService | 投稿アイテムリストの取得 | CloudFirestore | 複合インデックスが必要なクエリを実行しています |
アイテムリスト
ホーム画面のアイテムリストは、アイテムの追加や削除を行うと自動で更新されるようになっています。また、最初に投稿アイテムを一気に取得するのではなく、下にスクロールして最後まで表示されると20件ずつ読み込むようになっています。
それらの処理は、ItemListService
のLoadAsync
で行なっています。最後のアイテムが表示された時なら、LoadAsync
を呼び出し、続きの20件を取得するようにしています。
実際にリアルタイム監視を行なっているのは以下の部分です。
var query = _firestore.GetCollection(Item.CollectionPath)
.OrderBy(nameof(Item.Timestamp), true)
.EndAt(new long[] { _lastTimestamp });
query.ObserveAdded()
.Where(d => _items.FirstOrDefault(i => i.Id == d.Document.Id) == null)
.Subscribe(d => _items.Insert(d.NewIndex, d.Document.ToObject<Item>()))
.AddTo(_disposables);
query.ObserveModified()
.Select(d => d.Document.ToObject<Item>())
.Subscribe(item =>
{
var targetItem = _items.FirstOrDefault(i => i.Id == item.Id);
if (targetItem != null)
{
item.CopyTo(targetItem);
}
})
.AddTo(_disposables);
query.ObserveRemoved()
.Select(d => _items.FirstOrDefault(i => i.Id == d.Document.Id))
.Where(item => item != null)
.Subscribe(item => _items.Remove(item))
.AddTo(_disposables);
ObserveAdded
で追加、ObserveModified
で変更、ObserveRemoved
で削除の監視を行なっています。
アイテムデータには、追加された時間であるTimestamp
を持っています。_lastTimestamp
は、その表示されている最後のアイテムのTimestamp
なので、EndAt
で最後より新しいアイテムデータを監視していることになります。
ちなみに、Timestamp
はlong
型で、DateTime.Now.Ticks
を与えています。時間ならDateTime
型でもいいのではないかと思いますが、それだとうまく動かないことがあります。なぜなら、NSDate
やJava.Util.Date
から、DateTime
に変換するときに微妙に誤差が出てしまい、データベース上に持っている値と異なることがあるからです。なので、誤差の出ることがないlong
型を使うようにしています。
データ監視の解除
Prismでは、ページが破棄された時にはDestroy
が呼ばれるので、監視の解除はこのタイミングで行えばいいです。
ただ、Androidでは、バックボタンでアプリを終了した場合は、Destroy
が呼ばれないので、監視をバックグラウンドで続けたままになってしまいます。これはよろしくないので、MainActivity
のOnDestroy
で解除処理を走らせています。(App
クラスのDestroy
が解除処理です。)
protected override void OnDestroy()
{
base.OnDestroy();
App.Destroy();
}
トランザクション
いいねした時のいいね数の更新は、取得している値をインクリメントしていますが、そのまま普通に更新してしまうと、同時にいいねされた時などにデータの不整合が起きる可能性があります。このような場合に対応するために、Firestoreにはトランザクションの機能があります。
ItemService
のLikeAsync
で以下のように利用しています。
var success = await _firestore.RunTransactionAsync(transaction =>
{
var document = _firestore.GetCollection(Models.Item.CollectionPath)
.GetDocument(Item.Value.Id);
var item = transaction.GetDocument(document).ToObject<Item>();
if (item == null)
return false;
item.LikeCount++;
transaction.UpdateData(document, item);
return true;
}).ConfigureAwait(false);
Analyticsのscreen_viewイベント
Analyticsは、画面遷移が発生すると自動的にUIViewController
やActivity
のクラス名でscreen_view
イベントを送るのですが、Xamarin.Formsではうまく機能しません。iOSでは、遷移は同じクラスのViewControllerが作られるので、画面の判別がつきません。Androidは、1つのActivityでViewを切り替えているだけなので、イベント自体送られません。仕方ないので、サンプルアプリでは、OnAppearing
時に手動でイベントを送るようにしています。(OnNavigatedTo
でもいいのですが、最初に表示される画面がうまく設定されなかったためOnAppearing
にしました)
public virtual void OnAppearing()
{
var typeName = GetType().Name;
CrossFirebaseAnalytics.Current.SetCurrentScreen(typeName, typeName);
}
まとめ
Xamarin + Firebaseはいいぞ!
明日は、@ktz_alias さんです。よろしくお願いします!