Cloud Firestoreへアクセス
Androidアプリは、Android端末をお持ちの方なら誰でも手軽にインストールできます。一部のアプリはGメールやSNSアカウントなどを使用してユーザーを厳密に特定する必要がありますが、私が作成するアプリでは、バックアップデータや機種変更時の移行データなどの目的でアプリを特定する必要はありますが、Gmailなどのユーザーの個人情報を取得する必要はありません。
個人情報の入力は、漏洩や不正利用への不信感があるため、せっかくインストールしてくれたユーザーがアプリをアンインストールする可能性が高まります。もし個人情報が必要無いのであれば入力させない方が良いでしょう。
アプリを特定するだけであれば、匿名でログインした後に内部的にユーザーIDのコレクションを作成し、そこにユーザー毎のデータにアクセスするだけで十分です。
Firebase では、Authenticationという機能がユーザーの認証を担当し、Cloud Firestoreという機能がデータの保存や管理を担当します。Cloud Firestoreでは、ログインしたユーザーのUIDを使用して、ユーザーごとにデータを管理します。
Firebaseへの匿名ログインの方法については、以下を参考にしました。
匿名ログインを有効にする
今回、Firebaseへは匿名でログインします。そのために、Firebaseコンソールで、匿名ログインの許可を行います。「構築」-「Authentication」をクリックしてください。
「始める」をクリックします。
「Sign-in method」タブの「匿名」をクリックします。
「有効にする」をアクティブにして、「保存」をクリックします。
「匿名」が有効になりました。
使用するライブラリの依存関係の追加
モジュール(アプリレベル)の Gradle ファイルに、Firebase プラットフォームの一部であるユーザーの認証機能を提供する以下のライブラリ(主な機能:①匿名認証②メール/パスワード認証③サードパーティプロバイダ認証④電話番号認証 等)の依存関係を追加します。
dependencies {
・
implementation platform('com.google.firebase:firebase-bom:32.7.0')
・
・
implementation("com.google.firebase:firebase-auth")
・
}
Firebase Android BoM(部品表)は、Firebaseライブラリの複数のバージョンを管理する便利な機能です。BoM(部品表)のバージョンを指定するだけで、他のライブラリは、バージョンを記入しなくても自動的に依存関係を考慮してくれます。
匿名アカウントでログイン後、ユーザーのドキュメント生成
匿名ログインでログインした後、ユーザーIDのドキュメント情報があるかどうかを判定し、無ければユーザーIDのドキュメントを作成、あれば、ユーザーIDのドキュメント情報を取得します。
実現するためのコード
匿名認証を管理するためのクラスを作成し、そのメソッドをMainActivityから呼び出します。
【匿名認証を管理するためのクラス】
package biz.myftp.virnetcom.sub.filrebase;
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.firestore.DocumentReference;
import com.google.firebase.firestore.DocumentSnapshot;
import com.google.firebase.firestore.FirebaseFirestore;
import java.util.HashMap;
import java.util.Map;
/**
* Firebase匿名認証を管理するためのクラス
*/
public class FirebaseAuthManager {
/**
* Firebase認証とFirestoreデータベースを管理するためのタグ
*/
private static final String TAG = "FirebaseAuthManager";
/**
* Firebase認証を扱うためのインスタンス
*/
private FirebaseAuth mAuth;
/**
* Firestoreデータベースを扱うためのインスタンス
*/
private FirebaseFirestore db;
/**
* アプリケーションコンテキスト
*/
private Context context;
/**
* ユーザーデータを格納するためのマップ
*/
Map<String, Object> userData = new HashMap<>();
/**
* FirebaseAuthManagerのコンストラクタ
*
* @param context アプリケーションコンテキスト
*/
public FirebaseAuthManager(Context context) {
this.context = context;
mAuth = FirebaseAuth.getInstance();
db = FirebaseFirestore.getInstance();
}
/**
* FirebaseAuth のインスタンスを初期化するメソッド
* onCreate メソッドで呼び出す
*/
public void initializeFirebaseAuth() {
// onCreate メソッドで FirebaseAuth のインスタンスを初期化
mAuth = FirebaseAuth.getInstance();
}
/**
* 匿名でログインし、ユーザーの存在を確認し、存在しない場合は新しいドキュメントを作成するメソッド
*/
public void signInAnonymouslyAndCheckUser() {
mAuth.signInAnonymously() // todo ← ①匿名ユーザーでログインし、結果を非同期処理のAPI(Task)に取得する
.addOnCompleteListener(new OnCompleteListener() {
@Override
public void onComplete(@NonNull Task task) {
if (task.isSuccessful()) {
// ログイン成功時の処理
FirebaseUser user = mAuth.getCurrentUser();
checkUserDocument(user.getUid());
} else {
// ログイン失敗時の処理
Log.w(TAG, "signInAnonymously:failure", task.getException());
}
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
Log.e(TAG, "signInAnonymously:failure", e);
}
});
}
/**
* onStart メソッドでの操作をまとめたメソッド
*/
public void onStartOperations() {
// onStart メソッドでの処理をここに記述
signInAnonymouslyAndCheckUser();
}
/**
* ユーザーのドキュメントを確認し、存在しない場合は新しいドキュメントを作成するメソッド
*
* @param userId FirebaseユーザーID
*/
private void checkUserDocument(String userId) {
DocumentReference userRef = db.collection("users").document(userId);
userRef.get().addOnCompleteListener(new OnCompleteListener() { // ← ②Firestoreユーザーデータの取得
@Override
public void onComplete(@NonNull Task task) { // ← ユーザーデータの取得結果はここに返る
if (task.isSuccessful()) {
DocumentSnapshot document = (DocumentSnapshot) task.getResult();
if (document.exists()) {
// ドキュメントが存在する場合の処理
Log.d(TAG, "User document exists!");
} else {
// ドキュメントが存在しない場合の処理
createUserDocument(userId);
}
} else {
// ドキュメント取得失敗時の処理
Log.e(TAG, "Failed to get user document", task.getException());
}
}
});
}
/**
* 新しいユーザードキュメントを作成するメソッド
*
* @param userId FirebaseユーザーID
*/
private void createUserDocument(String userId) {
userData.put("name", "John Doe");
userData.put("email", "john.doe@example.com");
// ドキュメント作成
db.collection("users").document(userId) // ← ③Firestoreドキュメント("users")に対してデータの書き込み
.set(userData)
.addOnCompleteListener(new OnCompleteListener() {
@Override
public void onComplete(@NonNull Task task) { // ← 結果はここに返る
if (task.isSuccessful()) {
// ドキュメント作成成功時の処理
Log.d(TAG, "User document created!");
} else {
// ドキュメント作成失敗時の処理
Log.e(TAG, "Failed to create user document", task.getException());
}
}
});
}
}
【MainActivity:匿名認証を管理するクラスの呼び出し】
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
/**
* Firebaseを使用したAndroidアプリのメインアクティビティクラス.
*/
public class MainActivity extends AppCompatActivity {
private FirebaseAuthManager firebaseAuthManager;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
firebaseAuthManager= new FirebaseAuthManager(this);
firebaseAuthManager.initializeFirebaseAuth(); // ← Firebase初期化処理呼び出し
}
@Override
protected void onStart() {
super.onStart();
firebaseAuthManager.onStartOperations(); // ← 匿名ログイン処理呼び出し
}
}
実行した結果
“USERS”コレクションが無い状態で上記ソースを実行した結果です。
“USERS”コレクションが作成され、その中に “mXFNer・・・・・(ユーザーID)” ドキュメントが作成され、さらにその中に email フィールドと name フィールドに値が設定されているのがわかります。
テストで作成したコレクション、ドキュメント、フィールドは簡単に削除できます。コレクションやドキュメントを削除するには三点リーダー(︙)をクリックし、削除メニューを選択します。フィールドを削除するには、該当するフィールドの名前にカーソルを合わせるとゴミ箱マークが表示されます。それをクリックすると、フィールドを簡単に削除できます。
ソースの説明
①匿名ユーザーでログインし、結果を非同期処理のAPI(Task)に取得する
signInAnonymously はFirebase Authentication(Firebaseの認証サービス)で匿名ユーザーを認証するためのメソッドです。このメソッドを使用すると、ユーザー(androidアプリ)は匿名でアプリにログインをしようとします。
ログインが成功すると、Firebaseはログインユーザー(androidアプリ)にユニーク(一意)なユーザーIDを割り当てます。android端末毎にこのユーザーIDを割り当て、ドキュメントを作成し、そのドキュメントの中にデータの保存やクエリを行うことができます。
addOnCompleteListener は、非同期処理が完了した際に呼び出されるコールバックを登録するためのメソッドです。ログイン処理が完了すると、Firebaseサービスは、引数のOnCompleteListener関数を呼び出し、その結果(成功か失敗)を通知します。
addOnFailureListenerメソッドは、ネットワーク接続の問題等により、onCompleteメソッドに戻ってこない等、ログインの過程でエラーが発生した場合に実行されるリスナーです。エラーが発生した場合は、ログにエラーメッセージを出力します。
通信エラーや他の理由でFirestoreデータベースにアクセスできない場合、.addOnFailureListener
を使って再度ログインを試みることができますが、必ずログインできる保証はありません。そのため、その後のドキュメントへのアクセスなどの処理で匿名ログインしていないとエラーが発生します。このエラーを避けるために、以下のように、事前に匿名ログインしているかどうかを確認してから処理を実行する方法もあります。
FirebaseAuth mAuth = FirebaseAuth.getInstance();
FirebaseUser currentUser = mAuth.getCurrentUser();
if (currentUser != null && currentUser.isAnonymous()) {
// 現在のユーザーが匿名ユーザーである場合の処理(ログインしていることが前提の処理)
Log.d(TAG, "現在のユーザーは匿名ユーザーです");
} else {
// 匿名ユーザーでない場合の処理(匿名ログインしていない場合の処理:再度ログイン等)
Log.d(TAG, "現在のユーザーは匿名ユーザーではありません");
}
②Firestoreユーザーデータの取得
このメソッドは、与えられたユーザーIDに対応するFirestoreのユーザードキュメントが存在するかどうかを確認します。具体的な処理は以下の通りです。
- db.collection(“users”).document(userId)を使用して、指定されたユーザーIDに対応するFirestoreのドキュメントへの参照(userRef)を取得します。
- userRef.get()を使用して、Firestoreからユーザードキュメントの内容を取得する非同期処理を開始します(②Firestoreユーザーデータの取得)。
- addOnCompleteListenerメソッドを使用して、非同期処理の完了時に実行されるリスナーを登録します。このリスナーのonCompleteメソッドは、非同期処理が完了した際に呼び出され、取得した結果を受け取ります(ユーザーデータの取得結果はここに返る)。
- task.isSuccessful()を使用して、タスクが成功したかどうかを確認します。成功した場合は、取得したドキュメントを表すDocumentSnapshotオブジェクトを取得し、そのドキュメントが存在するかどうかを確認します。
- ドキュメントが存在する場合は、ログにメッセージを出力します(”User document exists!”)。存在しない場合は、新しいユーザードキュメントを作成するcreateUserDocument()メソッドを呼び出します。
- 非同期処理が失敗した場合は、エラーログを出力します(”Failed to get user document”)。
このメソッドを呼び出すことで、ユーザードキュメントの存在を確認し、必要に応じて新しいドキュメントを作成できます。
userRef.get()は、Source.CACHEを引数として指定することも可能です。Source.CACHEは、データ取得のソースを指定するパラメータであり、ローカルのキャッシュからデータを取得する設定です。Android APIでは、アプリがオフラインでもスムーズに動作させるために、デフォルトで(何もしなくても)キャッシュへアクセスする設定が有効になっています。
キャッシュデータは、オフラインからオンラインへの切り替え時に、Firestore SDKが自動的にリモートサーバーと同期します。つまり、キャッシュは常に最新の状態を維持しようとします。一見、キャッシュにアクセスするだけで問題無いように思えますが、『キャッシュの有効期限切れ』『同期の失敗』『他の端末からのデータ更新』などの理論通りではない問題が発生する可能性がありますので、『エラーハンドリング』や『オンラインからの直接データ取得』など、きちんと設計した上で実装する必要があります。
③Firestoreドキュメント(“users”)に対してデータの書き込み
このメソッドは、与えられたユーザーIDに対応するFirestoreのユーザードキュメントを新規に作成します。具体的な処理は以下の通りです。
- userDataという名前のMapを使用して、新しいユーザードキュメントのデータを準備します。この例では、名前と電子メールアドレスのフィールドを追加しています。
- db.collection(“users”).document(userId)を使用して、指定されたユーザーIDに対応するFirestoreのドキュメントへの参照を取得します。そして、そのドキュメントにデータを書き込む非同期処理を開始します(③Firestoreドキュメント(“users”)に対してデータの書き込み)。
- set(userData)メソッドを使用して、準備したユーザーデータをFirestoreに書き込みます。
- addOnCompleteListenerメソッドを使用して、非同期処理の完了時に実行されるリスナーを登録します。このリスナーのonCompleteメソッドは、非同期処理が完了した際に呼び出され、書き込みの結果を受け取ります(結果はここに返る)。
- task.isSuccessful()を使用して、タスクが成功したかどうかを確認します。成功した場合は、ログにメッセージを出力します(”User document created!”)。失敗した場合は、エラーログを出力します(”Failed to create user document”)。
set()メソッドは指定されたデータ(上記例ではuserData)でドキュメント全体を置き換えます。つまり、以前のデータは消えてしまいます。このため、すべてのデータを取得してから一部を変更して再びデータベースに保存するという方法で更新処理を実装した場合、他の端末に入れた同じアプリから同時に同じデータを変更すると、データの一貫性が保たれなくなります。
一方、update()メソッドを使用すると、データの一部だけをそのまま更新できます。つまり、他の項目の一貫性は保たれた状態で、指定した項目だけの変更ができます。updateという名前ですが、存在しないフィールドにupdateすると新規作成になります。
set()メソッドには、Source.CACHEのようなパラメータはありません。アプリがオフラインの場合、自動的にキャッシュへの書き込みが行われます。キャッシュデータは、オフラインからオンラインへの切り替え時にFirestore SDKが自動的にリモートサーバーと同期します。読み込みとは異なり、更新の場合は競合が発生する可能性があるため、アプリで適切な競合解決ポリシーを実装することが重要です。
補足
ここまでお読みいただき、ありがとうございます。これからは私の趣味についてお話します。もしよろしければ、もうしばらくお付き合いください。
この道具は、使い古された緑茶や前茶を煎って、手で振りながら強火で1分ほどでほうじ茶にするための道具です。
通常の使い方ではなく、コーヒー生豆の焙煎に使います。妻と週末に飲むために、毎週月曜日に10分だけ費やしています。
不器用な男性は、労力を通じて感謝や愛情を表現します
コーヒー豆を焙煎すると、そのおいしさがピークに達するのは焙煎後約5日目くらいだと言われています。一般的には、焙煎後3日から14日の間が最も美味しい期間とされています。週末に消費する分としては、約60g(約6杯分)が適量です。
私は以前はブルーマウンテンやエメラルドマウンテンのような酸味のある豆が好みでしたが、最近は高価で手が届かなくなりました。酸味のある豆の中で、手ごろな価格で手に入る代表がモカです。モカは広く知られているため、価格が比較的安定しているでしょう。私の好みの豆には及びませんが、モカも非常においしい豆の一つです。
たまには贅沢に、最高級グレードのG1を試してみませんか?少量でも手ごろな価格で入手できると思います。
1杯あたりの価格は約10gで50~60円ほどです。G1のモカをこの価格で味わえるのは本当にお得です。財布に厳しい妻も大満足です。
なんだか、コーヒーが飲みたくなってきたわ。
最後まで読んでいただき、ありがとうございました。
コメント