3
4

More than 1 year has passed since last update.

【Android】パスワードのリセットメールを送信して、ディープリンクを使ってアプリに戻る方法のまとめ

Posted at

はじめに

現在開発中のアプリでパスワードのリセット機能を作成する機会がありました。アプリ内で再認証を行いパスワードのリセットを行うこともできるのですが、今回はパスワードリセットメールの送信を行いパスワードのリセットを行うことに決めました。

その際にディープリンクの設定など初見では複雑にみえる処理などがあり、躓くポイントが何点かありました

今回は、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()

送信されたリンクを開いてパスワードを変更する

受信したメールを開くとリンクが貼り付けられているので、端末で開きます。

5.png

新しいパスワードを入力してSAVEをクリックするとパスワードの変更が完了します。

ActionCodeSettingsを使用した場合は、CONTINUEをクリックするとアプリにリダイレクトさせることができます。

6.png

このとき、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と等価です。それをリストにまとめて、composabledeepLinks引数に指定しています。

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 ]

これは、ダイナミックリンクのドメインを承認済みドメインに登録しないでリセットメールの送信を行おうとした場合のエラーです。

ディープリンクで使用するドメインを承認済みドメインに登録していない場合は、リンクを開いた際に次のようなページが開かれてパスワードのリセットを行うことができない状態になります。

7.png

ダイナミックリンクを設定する

ダイナミックリンクを承認済みドメインに登録するために、まずはダイナミックリンクドメインの作成を行います。

FirebaseコンソールでDyanmic Linksを開いて、始めるをクリックします。

5.png

セレクトボックスから任意のドメインを選択して続行をクリックします。

6.png

追加が完了したら、完了をクリックします。

7.png

この画面で表示されているのが、ダイナミックリンクのドメインです。この値をコピーしておきます。

8.png

ダイナミックリンクのドメインとディープリンクで使用するドメインを承認済みドメインに登録する

承認済みドメインに任意のドメインを追加したい場合は、FirebaseコンソールでAuthenticationを開いてSettingsタブをクリックします。

サイドナビゲーションから、承認済みドメインをクリックします。

9.png

ドメインの追加をクリックして、2つのドメインをそれぞれ分けて登録します。

packageNameを取得する方法は?

packageNameContextを通して取得します。

JetpackComposeを使用している場合は、LocalContext.currentを通してContextを取得し、packageNameプロパティを使ってpackageNameを取得できます。

val context = LocalContext.current
val packageName = context.packageName

minimumVersionには何を指定すれば良いの?

minimumVersionに指定する値は、build.gradleで設定したversionCodeの値です。

アプリにリダイレクトはされるけど、指定した画面を開くことができない

アプリが終了した状態でリンクを開いて、期待した画面が開く場合には、AndriodManifest.xmlの<Activity />launchModesingleTaskになっている場合があります。

<activity
    android:launchMode="singleTask"
>
</activity>

この場合、何も指定しないか、standardもしくはsingleTopを指定すると改善する可能性がありますのでお試しください。

それ以外の場合には、設定したURLと<intent-filter />composable()に設定したdeepLinksの内容に相違がある場合がありますのでもう一度ご確認ください。

まとめ

パスワードのリセットのためのメールの送信や、パスワードを更新する際のWeb上のページなど、Firebase側があらかじめ提供しれくれている機能を使用することで開発しないといけないアプリ上の機能などに時間を費やすことができるようになります。

みなさんも積極的にFirebaseを活用し、素敵なアプリの開発を行なってみてください。

参考にした記事

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4