🔥

ログインが必要になったらFirebase Authenticationでトークンを取るフラグメント出す for Android

2023/02/04に公開

あいさつ

はじめまして.Androidアプリを作っているlog.suzakiです.
初めて記事を書きます.ミスや間違いがあるかもしれませんが許して下さい.
この記事はなるべくプログラムのソースコードをimportから全部貼るようにします.
誰かの助けになれば幸いです.

環境

Android Studio: Electric Eel | 2022.1.1 Patch 1
kotlin: 1.7.21
minSdk: 31
targetSdk: 33

対象

  • Androidアプリを作ってる人.
  • Kotlinで書いてる人.
  • Fragmentを知ってる人.
  • applicationクラスを知ってる人.
  • LiveDataを知っている人.

最終目標

トークンが必要になった時applicationクラスの関数を呼ぶだけで認証できるようにする.

中間目標

  • Firebase AuthenticationでOAuthのトークンを取得するOAuthFragentを作る.
  • OAuthFragentで手に入れたTokenをMainActivityにCallBackする.

完成品

流れ

  1. まずMainFragmentでユーザがログインが必要な状態になる.
    MainFragmentはMainApplicationにTokenが発行されているか確認する.
  2. Tokenが発行されていなかったらMainApplicationはMainActivityにTokenを発行するようにお願いする.
  3. MainActivityはOAuthFragmentを生成しtokenを受け取るためにリスナを生成.
  4. OAuthFragmentはtokenを発行し,MainActivityに送る.
  5. MainActivityはtokenを受け取ったらMainApplicationのLiveDataにPOSTする.
  6. MainFragmentはMainApplicationのLiveDataを監視して発行されたらアクションする.

目次

  1. FirebaseとAndroidを接続
  2. Firebase AuthenticationでTokenをとってLog表示するFragmentを作る.
  3. OAuth認証するFragmentを表示する関数とFragmentを作る
  4. 別FragmentからTokenを受け取る時,Tokenが発行されていなかったら発行するようにする.

FirebaseとAndroidを接続

この記事に沿ってやって下さい.↓
AndroidでFirebase Authenticationを利用してGoogle認証ログインする

ここの4.4で必要になるフィンガープリントはこのようにしても確認できます.

フィンガープリントの確認法

Android Studioの右にあるGradleを選択

プロジェクトの名前 → app → Tasks → android → signingReport

見つからないって人はPreferencesを開いて

Only include test tasks...のチェックを外す

appを右クリック→Reloadで出ると思います.

  • Firebaseと接続する時,Gradleのdependenciesにimplementation以外(androidTestImplementation等)が含まれるとうまく接続できない時があります.
gradle(app)
dependencies {
    implementation 'androidx.core:core-ktx:1.9.0'
    // ↓こういうのとか
    testImplementation 'junit:junit:4.13.2'
    // ↓こういうの
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}
  • その時は全てimplementationに書き換えて下さい.

Firebase AuthenticationでTokenをとってLog表示するFragmentを作る.

ここでの目標

こういう画面でログインボタン押すと

Log上で

Log
D/OAuthFragment: Token: eyJhbGciOiJSUzI1NiI.....(省略)

トークンが確認できる.

Log
D/OAuthFragment: Sign in success: "Googleアカウントの名前"

ついでにアカウントの名前も見れる.

手順

プロパイダを決めます.
ここで入れるプロパイダの分だけライブラリのインポートが必要です.
(FacebookBuilder()を入れたらandroid:facebook-loginをインポート)
詳しく知りたい場合は公式ドキュメントを読んでください.
FirebaseUIを使用してAndroidアプリにサインインを簡単に追加する

val providers = arrayListOf(
        AuthUI.IdpConfig.EmailBuilder().build(),
        AuthUI.IdpConfig.PhoneBuilder().build(),
        AuthUI.IdpConfig.GoogleBuilder().build(),
        AuthUI.IdpConfig.FacebookBuilder().build(),
        AuthUI.IdpConfig.TwitterBuilder().build())

今回はGoogleだけにします.

val providers = arrayListOf(
    AuthUI.IdpConfig.GoogleBuilder().build(),
)

次にFirebase AuthenticationのAuthUIを使ってサインイン画面を表示します.
どのアカウントを使いますか?とか表示するやつですね.

startActivityForResult(
    AuthUI.getInstance()
	.createSignInIntentBuilder()
	.setAvailableProviders(providers)
	.build(),
    RC_SIGN_IN
)

ログインしてもらったら,ログイン情報を受け取ります.
onActivityResultは複数のコールバックの受け皿にされるのでrequestCodeというのを自分で設定します.
リクエストするときに任意の数字を一緒に送って帰ってきたデータの任意の数字が一致しているか確認するわけですね.

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        if (requestCode == RC_SIGN_IN) {
            val response = IdpResponse.fromResultIntent(data)
            if (resultCode == Activity.RESULT_OK) {
                val user = firebaseAuth.currentUser
                Log.d(TAG, "Sign in success: ${user?.displayName}")

                val token = user?.getIdToken(true)
                    ?.addOnCompleteListener { task ->
                        if (task.isSuccessful) {
                            val idToken = task.result?.token
                            // トークンはここで確認します.
                            Log.d(TAG, "Token: $idToken")
                        } else {
                            Log.e(TAG, "Failed to retrieve token")
                        }
                    }

            } else {
                // リクエスト失敗
                Log.d(TAG, "Sign in failed: ${response?.error?.errorCode}")
            }
        }
    }

ソースコード

Manifest, OAuthFragment, activity_main, fragment_o_auth, gradle(app)
AndroidManifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    >

    <application>
        <!--省略-->
    </application>

    <!--必要な権限 この二つを追加-->
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />

</manifest>

OAuthFragment
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.firebase.ui.auth.AuthUI
import com.firebase.ui.auth.IdpResponse
import com.google.firebase.auth.FirebaseAuth

class OAuthFragment : Fragment() {
    companion object {
        private const val RC_SIGN_IN = 123 // 好きな数字.識別用.
        private const val TAG = "OAuthFragment"
    }
    private lateinit var firebaseAuth: FirebaseAuth

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_o_auth, container, false)

        firebaseAuth = FirebaseAuth.getInstance()
	// ボタン一個だからBindingしてない.
        val signInButton = view.findViewById<com.google.android.gms.common.SignInButton>(R.id.sign_in_button)
        signInButton.setOnClickListener {
            startSignInFlow()
        }

        return view
    }

    private fun startSignInFlow() {
	// ここにfacebookログインとかtwitterログインを追加できる.
        val providers = arrayListOf(
            AuthUI.IdpConfig.GoogleBuilder().build(),
        )

        startActivityForResult(
            AuthUI.getInstance()
                .createSignInIntentBuilder()
                .setAvailableProviders(providers)
                .build(),
            RC_SIGN_IN
        )
    }
    
    // CallBack.
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
	// まずはOAuth認証の結果がもらえたのかどうかを確認(別のコールバックの可能性がある)
        if (requestCode == RC_SIGN_IN) {
            val response = IdpResponse.fromResultIntent(data)

            if (resultCode == Activity.RESULT_OK) {
                val user = firebaseAuth.currentUser
                // サインインに成功したらサインインしたユーザのUIを変える.
                Log.d(TAG, "Sign in success: ${user?.displayName}")

                val token = user?.getIdToken(true)
                    ?.addOnCompleteListener { task ->
                        if (task.isSuccessful) {
                            val idToken = task.result?.token
                            // トークンはここで確認します.
                            Log.d(TAG, "Token: $idToken")
                        } else {
                            Log.e(TAG, "Failed to retrieve token")
                        }
                    }

            } else {
                // リクエスト失敗
                Log.d(TAG, "Sign in failed: ${response?.error?.errorCode}")
            }
        }
    }
}
gradle(app)
dependencies {
    implementation 'androidx.core:core-ktx:1.9.0'
    implementation 'androidx.appcompat:appcompat:1.6.0'
    implementation 'com.google.android.material:material:1.8.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    // FirebaseAuthのインスタンスに必要なやつ.
    implementation 'com.google.firebase:firebase-auth:21.1.0'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
    // Firebase入れた時に入れるOAuth認証に必要なやつ↓
    implementation 'com.google.android.gms:play-services-auth:20.4.1'
    // 今回入れるやつ↓
    implementation 'com.firebaseui:firebase-ui-auth:8.0.1'
    // 別プロパイダのログイン方法を使うなら↓これはfacebookのやつ
    // implementation 'com.facebook.android:facebook-login:14.0.0'
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="#FFD5D5"
    tools:context=".MainActivity">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/OAuthFragmentContainerView"
        android:name="com.b22712.AuthSample.OAuthFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="#B2C5FF"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
fragment_o_auth.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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"
    tools:context=".OAuthFragment">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#AEAEAE">

        <TextView
            android:id="@+id/textView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="32dp"
            android:gravity="center"
            android:text="この機能を使いたいなら\nログインしてね!"
            android:textColor="#000000"
            android:textSize="24sp"
            app:layout_constraintBottom_toTopOf="@+id/sign_in_button"
            app:layout_constraintEnd_toEndOf="@+id/sign_in_button"
            app:layout_constraintStart_toStartOf="@+id/sign_in_button" />

        <com.google.android.gms.common.SignInButton
            android:id="@+id/sign_in_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

FragmentでTokenを発行してMainActivityに送る

ここでの目標

MainActivity内でgetToken()という関数を呼ぶとMainActivity内でトークンをログに出せる.

手順

まず,OAuthFragmentを動的に生成します.
そのためにactivity_main.xmlのFragmentContainerViewからandroid:nameを削除します.
そしてandroid:idをOAuthFragmentContainerViewにしておきます.
(この後mainFragmentを作る準備)

acivity_main.xml
<androidx.fragment.app.FragmentContainerView
        android:id="@+id/OAuthFragmentContainerView"
        android:name="パッケージ名.OAuthFragment"←これを削除
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

こうなれば良い.

acivity_main.xml
<androidx.fragment.app.FragmentContainerView
        android:id="@+id/OAuthFragmentContainerView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

で,動的に生成するためにOAuthFragmentはnewInstance()で自分のインスタンスを渡せるようにします.

OAuthFragment
companion object {
        private const val RC_SIGN_IN = 123
        private const val TAG = "OAuthFragment"
        // 動的にFragmentを生成するためにインスタンスを返す関数をcompanion objectに書く
        fun newInstance() = OAuthFragment()
    }

最後にMainActivityがFragmentContainerViewにOAuthFragmentを生成します.

MainActivity
val oAuthFragment = OAuthFragment.newInstance()
supportFragmentManager.beginTransaction()
    .add(R.id.OAuthFragmentContainerView, oAuthFragment)
    .commit()

(変数にするときは先頭小文字にするんですけどoAuthって文字列が気持ち悪い...)

これで動的にfragmentが生成できるようになったので次はリスナ登録してコールバックを受けるようにします.

次にOAuthFragmentがリスナを受け取ってコールバックを返すようにします.

OAuthFragment
class OAuthFragment : Fragment() {
    private var listener: OnTokenReceivedListener? = null
	
    // これを継承したクラスはonTokenReceivedをオーバーライドしてコールバックを受け取る
    interface OnTokenReceivedListener {
        fun onTokenReceived(token: String?)
    }
    // 他のクラスからもらったリスナを登録
    fun setOnTokenReceivedListener(listener: OnTokenReceivedListener) {
        this.listener = listener
    }
}

あとはトークンを受け取ったらリスナにトークンを送るだけです.

OAuthFragment
val token = user?.getIdToken(true)
	    ?.addOnCompleteListener { task ->
		if (task.isSuccessful) {
		    val idToken = task.result?.token
		    // トークンはここで確認します.
		    Log.d(TAG, "Token: $idToken")
		    // リスナにトークンを渡す
		    listener?.onTokenReceived(idToken)
		} else {
		    Log.e(TAG, "Failed to retrieve token")
		}
	    }

これでOAuthFragmentクラスは完成したのでMainActivityでコールバックを受け取ります.

MainAvtivity
fun getToken() {
        val oAuthFragment = OAuthFragment.newInstance()
        supportFragmentManager.beginTransaction()
            .add(R.id.OAuthFragmentContainerView, oAuthFragment)
            .commit()
        val tokenReceivedListener = object : OAuthFragment.OnTokenReceivedListener {
            override fun onTokenReceived(token: String?) {
                // コールバックでtokenをもらう
                Log.d("MainActivity", "Token received: $token")
            }
        }
        oAuthFragment.setOnTokenReceivedListener(tokenReceivedListener as OAuthFragment.OnTokenReceivedListener)
    }

ただ,これだとトークンを発行した後もOAuthFragmentが残り続けるのでトークンをもらったらFragmentを表示しないようにします.

supportFragmentManager.beginTransaction()
	    .remove(oAuthFragment)
	    .commit()

これで,MainActivity生成→OAuthFragment生成→トークン発行→トークンをログ表示→OAuthFragment削除,という流れになります.

ソースコード

OAuthFragment, MainActivity
OAuthFragment.kt
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.firebase.ui.auth.AuthUI
import com.firebase.ui.auth.IdpResponse
import com.google.firebase.auth.FirebaseAuth

class OAuthFragment : Fragment() {
    companion object {
        private const val RC_SIGN_IN = 123
        private const val TAG = "OAuthFragment"
        // 動的にFragmentを生成するためにインスタンスを返す関数をcompanion objectに書く
        fun newInstance() = OAuthFragment()
    }

    private lateinit var firebaseAuth: FirebaseAuth
    private var listener: OnTokenReceivedListener? = null

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_o_auth, container, false)

        firebaseAuth = FirebaseAuth.getInstance()

        val signInButton = view.findViewById<com.google.android.gms.common.SignInButton>(R.id.sign_in_button)
        signInButton.setOnClickListener {
            startSignInFlow()
        }

        return view
    }

    private fun startSignInFlow() {
        val providers = arrayListOf(
            AuthUI.IdpConfig.GoogleBuilder().build(),
        )

        startActivityForResult(
            AuthUI.getInstance()
                .createSignInIntentBuilder()
                .setAvailableProviders(providers)
                .build(),
            RC_SIGN_IN
        )
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        if (requestCode == RC_SIGN_IN) {
            val response = IdpResponse.fromResultIntent(data)
            if (resultCode == Activity.RESULT_OK) {
                val user = firebaseAuth.currentUser
                Log.d(TAG, "Sign in success: ${user?.displayName}")

                val token = user?.getIdToken(true)
                    ?.addOnCompleteListener { task ->
                        if (task.isSuccessful) {
                            val idToken = task.result?.token
                            // トークンはここで確認します.
                            Log.d(TAG, "Token: $idToken")
                            // リスナにトークンを渡す
                            listener?.onTokenReceived(idToken)
                        } else {
                            Log.e(TAG, "Failed to retrieve token")
                        }
                    }

            } else {
                // リクエスト失敗
                Log.d(TAG, "Sign in failed: ${response?.error?.errorCode}")
            }
        }
    }

    // これを継承したクラスはonTokenReceivedをオーバーライドしてコールバックを受け取る
    interface OnTokenReceivedListener {
        fun onTokenReceived(token: String?)
    }
    // 他クラスが作ったリスナをもらってここで登録
    fun setOnTokenReceivedListener(listener: OnTokenReceivedListener) {
        this.listener = listener
    }
}

MainActivity
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity


class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        getToken()
    }

    fun getToken() {
        val oAuthFragment = OAuthFragment.newInstance()
        supportFragmentManager.beginTransaction()
            .add(R.id.OAuthFragmentContainerView, oAuthFragment)
            .commit()
        val tokenReceivedListener = object : OAuthFragment.OnTokenReceivedListener {
            override fun onTokenReceived(token: String?) {
                // コールバックでtokenをもらう
                Log.d("MainActivity", "Token received: $token")
		// Fragmentを消す
                supportFragmentManager.beginTransaction()
                    .remove(oAuthFragment)
                    .commit()
            }
        }
        oAuthFragment.setOnTokenReceivedListener(tokenReceivedListener as OAuthFragment.OnTokenReceivedListener)
    }

}

別FragmentからTokenを受け取る時,Tokenが発行されていなかったら発行するようにする.

ここでの目標

最後まで行きます.

手順

MainApplicationとMainFragmentを作ります.
MainApplicationにはTokenを保管するLiveDataを宣言しておきます.

MainApplication
class MainApplication: Application() {

    private val _token = MutableLiveData<String?>(null)
    val token: LiveData<String?> = _token
    fun setToken(token: String?) {
        _token.postValue(token)
    }

}

MainFragmentではViewBindingが必要です.
gradleに以下の設定をしておきましょう.

gradle(app)
android {
    // 省略
    buildFeatures {
        viewBinding true
    }
}

ViewBindingの設定をしてMainApplicationとMainActivityにアクセスできるようにしておきましょう
MainAplicationが呼び出すとnullになる場合,Manifestのandroid:nameを宣言しましょう

MainFragment
class MainFragment : Fragment() {

    private var _binding: FragmentMainBinding? = null
    private val binding get() = _binding!!
    lateinit var app: MainApplication
    lateinit var activity: MainActivity

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        // Inflate the layout for this fragment
        app = requireActivity().application as MainApplication
        activity = getActivity() as MainActivity
        _binding = FragmentMainBinding.inflate(inflater, container, false)
        return binding.root
    }
}
AndroidManifest
<application
        android:name=".MainApplication"
	     >

ではMainApplicationからMainActivityの関数を呼びます
実は今までできていたgetCurrentActivityは使えなくなりました.
val currentActivity = (getApplicationContext() as MainApplication).getCurrentActivity() as MainActivity
currentActivity.getToken()

変わりにBroadcastReceiverというのを使います.
MainApplicationがMainActivityの関数を直接呼ぶのではなく,
Intentを渡して「関数実行しといてね」とお願いする感じです.
Listenerと同じようにonResumeで登録,onPauseで解除しましょう.(今回は何度もコールバックされ続ける訳ではないので気にするほどではありませんが)

MainActivity
private val receiver = CallGetTokenReceiver()

override fun onResume() {
    super.onResume()
    LocalBroadcastManager.getInstance(this).registerReceiver(receiver, IntentFilter("call_getToken"))
}

override fun onPause() {
    super.onPause()
    LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver)
}

private inner class CallGetTokenReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        getToken()
    }
}

ではMainApplicationでIntentを作ってMainActivityに送りましょう

MainAplication
fun getToken(){
        // MainActivityに関数を実行してもらう
        val intent = Intent("call_getToken")
        LocalBroadcastManager.getInstance(applicationContext).sendBroadcast(intent)
    }

ここまでこれば後一息です.
MainFragmentと接続します.
MainFragmentにボタンとテキストを設置しておきます.
ボタンを押すとTokenをリクエストして,TextViewでTokenがもらえたかどうか確認します.
TextViewのidは"token_textView"
Buttonは"button_favorite"としておきましょう

MainFragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        binding.buttonFavorite.setOnClickListener {
            app.getToken()
        }

        app.token.observe(viewLifecycleOwner) {
            binding.tokenTextView.text = it
        }

    }

完成です

完成品ソースコード

4Kotlin, 4xml, 2gradle
MainActivity
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.localbroadcastmanager.content.LocalBroadcastManager


class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

    }

    private val receiver = CallGetTokenReceiver()

    override fun onResume() {
        super.onResume()
        LocalBroadcastManager.getInstance(this).registerReceiver(receiver, IntentFilter("call_getToken"))
    }

    override fun onPause() {
        super.onPause()
        LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver)
    }

    private inner class CallGetTokenReceiver : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            getToken()
        }
    }

    fun getToken() {
        val application = applicationContext as MainApplication
        val oAuthFragment = OAuthFragment.newInstance()
        // Fragmentを作る
        supportFragmentManager.beginTransaction()
            .add(R.id.OAuthFragmentContainerView, oAuthFragment)
            .commit()
        val tokenReceivedListener = object : OAuthFragment.OnTokenReceivedListener {
            override fun onTokenReceived(token: String?) {
                // コールバックでtokenをもらう
                Log.d("MainActivity", "Token received: $token")
                // applicationにtokenを渡す.
                application.setToken(token)
                // Fragmentを消す
                supportFragmentManager.beginTransaction()
                    .remove(oAuthFragment)
                    .commit()
            }
        }
        oAuthFragment.setOnTokenReceivedListener(tokenReceivedListener as OAuthFragment.OnTokenReceivedListener)
    }

}
MainApplication
import android.app.Application
import android.content.Intent
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.localbroadcastmanager.content.LocalBroadcastManager

class MainApplication: Application() {

    private val _token = MutableLiveData<String?>(null)
    val token: LiveData<String?> = _token
    fun setToken(token: String?) {
        _token.postValue(token)
    }

    fun getToken(){
        // MainActivityに関数を実行してもらう
        val intent = Intent("call_getToken")
        LocalBroadcastManager.getInstance(applicationContext).sendBroadcast(intent)
    }

}
MainFragment
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.b22712.AuthSample.databinding.FragmentMainBinding

class MainFragment : Fragment() {

    private var _binding: FragmentMainBinding? = null
    private val binding get() = _binding!!
    lateinit var app: MainApplication
    lateinit var activity: MainActivity

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        // Inflate the layout for this fragment
        app = requireActivity().application as MainApplication
        activity = getActivity() as MainActivity
        _binding = FragmentMainBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        binding.buttonFavorite.setOnClickListener {
            app.getToken()
        }

        app.token.observe(viewLifecycleOwner) {
            binding.tokenTextView.text = it
        }

    }
}
OAuthFragment
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.firebase.ui.auth.AuthUI
import com.firebase.ui.auth.IdpResponse
import com.google.firebase.auth.FirebaseAuth

class OAuthFragment : Fragment() {
    companion object {
        private const val RC_SIGN_IN = 123
        private const val TAG = "OAuthFragment"
        // 動的にFragmentを生成するためにインスタンスを返す関数をcompanion objectに書く
        fun newInstance() = OAuthFragment()
    }

    private lateinit var firebaseAuth: FirebaseAuth
    private var listener: OnTokenReceivedListener? = null

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_o_auth, container, false)

        firebaseAuth = FirebaseAuth.getInstance()

        val signInButton = view.findViewById<com.google.android.gms.common.SignInButton>(R.id.sign_in_button)
        signInButton.setOnClickListener {
            startSignInFlow()
        }

        return view
    }

    private fun startSignInFlow() {
        val providers = arrayListOf(
            AuthUI.IdpConfig.GoogleBuilder().build(),
        )

        startActivityForResult(
            AuthUI.getInstance()
                .createSignInIntentBuilder()
                .setAvailableProviders(providers)
                .build(),
            RC_SIGN_IN
        )
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        if (requestCode == RC_SIGN_IN) {
            val response = IdpResponse.fromResultIntent(data)
            if (resultCode == Activity.RESULT_OK) {
                val user = firebaseAuth.currentUser
                Log.d(TAG, "Sign in success: ${user?.displayName}")

                val token = user?.getIdToken(true)
                    ?.addOnCompleteListener { task ->
                        if (task.isSuccessful) {
                            val idToken = task.result?.token
                            // トークンはここで確認します.
                            Log.d(TAG, "Token: $idToken")
                            // リスナにトークンを渡す
                            listener?.onTokenReceived(idToken)
                        } else {
                            Log.e(TAG, "Failed to retrieve token")
                        }
                    }

            } else {
                // リクエスト失敗
                Log.d(TAG, "Sign in failed: ${response?.error?.errorCode}")
            }
        }
    }

    // これを継承したクラスはonTokenReceivedをオーバーライドしてコールバックを受け取る
    interface OnTokenReceivedListener {
        fun onTokenReceived(token: String?)
    }
    fun setOnTokenReceivedListener(listener: OnTokenReceivedListener) {
        this.listener = listener
    }

}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="パッケージ"
    >

    <application
        android:name=".MainApplication"
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/Theme.MyApplication"
        >
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

    <!--必要な権限-->
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />

</manifest>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="#FFD5D5"
    tools:context=".MainActivity">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/mainFragmentContainerView"
        android:name="com.b22712.AuthSample.MainFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="#B2C5FF"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/OAuthFragmentContainerView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginStart="32dp"
        android:layout_marginTop="32dp"
        android:layout_marginEnd="32dp"
        android:layout_marginBottom="32dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
fragment_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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"
    tools:context=".MainFragment">
    
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/token_textView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="TextView"
            android:textColor="#000000"
            app:layout_constraintBottom_toTopOf="@+id/button_favorite"
            app:layout_constraintEnd_toEndOf="@+id/button_favorite"
            app:layout_constraintStart_toStartOf="@+id/button_favorite"
            app:layout_constraintTop_toTopOf="parent" />

        <Button
            android:id="@+id/button_favorite"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Favorite"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>

</FrameLayout>
fragment_o_auth.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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:id="@+id/OAuthFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".OAuthFragment">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#AEAEAE">

        <TextView
            android:id="@+id/textView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="32dp"
            android:gravity="center"
            android:text="この機能を使いたいなら\nログインしてね!"
            android:textColor="#000000"
            android:textSize="24sp"
            app:layout_constraintBottom_toTopOf="@+id/sign_in_button"
            app:layout_constraintEnd_toEndOf="@+id/sign_in_button"
            app:layout_constraintStart_toStartOf="@+id/sign_in_button" />

        <com.google.android.gms.common.SignInButton
            android:id="@+id/sign_in_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>
gradle(project)
buildscript {
    dependencies {
        classpath 'com.google.gms:google-services:4.3.15'
    }
}// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
    id 'com.android.application' version '7.4.1' apply false
    id 'com.android.library' version '7.4.1' apply false
    id 'org.jetbrains.kotlin.android' version '1.7.21' apply false
}
gradle(app)
plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id 'com.google.gms.google-services'
}

android {
    namespace 'パッケージ'
    compileSdk 33

    defaultConfig {
        applicationId "パッケージ"
        minSdk 31
        targetSdk 33
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
    buildFeatures {
        viewBinding true
    }
}

dependencies {

    implementation 'androidx.core:core-ktx:1.9.0'
    implementation 'androidx.appcompat:appcompat:1.6.0'
    implementation 'com.google.android.material:material:1.8.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    implementation 'com.google.firebase:firebase-auth:21.1.0'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
    implementation 'com.google.android.gms:play-services-auth:20.4.1'
    implementation 'com.firebaseui:firebase-ui-auth:8.0.1'
}

Discussion