ログインが必要になったらFirebase Authenticationでトークンを取るフラグメント出す for Android
あいさつ
はじめまして.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する.
完成品
流れ
- まずMainFragmentでユーザがログインが必要な状態になる.
MainFragmentはMainApplicationにTokenが発行されているか確認する. - Tokenが発行されていなかったらMainApplicationはMainActivityにTokenを発行するようにお願いする.
- MainActivityはOAuthFragmentを生成しtokenを受け取るためにリスナを生成.
- OAuthFragmentはtokenを発行し,MainActivityに送る.
- MainActivityはtokenを受け取ったらMainApplicationのLiveDataにPOSTする.
- MainFragmentはMainApplicationのLiveDataを監視して発行されたらアクションする.
目次
- FirebaseとAndroidを接続
- Firebase AuthenticationでTokenをとってLog表示するFragmentを作る.
- OAuth認証するFragmentを表示する関数とFragmentを作る
- 別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等)が含まれるとうまく接続できない時があります.
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上で
D/OAuthFragment: Token: eyJhbGciOiJSUzI1NiI.....(省略)
トークンが確認できる.
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)
<?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>
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}")
}
}
}
}
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'
}
<?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>
<?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を作る準備)
<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" />
こうなれば良い.
<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()で自分のインスタンスを渡せるようにします.
companion object {
private const val RC_SIGN_IN = 123
private const val TAG = "OAuthFragment"
// 動的にFragmentを生成するためにインスタンスを返す関数をcompanion objectに書く
fun newInstance() = OAuthFragment()
}
最後にMainActivityがFragmentContainerViewにOAuthFragmentを生成します.
val oAuthFragment = OAuthFragment.newInstance()
supportFragmentManager.beginTransaction()
.add(R.id.OAuthFragmentContainerView, oAuthFragment)
.commit()
(変数にするときは先頭小文字にするんですけどoAuthって文字列が気持ち悪い...)
これで動的にfragmentが生成できるようになったので次はリスナ登録してコールバックを受けるようにします.
次にOAuthFragmentがリスナを受け取ってコールバックを返すようにします.
class OAuthFragment : Fragment() {
private var listener: OnTokenReceivedListener? = null
// これを継承したクラスはonTokenReceivedをオーバーライドしてコールバックを受け取る
interface OnTokenReceivedListener {
fun onTokenReceived(token: String?)
}
// 他のクラスからもらったリスナを登録
fun setOnTokenReceivedListener(listener: OnTokenReceivedListener) {
this.listener = listener
}
}
あとはトークンを受け取ったらリスナにトークンを送るだけです.
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でコールバックを受け取ります.
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
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
}
}
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を宣言しておきます.
class MainApplication: Application() {
private val _token = MutableLiveData<String?>(null)
val token: LiveData<String?> = _token
fun setToken(token: String?) {
_token.postValue(token)
}
}
MainFragmentではViewBindingが必要です.
gradleに以下の設定をしておきましょう.
android {
// 省略
buildFeatures {
viewBinding true
}
}
ViewBindingの設定をしてMainApplicationとMainActivityにアクセスできるようにしておきましょう
MainAplicationが呼び出すとnullになる場合,Manifestのandroid:nameを宣言しましょう
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
}
}
<application
android:name=".MainApplication"
>
ではMainApplicationからMainActivityの関数を呼びます
実は今までできていたgetCurrentActivityは使えなくなりました.
val currentActivity = (getApplicationContext() as MainApplication).getCurrentActivity() as MainActivity
currentActivity.getToken()
変わりにBroadcastReceiverというのを使います.
MainApplicationがMainActivityの関数を直接呼ぶのではなく,
Intentを渡して「関数実行しといてね」とお願いする感じです.
Listenerと同じようにonResumeで登録,onPauseで解除しましょう.(今回は何度もコールバックされ続ける訳ではないので気にするほどではありませんが)
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に送りましょう
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"としておきましょう
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
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)
}
}
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)
}
}
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
}
}
}
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
}
}
<?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>
<?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>
<?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>
<?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>
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
}
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