こんにちは、WantedlyでAndroidアプリエンジニアをしている吉岡です。
8月23日に弊社オフィスにて 参加者全員がTipsを発表する というコンセプトの iOS / Android アプリ開発者向け勉強会 potatotips #32 を開催いたしました。
今回の Wantedly Engineer Blog では、こちらで私が発表しました 暗黙的IntentをプログラマブルにするTips というLTの資料を公開します。
WantedlyではAndroid向けに、Wantedly会社訪問アプリ, 就活インターンアプリ, ビジネスチャットアプリのSync, そして就活メール・テンプレアプリ を提供しています。私はAndroidアプリエンジニアとして、会社訪問アプリのグロースを担当するチームに所属しています。
グロースをさせる上で欠かせない要素のひとつに、メールやウェブサイトからアプリへシームレスに移動できる機能の実装があります。
これらの技術として、今まで独自スキーム指定によるディープリンクの実装が一般的でした。しかしiOS9で http や https といったスキームからでも遷移を可能にする「Universal Link」が登場したのをきっかけに、iOS, Android併せて実装する流れが主流になりつつあります。
そもそもAndroidではいつからUniversal LinkのようなhttpのURLから起動する機能があったかというと、実はAPI Level 1、つまり初期リリースから存在しています。
これは暗黙的Intentと言って、AndroidManifest.xml内にintent-filter要素を記述し、マッチしたURLからActivityを起動することができる機能です。
Standard Approach
さて、よくあるintent-filterの実装をしてみます。
`https://example.wantedly.com/example_content/{id}` というWebページがあるとしましょう。{id} 部分はコンテンツに応じて可変で、サーバ側でインクリメントされる数値などだとします。内容としては、Web上で完結させることも可能だけど、ユーザにはアプリ内で操作をしてほしいコンテンツが含まれていると想定します。例えばブラウザを閉じてもPush通知してほしいチャットなどがこれに当たりそうです。
このリンクをタップしたタイミングでアプリ上で閲覧することを促します。<activity/>要素はこのようになります。
これを受け取ったアプリ側はidを解釈し、適切な表示に切り替えるはずです。
おめでとうございます、ひとまず完成です! これでAndroidでもUniversal Linkと同様の動作が期待できます。
さて、暗黙的Intentへの対応初期であれば、Android側で対応する画面が少ないためおそらくこの形でリリースされるでしょう。
しかし将来Androidで対応する画面が増えるとどうなるでしょうか? 同様のコード、つまりActivityごとにintent-filterを記述し、Activityごとにパターンマッチングを記述することになるでしょう。既にこのコードで発生してる問題でもありますが、ManifestとJavaコードの両方へパターンマッチが分散しているためメンテナンス時には2つのファイルを編集する必要が出てきます。
さらに暗黙的Intentだけでなくディープリンクに対応することになるとどうでしょう?
サンプルコード: AndroidManifest_partial2.xml
サンプルコード: ImplicitIntentActivity_partial2.java
一気に冗長になりました。
さらに想定されるケースとして、アプリ内でのリンクタップ時に対応する画面に遷移させたい場合がありそうです。ブラウザかアプリかの選択肢を表示させず、直接対応する画面へ遷移させたいですよね。その場合は拡張されたLInkMovementMethodをTextViewに与え、アプリ内で処理できる全てのURLマッチングパターンを試し、対応する画面へ遷移させることに...
どんどん冗長的になり、メンテナンス時のミスも発生しうる状態です。これらをプログラマブルに出来ないのでしょうか?
Wild Approach
つまり問題は単純です。関連するURL全てをキャッチし、全て同じユーティリティクラスの中で処理すればいいのです。簡単!
サンプルコード: AndroidManifest_partial3.xml
サンプルコード: WildlyIntentHelper.java
共通するURL処理を行うユーティリティを作成し、同じActivityからルーティングすればよさそうです。
以上のアプローチで一見解決されたように思えますが、課題として以下が挙げられます。
- 未対応の画面もアプリで開かれてしまう
- 一度直接的には関係ないActivityを通ってしまう
それぞれ、例えばActivityはTranslucentなものにする、未対応なページは再度ブラウザで開くように転送する、WebViewで閲覧させる、などで解決できるかもしれません。しかしプロジェクト規模によっては有効なこのアプローチも、ユーザ体験を考慮するとあまり良いとは言えません。
Black Magic Approach
WantedlyのAndroidエンジニアはこの問題を解決すべく、黒魔術で対抗しました。課題解決の方法を考えれば至極単純で、Manifestやパターンマッチングのコードを自動生成すればいいことになります。
昨今のAndroid開発ではビルドツールとしてGradleが広く使われています。Gradleで利用されるビルドスクリプトの実体はGroovyで記述されたスクリプトファイルです。
Groovyについて少しおさらいしましょう。GroovyはJVM上で動作する言語処理系で、Javaと直接的な連携が可能です。またコンパイル時に生成されるコードがJavaソースコードではなくJavaバイトコードであることも特徴です。
わかりやすく言うならば、JavaScriptに対するCoffeeScript, TypeScript, HaxeなどのAltJS、GroovyはいわばAltJavaとなる言語です。
GroovyはJavaと直接的な連携が可能なため、Javaで記述されたクラスやライブラリをそのまま扱うことが出来ます。そこでJava向けのテンプレートエンジンであるmvel2を用い、Gradleのビルドスクリプトから直接コード生成する、という選択をしました。
また、Gradle (正しくはAndroid Plugin) にはManifest Mergerという機能があり、2つ以上のモジュールにまたがったManifestを適切にマージすることができます。
これを応用し、このアプローチではプロジェクト内に配置した別のモジュール内にManifestを生成しています。こうすることで、本体アプリのManifestへ記述しないといけない値をそのまま残しつつ、intent-filter関連のみの情報を後からマージさせることが出来ます。
データを元にManifestとマッチング処理が自動生成されるため、冗長的な記述はゼロにすることができます。
しかしプロダクト本体以外へのメンテナンスコストがそれなりにかかること、あくまで黒魔術であることなど、本当に自分たちのチームで導入すべきかは検討する必要があります。
なお、このアプローチについては弊社の住友がQiitaにて詳細に解説しております。是非あわせてご覧ください。
Another Approach
あんなにワイルドなアプローチは嫌だ、黒魔術はやりたくない、自分たちのチームには導入したくない! という方に朗報です。Androidネイティブアプリでありながらこれらの課題を一挙に解決出来るソリューション、
Xamarinのご紹介です。
Xamarinは.NETのクロスプラットフォームな実装で、Gnomeデスクトップ環境などでお馴染みのMonoに由来しています。C#からAndroidなど様々なプラットフォーム向けのネイティブアプリを開発できるだけでなく、.NETの資産を活用することが可能です。Microsoftによって買収されたことも記憶に新しいですね。
Xamarin自体は各プラットフォームのAPIを薄くラップしたもののため、基本的にはプラットフォームのAPIをC#から直接叩くことになります。よって、実際に活用するためにはプラットフォームに対する十分な理解が必要です。
しかしただのラッパーだけではなく、C#の言語特徴やデザインパターンを上手く活用出来るようバインディングも充実しており、Manifestへの記述も例外ではありません。
サンプルコード: AnotherImplicitActivity.cs
Activityクラスに対しActivityAttributeとIntentFilterAttributeを付加することで、Manifestへの記述も含めコード上で全てが完結します! すばらしいですね。
これはあくまでシンプルな一例ですが、全ての値をプログラム内に置けるため活用すればメンテナンスコストを大幅に減らすことが可能なはずです。ユーティリティクラスなどを作成するとより完結に実装できるでしょう。
念のため、お知らせです。
まとめ
暗黙的IntentをプログラマブルにするTipsを3つご紹介しました。それぞれメリット, デメリットが存在し、またプロジェクトの規模によっても選択すべきものが大きく異なるでしょう。
- チームやプロジェクトに合ったアプローチを選ぼう
- 課題解決の過程でもユーザ体験を考えること
- 黒魔術は計画的に
- Xamarinはいいぞ
さいごに
Wantedlyでは技術的アプローチで課題解決できるエンジニアを募集しております。是非、気軽に話を聞きに来てみてください。