Firebaseを使ってメール/パスワード認証を実装してみた
前々からFirebaseについて色々と勉強したいと思っていたが、5月のGWで時間に余裕が出来て勉強したのでその内容を共有するということと、メモ書きとして残すために記事を書きました。
目次
- 目次
- Firebaseって?
- 筆者の環境
- サンプルアプリの作成
- まとめ
- 参考・引用
Firebaseって?
まずFirebaseを知らない方に簡単に説明すると、Firebaseとは
Firebaseは、2011年にFirebase, Inc.が開発したモバイル・Webアプリケーション開発プラットフォームで、その後2014年にGoogleに買収された。 2020年3月現在、Firebaseプラットフォームには19の製品があり、9GAGを含む150万以上のアプリが利用されている。
とあり、Firebaseを使うことで様々なサードパーティーの認証を実装出来たり、Firebaseにデータを保存したり、クライアント端末に通知を送信する...etcといったことが比較的簡単に実装できるようになります。
筆者の環境
IDE:AndroidStudio4.21 ホストPC:Windows10 端末:Galaxy S8 SCV36 端末OS:Android9 使用言語:Java
サンプルアプリの作成
それでは今回の記事で作るサンプルアプリを紹介します。
また、これ以降ではコード前文の解説ではなく抜粋しての解説ですので、全ての処理を知りたい方はこちらのコードの全文を見ながら読むことを推奨します。
概要
起動直後のアプリのトップ画面は以下の画像のようになっています。
アカウントがある場合は「メールアドレスとパスワード」を入力してログインボタンをタップするとログインできます。
アカウントが無い場合は「SignUp」のスイッチを切り替えて、同じく「メールアドレスとパスワード」を入力してサインアップします。
なお、今回は特にメールアドレスの存在確認といった処理は行いません
(※後日、記事を出すかも…?)
「サインイン」と「サインアップ」の両方とも、認証フローが正常に終了すれば画面下部にユーザ情報が表示されるようにしています。
反対に、認証フローが失敗(メールアドレス or パスワードが違う)したならばトーストを表示させるようにしています
また、画面右上のオプションメニューからサインアウトすることが出来るようにもしています。
xmlの編集
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#dfe" android:orientation="vertical" tools:context=".MainActivity"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:layout_marginTop="15dp" android:text="@string/hello" android:textSize="25sp" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/description" android:layout_marginTop="5dp" android:text="@string/description" android:textSize="20sp" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:weightSum="1.1" android:orientation="horizontal"> <TextView android:id="@+id/mail_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="15dp" android:text="@string/mail" android:textSize="17sp" /> <EditText android:id="@+id/mail_edit" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="15dp" android:layout_marginStart="5dp" android:layout_weight="1" android:hint="@string/sample_mail" android:maxLines="1" android:maxLength="50" android:inputType="textEmailAddress"/> </LinearLayout> <com.google.android.material.textfield.TextInputLayout style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="2dp" android:layout_marginStart="5dp" android:layout_marginEnd="5dp" android:hint="@string/sample_pass" app:passwordToggleEnabled="true"> <com.google.android.material.textfield.TextInputEditText android:id="@+id/password_edit" android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="textPassword" android:maxLines="1" android:maxLength="20"/> </com.google.android.material.textfield.TextInputLayout> <Button android:id="@+id/execute_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginTop="10dp" android:text="@string/login_button" android:onClick="execute" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:weightSum="1"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:text="@string/signup" android:layout_weight="0.95" android:gravity="right"/> <Switch android:id="@+id/signup_switch" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:layout_weight="0" tools:ignore="UseSwitchCompatOrMaterialXml" /> </LinearLayout> <TextView android:layout_width="match_parent" android:layout_height="0dp" android:id="@+id/result" android:layout_weight="1" android:textSize="15sp" /> </LinearLayout>
最新のAndroidStudioを使ってる方はプロジェクトを作成した時点でxmlは「ConstraintLayout」になっていると思いますが、今回は「LinerLayout」を使用します。
com.google.android.material.textfield.TextInputLayout
app:passwordToggleEnabled="true"
上記の記述でユーザーは入力したパスワードをパスワード欄右のアイコンから確認できるようになります。
com.google.android.material.textfield.TextInputEditText
このフィールド内でパスワード入力欄の様々な設定(文字数制限やインプットタイプの指定等)が出来ます。
Firebaseとの連携設定
レイアウトや文字の記述が終わったら次は処理の記述なのですが、その前にFirebaseでアプリの登録を行い、連携を完了させておきましょう。
この記事では詳しい解説は行いませんが、Firebaseの公式ドキュメントでやり方が記述されているのでそちらをご覧ください。
SwitchListenerの定義
まず最初に「サインイン」と「サインアップ」の切り替えを行う「スイッチ」の処理を記述しましょう。*1
private class SignUpSwitchListener implements CompoundButton.OnCheckedChangeListener{ @Override public void onCheckedChanged(CompoundButton button, boolean isChecked){ flag = isChecked; if (flag){ execute_b.setText(getString(R.string.signup)); }else { execute_b.setText(getString(R.string.login_button)); } } }
onCheckedChanged
このメソッドの第2引数にswitchのon/offがtrue
とfalse
で格納されています。
そしてそれをflagというboolean型のグローバル変数に格納してその後の条件式や、他のメソッドでも用います。
ここではswitchの切り替えによってボタンテキストの中身を変更しています。
サインアップ時の処理
次に別クラスAuthenticationFlowClass
を作成してその中にまずはサインアップ処理を記述していきます。
public class AuthenticationFlowClass extends MainActivity{ private final FirebaseAuth mAuth; private final MainActivity mainActivity; AuthenticationFlowClass (MainActivity mainActivity){ this.mAuth = MainActivity.mAuth; this.mainActivity = mainActivity; } void CreateUser(String mail, String pass){ mAuth.createUserWithEmailAndPassword(mail, pass) .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() { @Override public void onComplete(@NonNull Task<AuthResult> task) { if (task.isSuccessful()){ Log.d("success","createUserWithEmail:success"); FirebaseUser user = mAuth.getCurrentUser(); mainActivity.updateUI(Objects.requireNonNull(user)); }else { Log.w("Error","createUserWithEmail:failure",task.getException()); Toast.makeText(mainActivity,"エラーが発生しました",Toast.LENGTH_SHORT).show(); } } }); } }
createUserWithEmailAndPassword
この内部でアカウント作成成功時と失敗時の処理がそれぞれ行われています。
第1引数にメールアドレス、第2引数にパスワードを渡して呼ぼ出します。
この時のmAuthはMainActivityのonCreate*2でFirebaseAuth.getInstance()
で初期化されています。
成功時はupdateUIに現在のログインしているユーザの情報を取得するgetCurrentUser()
によって格納されたFirebaseUser
を渡します。
(※updateUIについては後述します)
失敗時はトーストでエラーが発生しました
と表示されるようになっています。
サインイン時の処理
void SignIn(String email, String pass){ mAuth.signInWithEmailAndPassword(email, pass) .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() { @Override public void onComplete(@NonNull Task<AuthResult> task) { if (task.isSuccessful()){ FirebaseUser user = mAuth.getCurrentUser(); mainActivity.updateUI(Objects.requireNonNull(user)); }else { Toast.makeText(mainActivity,"Authentication failed.",Toast.LENGTH_SHORT).show(); } } }); }
signInWithEmailAndPassword
この内部でサインインの認証処理と、その結果を処理します。
第1引数はメールアドレス、第2引数はパスワードを渡して呼び出します。
成功時と失敗時の処理はサインアップ時と然程違いはありません。
updateUI処理
このupdateUI
ではメイン画面の下部にユーザ情報を表示させる画面表示処理です。
protected void updateUI(FirebaseUser user){ String email = "Email:" + user.getEmail() + "\n"; String emailVerified = "Verified:" + user.isEmailVerified() + "\n"; String uid = "Uid:" + user.getUid() + "\n"; String result_str = email + emailVerified + uid; resultText.setText(result_str); }
getEmail
FirebaseUserにはそれぞれの呼び出し元においてgetCurrentUser()
された現在のログインしているユーザの情報が格納されています。
その中でも`getEmail
はユーザのメールアドレスを取得するメソッドです。
user.isEmailVerified
このメソッドにはユーザのメールアドレスが認証されている(正しいメールアドレス)かどうかの情報を取得できるメソッドです。
ここでは解説していない確認メールの送信処理を別途、アプリ内に記述して処理を行うことでこの値がtrue
になります。
getUid
現在ログインしているユーザのユーザーID(一意の値)を取得することが出来るメソッドです。
認証フローを呼び出す
サインイン時の処理とサインアップ時の処理で認証フローの定義は行ったので、次はボタンを押したらその処理が呼び出されるコードを記述*3していきます。
public void execute(View view) { String mail_str = mail_e.getText().toString(); String pass_str = pass_e.getText().toString(); EditCheck(mail_str,pass_str); } private void EditCheck(String email,String pass){ if (email.length() > 0 && pass.length() > 0) { if (flag){ afc.CreateUser(email, pass); }else { afc.SignIn(email, pass); } } else if (email.length() == 0) { Toast.makeText(this, "mailアドレスが入力されていません", Toast.LENGTH_SHORT).show(); if (pass.length() == 0) { Toast.makeText(this, "passwordが入力されていません。", Toast.LENGTH_SHORT).show(); } } else { Toast.makeText(this, "passwordが入力されていません。", Toast.LENGTH_SHORT).show(); } }
execute
メールアドレスとパスワードを入力するエディットボックスからそれぞれ文字列を抽出して変数に代入しています。
またそれをEditCheck
という入力値チェックの関数に渡しています。
EditCheck
レイアウトファイルにも記載してメールアドレスとパスワードには文字数制限を掛けておきましたが、念のためJavaファイルの方でも文字数制限を記述しておきます。
また、そもそも入力が行われていないときや、制限文字数を超えて入力した場合はトーストを表示させて、認証フローに処理が飛ばないようにしています。
適切な文字数が入力された場合には、SwitchListenerで変化するflag
によって処理を分岐して認証処理を開始*4します。
サインアウト処理
アプリをアンインストールしたり、開きなおせば必然的にサインアウトも行われるでしょうが、それでは少し不便なのでユーザが明示的にサインアウトを行えるようにしましょう。
@Override public boolean onCreateOptionsMenu(Menu menu){ MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.menu_options_menu_list,menu); return super.onCreateOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item){ int itemId = item.getItemId(); if (itemId == R.id.signout){ mAuth.signOut(); finish(); overridePendingTransition(0,0); startActivity(getIntent()); overridePendingTransition(0,0); } return super.onOptionsItemSelected(item); }
signOut
ここではオプションメニューを用いてサインアウトを行えるようにしています。
(※オプションメニューの詳しい解説は行いません)
サインアウトを行うにはFirebaseAuth.getInstance.signOut
でOKです。
onStart()のOverride
最後に現在ログインしていてサインアウトをしていないにも関わらず、アプリを開きなおすたびにメールアドレスとパスワードの再入力を求めるのはセキュリティの高いアプリ(銀行...etc)でない限り、不便です。
よってアクティビティライフサイクルのonStartメソッドをオーバーライドしてログインが行われていたら、updateUI
を呼び出すような処理を追加します。
@Override public void onStart(){ super.onStart(); FirebaseAuth mAuth = FirebaseAuth.getInstance(); FirebaseUser currentUser = mAuth.getCurrentUser(); if (currentUser != null){ updateUI(currentUser); } }
まとめ
今回はFirebaseでメールアドレスとパスワードを用いた認証を行う方法を解説しました。
やはりFirebaseを使うと自分でユーザ認証の実装を行うという面倒くさいことをしなくてよくなるのは大きなメリットですよね。
参考・引用
https://firebase.google.com/docs/android/setup?hl=ja