見出し画像

Android Navigation componentでdeep link定義するときの注意点

AndroidのNavigationを使えば簡単にdeep linkの定義追加ができますが、実際に使ってみていくつか注意することがありました。

なかなかどれも説明が大変なものばかりなうえに、ハマると時間が溶けるものだったので紹介してみます。

androidx.navigationライブラリのバージョンが2.3.3の時点での記事です。今後のアプデで挙動が変わっている可能性あります。

ホスト部にアンダースコアは使えない

XMLで定義したURLはJavaのURIクラスで処理されるようなのですが、このURIクラスが採用しているURIの規格ではホスト部分のアンダースコアを許容していないみたいです。

ホスト部分というのは下のURLのhostの部分です。pathの部分にアンダースコアは大丈夫なのですが、hostの部分にアンダースコアはNG。

scheme://host/path/path/?query=1

ただ、アンダースコアを定義したからと言ってコンパイルエラーになるというわけではなく、通ってしまいます。

getHost()関数の返り値がnullになるようなのですが、これの影響で、intent filterに定義されるdeep linkも、navigatinで解釈されるdeep linkもhostの部分が空文字になってしまいます。

こういうdeep linkとしてコンパイルされてしまっている。
scheme:///path/path/?query=1

これ、scheme://host/path/?query=1のdeep linkを受信したときにも空文字になってしまうので、うまく動いてしまうという問題もあります。これのせいで案外気づかない。。なんてことも発生してしまいます。

僕が気づいたのは別のdeep linkを定義したときでした。そのdeep linkもアンダースコアを使っていたのですが、重複定義としてコンパイルエラーになりました。

example://app_detail_page
example://app_user_page

これはコンパイルすると以下のような形で登録されています。

example:///
example:///

同じになってしまってますね。。重複でコンパイルエラーになり、そしてこの仕様に気づいたというわけです。こればかりはアプリ側で同しようもないので、このアンダースコアのdeep linkをやめざるを得ませんでした。

path部が空の場合intent-filterに/が自動で付与される

こちらの記事で言及されており大変参考になったのですが、こいつがなかなかに曲者です。

例えばこういうdeep linkです。

example://host
example://host?query=12345

これをビルドすると、なぜかpathの部分に/が追加されてintent-filterに登録されます。

// 登録されるのはこういう形式になってる!
example://host/
example://host/?query=12345

なので、実際にdeep linkを受け取るときも、example://hostではアプリが反応せず起動しなくて、example://host/なら起動します。

ただし、navigationのほうの定義ではもとのexample://hostのままなので、起動はするけどnavigationのdeep link遷移処理は動きません(つら)

この仕様(バグじゃないのか。。)のせいでなんでか動かなくてつらいが発生します。

ちなみに、hostの部分ではなくてpathのほうでは、最後に/があってもなくてもそのままintent-filterに反映されます。

// どっちでもそのまま登録される
example://host/path
example://host/path/

この問題の厄介なところは、navigationを使わないで従来どおり手動でintent-filterに登録する場合はHost部の末尾に/があってもなくても大丈夫なんです。なので、今までHostの末尾に/がないdeep linkで運用していて、navigationコンポーネントを使い始めたときにも直面するのです。運用も始まっていて、なかなかあとからdeep linkを変えるというのも、現実的でないことも多いと思います。

そこで、対応策としては以下2点があります。

1. Host部の末尾に/がついて困るdeep linkだけ手動でintent-filterにも登録する。

intent-filterのほうはHost部の末尾に/がなくてもOKなので、そちらにも手動で登録するという方法です。その場合、navigationに定義されているdeep linkから自動登録されるdeep linkと、手動で登録したdeep linkの2つが登録されています。

// これを実運用でつかいたい!
example://host
// navigationにはこれで登録する + AndroidManifestにも手動で追加する
<deepLink app:uri="example://host" />
// 実際にintent-filterには2つ登録される
example://host/
example://host

こうすれば、example://hostを受信して、アプリが起動し、navigationも正常に遷移処理できます。example://host/を受信した場合は、アプリは起動するけど遷移処理はされない、という感じになるはずです。

これが一番いい解決方法なのかなという気がしますが、せっかくnavigationで定義できるのに、微妙に多重定義になってしまうのがなんとも、、というのが気になりますね。

2. Schemeでアプリを起動するようにしてしまう

若干ハック的なやりかたで、たまたま気づいた処理方法なのですが、例えば下記のようなSchemeをdeep linkで使っている場合、

// exampleの部分がScheme
example://host

navigationにexample:///を定義すれば、問題なくdeep linkを処理できるようになります。

// もはや完全におまじない😂
<deepLink app:uri="example:///" />

<deepLink app:uri="example://host" />

なぜこれで動作するようになるかというと、Schemeと空のhostでintent-filterで登録することで、Schemeがexampleのものであればすべてのdeep linkでアプリが起動するようになります。example://hostでアプリが起動さえしてしまえば、navigationのほうでは正常に処理できるのでうまくいくという方法です。

欠点としては、exampleスキームはどれでも起動してしまうという点です。例えば、example://aaaaやexample://bbbb/cccc?query=12345など、deep link定義のないものでもアプリが起動するようになります。スキームがあまり汎用的でなく、ほぼ他アプリと重複しないようなサービス名になっているのであれば十分ありかなと思います。

おわりに

Hostにアンダースコアが使えないのはなるほどと思いました。言語ごとにURLの採用企画を知っておいたほうがいいというのは学びでした。

勝手に/を付与するのは完全に罠ですね。。仕様らしいのですが、ドキュメントにも記載は無いような気がしますし、もともとintent-filterのほうでは許容されてるので、なんとかならないかなぁと思ってしまいました😭

この記事が気に入ったらサポートをしてみませんか?