みなさん、やっていますか?私はやっています。
Slack API を使いたくて、Slack の OAuth の仕様通り Redirect URI を設定してアプリに auth code
と state
を戻すために Navigation Architecture Component を使った DeepLink の実装をしたところ、盛大にハマったので記録を残しておきます。
OAuth の Redirect URI みたいなものをさくっと再現した状況で試してみます (https://github.com/KeithYokoma/NavComponentDeepLinkSample)。
前準備
アプリケーションを2つ用意します。この記事では DeepLink で開くアプリを nav1、DeepLink を発動するアプリを nav2 とします。nav1 から nav2 を普通の Intent で開くボタンで nav2 に遷移し、nav2 から nav1 を DeepLink で開くボタンをそれぞれ持っているとします。
まず準備するものとして次のものを使えるようにしておきます。
- Naivgation Architecture Components (Navigation):
2.1.0
そして画面としては次のような構成にしておきます。
まず nav1 のほう。
- AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="dev.keithyokoma.nav1"> <application> <activity android:name=".DeepLinkActivity" android:launchMode="singleTask"> <nav-graph android:value="@navigation/nav_graph"/> </activity> </application> </manifest>
- nav_graph.xml
<?xml version="1.0" encoding="utf-8"?> <navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/nav_graph" app:startDestination="@+id/nav_deep_link"> <fragment android:id="@+id/nav_deep_link" android:name="dev.keithyokoma.nav1.DeepLinkFragment" android:label="DeepLink"> <argument android:name="code" app:argType="string"/> <argument android:name="state" app:argType="string"/> <deepLink app:uri="nav1://deep/link?code={code}&state={state}"/> </fragment> </navigation>
簡単ですね。DeepLinkActivity
があって、Slack の仕様にあるように Redirect URI のパラメータとして code
と state
を受け取れるようにします。DeepLink を受け取ると DeepLinkFragment
を表示します。
OAuth 2.0 では code
は auth token を得るために一時的に発行されるトークンで、Slack なら Slack が生成して渡してくれます。一方で state
はアプリケーションが発行し、ID Provider (Slack など)に渡します。そして Redirect URI で返ってきた state
をみて同一なら途中でよくないことが起きていないチェックができるというものです(ざっくり)。
ということは、nav1 にある DeepLinkFragment
は自分がどんな state
を発行したのか覚えておく必要があります。そのため、DeepLinkActivity
には android:launchMode="singleTask"
を設定しているわけです。これがないと、DeepLink を発動するたびにあたらしい DeepLinkActivity
を起動してしまい、どんなに ViewModel
などで覚えていても意味がなくなります。
DeepLink を発動してみる
ここで DeepLinkFragment
で何をしているかを見てみます。
通常、DeepLink を受け取ると code
や state
は Fragment#setArguments
の Bundle
に詰め込まれるので、次のコードのように、onResume
等で getArguments
で Bundle
をとってきて中身を確認知れば、code
や state
がいるはずです。
class DeepLinkFragment : Fragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_deep_link, container, false).also { it.findViewById<Button>(R.id.button).setOnClickListener { startActivity(Intent() .setComponent(ComponentName("dev.keithyokoma.nav2", "dev.keithyokoma.nav2.MainActivity")) ) } } // ここで DeepLink のパラメータを読む override fun onResume() { super.onResume() arguments?.keySet()?.forEach { println(arguments?.get(it)) } } }
そして nav1 の DeepLinkFragment から nav2.MainActivity を立ち上げ、nav2.MainActivity にあるボタンで DeepLink を発動してみると…
println()
で出てきてほしいログが何一つ出てきません。つらい!
なぜなのか
要点は NavController#handleDeepLink(Intent)
のドキュメントにあります。
Checks the given Intent for a Navigation deep link and navigates to the deep link if present. This is called automatically for you the first time you set the graph if you've passed in an Activity as the context when constructing this NavController
このメソッドは DeepLink を受けとって Activity が起動すると、Navigation Graph を構築する段階で Graph 内にある適切な Fragment に DeepLink のパラメータを渡します。
つまり今回の実装のような、すでに Activity が起動していて (== すでに Navigation Graph が構築済の状態で)、その Activity を DeepLink で再度呼び戻す場合には自動で DeepLink のパラメータを渡してくれません。さっきのドキュメントには続きがあり、
but should be manually called if your Activity receives new Intents in Activity.onNewIntent(Intent).
とあるので、Activity#onNewIntent(Intent)
を使って自分で NavController#handleDeepLink(Intent)
を呼び出す必要があります。