はじめに
現在開発中のアプリでパスワードのリセット機能を作成する機会がありました。アプリ内で再認証を行いパスワードのリセットを行うこともできるのですが、今回はパスワードリセットメールの送信を行いパスワードのリセットを行うことに決めました。
その際にディープリンクの設定など初見では複雑にみえる処理などがあり、躓くポイントが何点かありました
今回は、NavigationコンポーネントとFirebaseを使用して、パスワードのリセットが完了した後にアプリの特定の画面を開く方法をわかりやすくまとめたいと思います。
パスワードリセットメールを送信する
パスワードリセットメールを送信するには、sendPasswordResetEmail()
を呼び出します。
公式ドキュメントでは、addOnCompleteListener()
が使われていますが、await()
を使うことでコールバック地獄を回避することができます。
※必ずsuspend
関数内で呼び出してください。
val auth = Firebase.auth
...
suspend fun sendPasswordResetEmail(email: String): Unit {
try {
auth.sendPasswordResetEmail(email).await()
} catch (e: Throwable) {
Log.d("sendPasswordResetEmail", "${e.message}")
}
}
ActionCodeSettingsを使ってパスワードリセットメールを送信する
ActionCodeSettings
を使うことで、ディープリンクを設定することができるようになります。
これにより、アプリ内のブラウザでパスワードリセットのリンクを開き、パスワードをリセットした後にContinueボタンをクリックすると、アプリがインストールされている場合はアプリにリダイレクトさせることができるようになります。
val actionCodeSettings = ActionCodeSettings.newBuilder()
.setUrl(url)
.setAndroidPackageName(
packageName,
true,
"1"
)
.setHandleCodeInApp(false)
.setDynamicLinkDomain("ダイナミックリンクのドメイン")
.build()
setUrl()
に指定するのは、アプリへのリダイレクトに使用するディープリンクです。ここで指定する値は、continueUrl
に指定されるURLのlink
クエリパラーメーターの値に適用されます。
setAndroidPackageName()
の第一引数には、アプリのパッケージ名を指定します。この値は、continueUrl
に指定されるURLのapn
クエリパラーメーターの値に適用されます。
setAndroidPackageName()
の第二引数はinstallIfNotAvailable
です。true
を設定した場合はアプリがデバイスにインストールされていない場合はGoogle Play Storeに遷移させてアプリをインストールするようにします。
setAndroidPackageName()
の第三引数にはアプリの最小バージョンを指定します。この値はcontinueUrl
に指定されるURLのamv
クエリパラーメーターの値に適用されます。この値も同じようにこの値に満たないバージョンのアプリがインストールされている場合は最新バージョンをインストールするようにストアに遷移させます。
setDynamicLinkDomain()
には、使用したいダイナミックリンクのドメインを明示的に指定します。Firebaseプロジェクトには複数のダイナミックリンクドメインを設定することが可能なため、明示的に指定しない場合は一番最初に設定したダイナミックリンクドメインが使われてしまいます。それが嫌な場合にこのメソッドを使用します。
準備ができたら、作成したActionCodeSettingsをsendPasswordResetEmail()
の第二引数に指定して呼び出します。
this.auth.sendPasswordResetEmail(email, actionCodeSettings).await()
送信されたリンクを開いてパスワードを変更する
受信したメールを開くとリンクが貼り付けられているので、端末で開きます。
新しいパスワードを入力してSAVEをクリックするとパスワードの変更が完了します。
ActionCodeSettingsを使用した場合は、CONTINUEをクリックするとアプリにリダイレクトさせることができます。
このとき、setUrul()
で指定したURLとマッチする<intent-filter />
とComposable()
が存在する場合は、その画面をディープリンク経由で開くことができます。
intent-filterを設定する
setUrl()
に指定したURLを通して特定の画面を開けるようにするため、AndroidManifest.xmlに<intent-filter />
を設定します。
<activity
...
>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="example.com"
android:pathPrefix="/special_destination"
android:scheme="https" />
</intent-filter>
</activity>
例えば、setUrl()
に指定したURLが次の場合
https://example.com/special_destination
<data />
のandroid:scheme
にはhttps
を指定します。android:host
にはexample.com
を指定します。android:pathPrefix
には/special_destination
を指定します。
これにより、アプリで上記のリンクを開いた場合はこのアプリでspecial_route
に一致するcomposable()
を開くことができるようになります。
URLにマッチするcomposable()を作成する
AndoridManifest.xmlで指定した<intent-filter />
の内容とマッチするcomposable()
は次のように作成します。
const val HTTP_SCHEME = "https"
const val DOMAIN = "example.com"
object SpecialDestination {
override val route = "special_route"
override val destination = "special_destination"
val arguments = listOf<NamedNavArgument>()
val deepLinks = listOf(
navDeepLink {
uriPattern =
"$HTTP_SCHEME://$DOMAIN/$destination"
}
)
}
fun NavGraphBuilder.specialGraph(
nestedGraphs: NavGraphBuilder.() -> Unit = {},
) {
navigation(
route = SpecialDestination.route,
startDestination = SpecialDestination.destination
) {
composable(
route = SpecialDestination.destination,
arguments = SpecialDestination.arguments,
deepLinks = SpecialDestination.deepLinks,
) { navBackStackEntry ->
SpecialRoute()
}
nestedGraphs()
}
}
navDeepLink()
のuriPattern
には、<intent-filter />
で設定した内容と同じ値を指定しています。ここでの"$HTTP_SCHEME://$DOMAIN/$destination"
はhttps://example.com/special_destination
と等価です。それをリストにまとめて、composable
のdeepLinks
引数に指定しています。
2つのドメインを承認済みドメインに登録する
送信されたパスワードリセットメールの本文には、パスワードをリセットするためのリンクが貼り付けられています。
※実際に送信されるリンクには%26などのUTF-8でエンコードされた文字列がが含まれていますが、わかりやすいようにデコード処理を行なった上で表示していますのでご注意ください。
https://プロジェクト名.firebaseapp.com/__/auth/action?mode=resetPassword&oobCode=oobCodeの内容&apiKey=apiKeyの内容&continueUrl=https://ダイナミックリンクのドメイン?link=ディープリンクのスキーマ(http or https)://ディープリンクのドメイン/ディープリンクのパス&apn=アプリのパッケージ名&amv=1&lang=en
このリンクでは、プロジェクト名.firebaseapp.com
以外に、ダイナミックリンクのドメインとディープリンクのドメインの2つのドメインが登場します。
この2つのドメインはFirebaseコンソールにて、承認済みドメインのリストに登録する必要があります。
Firebaseコンソールで、承認済みドメインの設定を行わない状態でパスワードリセットメールの送信を試みると、エラーが発生することがあります。
An internal error has occurred. [ UNAUTHORIZED_DOMAIN:Domain not whitelisted by project ]
これは、ダイナミックリンクのドメインを承認済みドメインに登録しないでリセットメールの送信を行おうとした場合のエラーです。
ディープリンクで使用するドメインを承認済みドメインに登録していない場合は、リンクを開いた際に次のようなページが開かれてパスワードのリセットを行うことができない状態になります。
ダイナミックリンクを設定する
ダイナミックリンクを承認済みドメインに登録するために、まずはダイナミックリンクドメインの作成を行います。
FirebaseコンソールでDyanmic Linksを開いて、始めるをクリックします。
セレクトボックスから任意のドメインを選択して続行をクリックします。
追加が完了したら、完了をクリックします。
この画面で表示されているのが、ダイナミックリンクのドメインです。この値をコピーしておきます。
ダイナミックリンクのドメインとディープリンクで使用するドメインを承認済みドメインに登録する
承認済みドメインに任意のドメインを追加したい場合は、FirebaseコンソールでAuthenticationを開いてSettingsタブをクリックします。
サイドナビゲーションから、承認済みドメインをクリックします。
ドメインの追加をクリックして、2つのドメインをそれぞれ分けて登録します。
packageNameを取得する方法は?
packageName
はContext
を通して取得します。
JetpackComposeを使用している場合は、LocalContext.current
を通してContext
を取得し、packageName
プロパティを使ってpackageName
を取得できます。
val context = LocalContext.current
val packageName = context.packageName
minimumVersionには何を指定すれば良いの?
minimumVersion
に指定する値は、build.gradleで設定したversionCode
の値です。
アプリにリダイレクトはされるけど、指定した画面を開くことができない
アプリが終了した状態でリンクを開いて、期待した画面が開く場合には、AndriodManifest.xmlの<Activity />
のlaunchMode
がsingleTask
になっている場合があります。
<activity
android:launchMode="singleTask"
>
</activity>
この場合、何も指定しないか、standard
もしくはsingleTop
を指定すると改善する可能性がありますのでお試しください。
それ以外の場合には、設定したURLと<intent-filter />
やcomposable()
に設定したdeepLinks
の内容に相違がある場合がありますのでもう一度ご確認ください。
まとめ
パスワードのリセットのためのメールの送信や、パスワードを更新する際のWeb上のページなど、Firebase側があらかじめ提供しれくれている機能を使用することで開発しないといけないアプリ上の機能などに時間を費やすことができるようになります。
みなさんも積極的にFirebaseを活用し、素敵なアプリの開発を行なってみてください。
参考にした記事