FirebaseUI を使った Android ログイン

この Codelab は、Kotlin を使った高度な Android 開発コースの一部です。Codelab を順番に進めると、このコースを最大限に活用できますが、これは必須ではありません。コースの Codelab はすべて、Kotlin を使った高度な Android 開発の Codelab ランディング ページに記載されています。

はじめに

Android アプリを構築する際に、ユーザーのログインをサポートすることで多くのメリットが得られます。ユーザーがアプリ内で ID を作成できるようにすることで、アプリの利用方法を増やすことができます。

パーソナライズされたアカウントを使用すると、ユーザーはアプリ内での操作をカスタマイズしたり、他のユーザーと交流したりできます。また、別のデバイス(ウェブや新しいスマートフォンなど)でアプリを使用している場合は、データを保持して転送することもできます。

この Codelab では、FirebaseUI ライブラリを使用してアプリのログインをサポートする基本的な方法を学びます。FirebaseUI ライブラリは、ログインフローを構築したいデベロッパーにとって使いやすく、ユーザー アカウントの管理作業を処理します。

前提となる知識

  • Android アプリの作成方法の基礎
  • LiveData と ViewModel

学習内容

  • プロジェクトに Firebase を追加する方法
  • Android アプリのログインをサポートする方法
  • アプリの現在の認証ステータスを確認する方法
  • ユーザーをログアウトさせる方法

演習内容

  • Firebase コンソールを使用して、アプリに Firebase を統合します。
  • ログイン機能を実装します。
  • ログインしているユーザー向けにアプリでカスタマイズを追加します。
  • ユーザーのログアウトを実装します。

LiveData と ViewModel について

この Codelab のアプリでは、LiveData と ViewModel の基本的な知識が必要です。これらのコンセプトの概要については、LiveDataViewModel の概要をご覧ください。

また、Kotlin による Android アプリの開発コースを受講して、この Codelab の一部として取り組むことになる Android の基本的なトピックについて学ぶこともできます。このコースは、Udacity コースコードラボ コースの両方で受講できます。

この Codelab では、Android に関する楽しい事実を表示するアプリを作成します。また、アプリには [ログイン/ログアウト] ボタンが表示されます。ユーザーがアプリにログインしている場合、表示される Android の豆知識には、パーソナライズされた挨拶が含まれます。

サンプルアプリを次のいずれかの方法でダウンロードします。

ZIP をダウンロード

または、次のコマンドを使用して、コマンドラインから GitHub リポジトリのクローンを作成し、リポジトリの start ブランチに切り替えます。

$  git clone https://github.com/googlecodelabs/android-kotlin-login

重要: Firebase を使用するようにアプリを統合するため、スターター アプリをビルドして実行するには、いくつかの設定が必要です。これについては、Codelab の次のステップで対応します。

ステップ 1: Firebase プロジェクトを作成する

Android アプリに Firebase を追加する前に、Android アプリに接続するための Firebase プロジェクトを作成します。

  1. Firebase コンソールで [プロジェクトを追加] をクリックします。
  2. プロジェクト名を選択または入力します。プロジェクトには任意の名前を付けることができますが、作成するアプリに関連する名前を選択してください。
  3. [続行] をクリックします。
  4. Google アナリティクスの設定をスキップして、[今は設定しない] オプションを選択できます。
  5. [Create Project] をクリックして、Firebase プロジェクトの設定を完了します。

ステップ 2: アプリを Firebase に登録する

Firebase プロジェクトを作成したら、プロジェクトに Android アプリを追加できます。

  1. Firebase コンソールの [プロジェクトの概要] ページの中央にある Android アイコンをクリックして設定ワークフローを起動します。
  2. アプリのアプリケーション ID を [Android パッケージ名] フィールドに入力します。アプリを Firebase プロジェクトに登録した後で、この値を追加または変更することはできません。必ずアプリで使用している ID を入力してください。
  1. アプリケーション ID はパッケージ名と呼ばれることもあります。
  2. このアプリケーション ID はモジュール(アプリレベル)の Gradle ファイル(通常は app/build.gradle)内に記載されています(アプリケーション ID の例: com.yourcompany.yourproject)。
  3. デバッグ用の署名証明書の SHA-1 を入力します。この鍵は、コマンドライン ターミナルで次のコマンドを入力して生成できます。
keytool -alias androiddebugkey -keystore ~/.android/debug.keystore -list -v -storepass android
  1. [アプリの登録] をクリックします。

ステップ 3: Firebase 構成ファイルをプロジェクトに追加する

Firebase Android 構成ファイルをアプリに追加します。

  1. [google-services.json をダウンロード] をクリックして、Firebase Android 構成ファイル(google-services.json)を取得します。
  • Firebase Android 構成ファイルはいつでも再ダウンロードできます。
  • 構成ファイルに余分な文字が追加されていないこと、google-services.json という名前のみであることを確認します。
  1. 構成ファイルをアプリのモジュール(アプリレベル)ディレクトリに移動します。

ステップ 4: Firebase プロダクトを有効にするように Android プロジェクトを構成する

  1. アプリで Firebase プロダクトを有効にするには、Gradle ファイルに google-services プラグインを追加します。
  1. ルートレベル(プロジェクト レベル)の Gradle ファイル(build.gradle)に、Google サービス プラグインを含めるためのルールを追加します。Google の Maven リポジトリがあることも確認してください。

build.gradle

buildscript {

  repositories {
    // Check that you have the following line (if not, add it):
    google()  // Google's Maven repository
  }

  dependencies {
    // ...

    // Add the following line:
    classpath 'com.google.gms:google-services:4.3.0'  // Google Services plugin
  }
}

allprojects {
  // ...

  repositories {
    // Check that you have the following line (if not, add it):
    google()  // Google's Maven repository
    // ...
  }
}
  1. モジュール(アプリレベル)の Gradle ファイル(通常は app/build.gradle)で、ファイルの末尾に以下の行を追加します。

app/build.gradle

apply plugin: 'com.android.application'

android {
  // ...
}

// Add the following line to the bottom of the file:
apply plugin: 'com.google.gms.google-services'  // Google Play services Gradle plugin

ステップ 4: Firebase の依存関係を追加する

この Codelab で Firebase を統合する主な理由は、ユーザーを作成して管理する方法を確保するためです。そのためには、ログインを実装できる Firebase ライブラリを追加する必要があります。

  1. アプリで SDK を使用できるように、build.gradle (Module:app) ファイルに次の依存関係を追加します。firebase-auth SDK を使用すると、アプリの認証済みユーザーを管理できます。

app/build.gradle:

implementation 'com.firebaseui:firebase-ui-auth:5.0.0'
  1. プロジェクトを Gradle ファイルと同期して、アプリで必要な依存関係がすべて利用可能であることを確認します。同期を求めるメッセージが表示されない場合は、Android Studio の [File] > [Sync Project with Gradle Files] を選択するか、ツールバーから選択します。

ステップ 5: アプリを実行してコードを検査する

  1. エミュレータまたは実機でアプリを実行し、開発を開始するための環境が正常に設定されていることを確認します。

成功すると、ホーム画面の左上に Android の楽しい事実とログイン ボタンが表示されます。ログインボタンをタップしても、まだ何も起こりません。

大まかに言うと、これは複数のフラグメントを持つ単一アクティビティのアプリです。MainFragment には、以下の画面に表示されるすべての UI が含まれています。(LoginFragmentSettingsFragment については、フォローアップの Codelab で扱います)。

  1. コードをよく理解してください。特に、次の点に注意してください。
  • FirebaseUserLiveData は、アプリに関連付けられている現在の Firebase ユーザーを監視するために実装するクラスです。後の手順で、FirebaseAuth インスタンスをエントリ ポイントとして使用して、このユーザー情報を取得します。
  • MainFragmentLoginViewModel に関連付けられています。LoginViewModel は、FirebaseUserLiveData を利用して authenticationState 変数を作成するために実装するクラスです。この authenticationState 変数を使用すると、MainFragment は値を監視して、それに応じて UI を更新できます。

このステップでは、Firebase コンソールを使用して、アプリでサポートする認証方法を設定します。この Codelab では、ユーザーが指定したメールアドレスまたは Google アカウントでログインできるようにすることに焦点を当てます。

  1. Firebase コンソールに移動します。(注: Firebase の追加ワークフローがまだ開いている場合は、左上の X をクリックしてコンソールに戻ります。
  2. プロジェクトにまだ参加していない場合は、プロジェクトを選択します。
  3. 左側のナビゲーションを開き、[Develop] > [Authentication ] を選択します。

  1. 上部のナビゲーション バーにある [ログイン方法] タブを選択します。

  1. [メールアドレス/パスワード] 行をクリックします。
  2. ポップアップで、[有効] スイッチを切り替えて [保存] をクリックします。
  3. 同様に、[Google] 行をクリックします。
  4. [有効] スイッチを切り替え、[プロジェクトのサポートメール] を入力して、[保存] をクリックします。

このタスクでは、ユーザーのログイン機能を実装します。

  1. MainFragment.kt を開きます。
  2. MainFragment のレイアウトで、auth_button を確認します。現在、ユーザー入力の処理は設定されていません。
  3. onViewCreated(), で、auth_buttononClickListener を追加して launchSignInFlow() を呼び出します。

MainFragment.kt

binding.authButton.setOnClickListener { launchSignInFlow() }
  1. MainFragment.ktlaunchSignInFlow() メソッドを探します。現在、TODO が含まれています。
  2. 次のように launchSignInFlow() 関数を完成させます。

MainFragment.kt

private fun launchSignInFlow() {
   // Give users the option to sign in / register with their email or Google account.
   // If users choose to register with their email,
   // they will need to create a password as well.
   val providers = arrayListOf(
       AuthUI.IdpConfig.EmailBuilder().build(), AuthUI.IdpConfig.GoogleBuilder().build()

       // This is where you can provide more ways for users to register and 
       // sign in.
   )

   // Create and launch sign-in intent.
   // We listen to the response of this activity with the
   // SIGN_IN_REQUEST_CODE 
   startActivityForResult(
       AuthUI.getInstance()
           .createSignInIntentBuilder()
           .setAvailableProviders(providers)
           .build(),
       MainFragment.SIGN_IN_REQUEST_CODE
   )
}

これにより、ユーザーはメールアドレスまたは Google アカウントで登録してログインできるようになります。ユーザーがメールアドレスで登録することを選択した場合、作成したメールアドレスとパスワードの組み合わせはアプリに対して一意になります。つまり、ユーザーはメールアドレスとパスワードの組み合わせでアプリにログインできますが、同じ認証情報で Firebase 対応の他のアプリにログインできるわけではありません。

  1. MainFragment.kt では、以下のように onActivityResult() メソッドを実装することで、ログイン プロセスの結果をリッスンできます。SIGN_IN_REQUEST_CODE でログイン プロセスを開始したので、SIGN_IN_REQUEST_CODEonActivityResult() に渡されたときにフィルタリングして、ログイン プロセスの結果をリッスンすることもできます。まず、ユーザーが正常にログインしたかどうかを確認するためのログステートメントを用意します。

MainFragment.kt

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
   super.onActivityResult(requestCode, resultCode, data)
   if (requestCode == SIGN_IN_REQUEST_CODE) {
       val response = IdpResponse.fromResultIntent(data)
       if (resultCode == Activity.RESULT_OK) {
           // User successfully signed in
           Log.i(TAG, "Successfully signed in user ${FirebaseAuth.getInstance().currentUser?.displayName}!")
       } else {
           // Sign in failed. If response is null the user canceled the
           // sign-in flow using the back button. Otherwise check
           // response.getError().getErrorCode() and handle the error.
           Log.i(TAG, "Sign in unsuccessful ${response?.error?.errorCode}")
       }
   }
}

これで、アプリでユーザーの登録とログインを処理できるようになりました。

  1. アプリを実行し、[ログイン] ボタンをタップするとログイン画面が表示されることを確認します。
  2. メールアドレスとパスワード、または Google アカウントでログインできるようになりました。
  3. ログイン後の UI には変更はありません(次のステップで UI の更新を実装します)。ただし、すべてが正しく機能していれば、登録フローを完了した後に Successfully signed in user ${your name}! というログメッセージが表示されます。
  4. Firebase コンソールに移動し、[開発] > [認証] > [ユーザー] に移動して、アプリに登録ユーザーが 1 人いることを確認することもできます。
  5. ユーザーがアプリのアカウントを作成すると、そのアカウントはアプリ専用となり、ログイン機能に Firebase を使用するアプリには関連付けられません。

このタスクでは、認証状態に基づいて UI を更新する処理を実装します。ユーザーがログインしている場合は、名前を表示してホーム画面をカスタマイズできます。また、ユーザーがログインしている場合は、[Login] ボタンを [Logout] ボタンに更新します。

  1. すでに作成されている FirebaseUserLiveData.kt クラスを開きます。まず、アプリ内の他のクラスがユーザーのログインまたはログアウトを認識できるようにする必要があります。ただし、LiveData の値が更新されていないため、クラスはまだ何も行いません。
  2. FirebaseAuth ライブラリを使用しているため、FirebaseUI ライブラリの一部として実装されている FirebaseUser.AuthStateListener コールバックを使用して、ログイン ユーザーの変更をリッスンできます。このコールバックは、ユーザーがアプリにログインまたはログアウトするたびにトリガーされます。
  3. FirebaseUserLiveData.ktauthStateListener 変数を定義します。この変数を使用して、LiveData の値を格納します。authStateListener 変数は、アプリケーションの状態に基づいて認証状態の変更を適切にリッスンできるようにするために作成されました。たとえば、ユーザーがアプリをバックグラウンドに移動した場合、メモリリークの可能性を防ぐために、アプリは認証状態の変化のリスニングを停止する必要があります。
  4. FirebaseUserLiveData の値が現在の Firebase ユーザーに対応するように authStateListener を更新します。

FirebaseUserLiveData.kt

private val authStateListener = FirebaseAuth.AuthStateListener { firebaseAuth ->
   value = firebaseAuth.currentUser
}
  1. LoginViewModel.kt を開きます。
  2. LoginViewModel.kt で、実装した FirebaseUserLiveData オブジェクトに基づいて authenticationState 変数を作成します。この authenticationState 変数を作成することで、他のクラスは LoginViewModel を通じてユーザーがログインしているかどうかをクエリできるようになりました。

LoginViewModel.kt

val authenticationState = FirebaseUserLiveData().map { user ->
   if (user != null) {
       AuthenticationState.AUTHENTICATED
   } else {
       AuthenticationState.UNAUTHENTICATED
   }
}
  1. MainFragment.kt. を開く
  2. MainFragment.ktobserveAuthenticationState() で、LoginViewModel で追加した authenticationState 変数を使用して、UI を適宜変更できます。ログインしているユーザーがいる場合は、authButton に [ログアウト] が表示されます。

MainFragment.kt

private fun observeAuthenticationState() {
   val factToDisplay = viewModel.getFactToDisplay(requireContext())

   viewModel.authenticationState.observe(viewLifecycleOwner, Observer { authenticationState ->
       when (authenticationState) {
           LoginViewModel.AuthenticationState.AUTHENTICATED -> {
               binding.authButton.text = getString(R.string.logout_button_text)
               binding.authButton.setOnClickListener {
                   // TODO implement logging out user in next step
               }

                // TODO 2. If the user is logged in, 
                 // you can customize the welcome message they see by
                 // utilizing the getFactWithPersonalization() function provided

           }
           else -> {
               // TODO 3. Lastly, if there is no logged-in user, 
                // auth_button should display Login and
                //  launch the sign in screen when clicked.
           }
       }
   })
}
  1. ユーザーがログインしている場合は、MainFragment で提供されている getFactWithPersonalization() 関数を使用して、ユーザーに表示されるウェルカム メッセージをカスタマイズすることもできます。

MainFragment.kt

binding.welcomeText.text = getFactWithPersonalization(factToDisplay)
  1. 最後に、ログインしているユーザーがいない場合(authenticationStateLoginViewModel.AuthenticationState.AUTHENTICATED 以外の場合)、auth_button は [ログイン] を表示し、クリックするとログイン画面を起動します。表示されるメッセージのパーソナライズも行われません。

MainFragment.kt

binding.authButton.text = getString(R.string.login_button_text)
binding.authButton.setOnClickListener { launchSignInFlow() }
binding.welcomeText.text = factToDisplay

すべてのステップが完了すると、最終的な observeAuthenticationState() メソッドは次のようになります。

MainFragment.kt

private fun observeAuthenticationState() {
   val factToDisplay = viewModel.getFactToDisplay(requireContext())

   viewModel.authenticationState.observe(viewLifecycleOwner, Observer { authenticationState ->
        // TODO 1. Use the authenticationState variable you just added 
         // in LoginViewModel and change the UI accordingly.
       when (authenticationState) {
            // TODO 2.  If the user is logged in, 
             // you can customize the welcome message they see by
             // utilizing the getFactWithPersonalization() function provided
           LoginViewModel.AuthenticationState.AUTHENTICATED -> {
               binding.welcomeText.text = getFactWithPersonalization(factToDisplay)
               binding.authButton.text = getString(R.string.logout_button_text)
               binding.authButton.setOnClickListener {
                   // TODO implement logging out user in next step
               }
           }
           else -> {
                // TODO 3. Lastly, if there is no logged-in user, 
                 // auth_button should display Login and
                 // launch the sign in screen when clicked.
               binding.welcomeText.text = factToDisplay

               binding.authButton.text = getString(R.string.login_button_text)
               binding.authButton.setOnClickListener {
                   launchSignInFlow()
               }
           }
       }
   })
}
  1. アプリを実行します。ユーザーがログインしているかどうかに応じて UI が更新されます。すべてが正常に動作し、ログインしている場合は、ホーム画面に Android の豆知識に加えて、名前が表示されるようになります。[ログイン] ボタンも [ログアウト] に変わります。

このタスクでは、ログアウト機能を実装します。

アプリでユーザーがログインできる場合は、ログアウトする方法も提供する必要があります。次の例は、1 行のコードでユーザーをログアウトする方法を示しています。

AuthUI.getInstance().signOut(requireContext())
  1. MainFragment.kt を開きます。
  2. MainFragment.ktobserveAuthenticationState() で、ログインしているユーザーがいる場合に auth_button が正しく機能するように、ログアウト ロジックを追加します。メソッドの最終的な結果は次のようになります。

MainFragment.kt

private fun observeAuthenticationState() {
   val factToDisplay = viewModel.getFactToDisplay(requireContext())

   viewModel.authenticationState.observe(viewLifecycleOwner, Observer { authenticationState ->
       when (authenticationState) {
           LoginViewModel.AuthenticationState.AUTHENTICATED -> {
               binding.welcomeText.text = getFactWithPersonalization(factToDisplay)

               binding.authButton.text = getString(R.string.logout_button_text)
               binding.authButton.setOnClickListener {
                   AuthUI.getInstance().signOut(requireContext())
               }
           }
           else -> {
               binding.welcomeText.text = factToDisplay

               binding.authButton.text = getString(R.string.login_button_text)
               binding.authButton.setOnClickListener {
                   launchSignInFlow()
               }
           }
       }
   })
}
  1. アプリを実行します。
  2. [ログアウト] ボタンをタップし、ユーザーがログアウトされ、ボタンの状態が [ログイン] に変わったことを確認します。

完成したアプリの最終バージョンは、https://github.com/googlecodelabs/android-kotlin-login で確認できます。

この Codelab では、以下のことを学びました。

  • gradle ファイルに必要な依存関係を追加し、Firebase コンソールでプロジェクトを設定して、プロジェクトに Firebase を追加する方法。
  • FirebaseUI ライブラリを使用してアプリのログインを実装し、ユーザーのログイン方法を指定する方法。ユーザーがアプリで作成したアカウントは、そのアプリ専用であり、ログイン機能に Firebase を使用するすべてのアプリで共有されるわけではありません。
  • LiveData を使用してアプリの現在の認証ステータスをモニタリングする方法。
  • ユーザーをログアウトする方法。

この Codelab では、Android アプリのログインをサポートする方法の基本について説明しました。

この Codelab では、ユーザーがメールアドレスを使用して登録とログインを行えるようにしました。ただし、FirebaseUI ライブラリを使用すると、電話番号でのログインなどの他の認証方法もサポートできます。FirebaseUI ライブラリの機能と、提供されるその他の機能の活用方法について詳しくは、以下のリソースをご覧ください。

ログインに関するベスト プラクティスの詳細については、次のリソースをご覧ください。

Codelabs:

Android デベロッパー ドキュメント:

動画:

このコースの他の Codelab へのリンクについては、Kotlin を使った高度な Android 開発の Codelab のランディング ページをご覧ください。