Dagger HiltのenableExperimentalClasspathAggregationオプションについて
少し前の話題ですが、取り上げている記事が見当たらなかったためまとめます。
前提
Androidプロジェクトにおいて3つのモジュールが以下のような依存関係にあったとします。
app -> intermediate -> db
appモジュールはApplicationクラスを持ち、モジュール間の依存関係はimplementationで記述されているとします。
このとき、マルチモジュールの利点の1つとして、
appモジュールがdbモジュールに直接依存していないことによって、dbモジュール内のコードを直接参照することを防ぐことができます。 … (1)
問題
Dagger Hiltではルートのappモジュールにおいて全てのインスタンスを生成するためにそれらに直接参照できる必要があります。
つまりDagger Hiltを利用しようとすると、
appモジュールがdbモジュールに直接依存するか、intermediateモジュール内でdbモジュールへの依存関係をapiで定義する必要があります。 … (2)
これだと先ほどのマルチモジュールの利点 (1) が失われてしまいます。
解決策
この問題のワークアラウンドとして、Dagger Hilt 2.31.x-alpha以降からappモジュールの build.gradle
ファイルに以下のような記述をすれば (2) の対応を行わずとも (1) の利点を保つことができるようになります。
hilt { enableExperimentalClasspathAggregation = true }
ただし、まだExperimentalです。
詳しくはこちら dagger.dev
ざっとしかコードを読んでいないのですが、遷移的依存関係をコンパイル時に利用できるように CompileOnly
コンフィグを利用して依存関係を処理しているようです。
このような処理を行っているので、ビルド時処理が増えてしまいます。
これに関しては、以下のように述べられているので今後改善していくと思われます。
This solution is inefficient and will cause build performance impact, but it is a starting point that can be further optimized by using a smarter transform that can extract the necessary classes required by Dagger and Hilt.
おまけ
この問題はHiltに限った話でなく、Daggerでも発生します。
幸いなことにDagger 2.29で追加された validateTransitiveComponentDependencies
オプションを利用すると解消されるようです。
これに関して解説しているページを見つけたので添付しておきます。
Android Roomの自動マイグレーション
AndroidにRoomというSQLiteのライブラリがありますが、マイグレーションに関してはサポートが薄く単純な変更ですらコストがかかっていると思います。
という状況だったのですが、 2.4.0-alpha01
にて一部の変更の自動マイグレーションがサポートされたようです 🎉🎉
全自動でマイグレーションされる場合
明文化はされていないようですが、以下の場合は全自動でマイグレーションされるようです。
- テーブルの追加
- カラムの追加
スキーマをエクスポートするように設定した後、以下のように Database
アノテーションに変更バージョン情報を持った AutoMigration
を指定してあげるだけです。
@Database( entities = [Todo::class], version = 2, autoMigrations = [ AutoMigration(from = 1, to = 2) ] ) abstract class AppDatabase : RoomDatabase() { abstract fun todoDao(): TodoDao }
サポートが必要な場合
Roomのコードを少し読んでみると、SchemaDiffer クラス でスキーマのdiffを取り、AutoMigrationProcessor クラス ではその追加されたテーブル/カラム情報は利用していますが削除された情報は利用していません。
これはテーブル/カラムが削除されていた場合、それが単なる削除なのかリネームなのかが判定できないからです。
ということで、以下の変更は AutoMigrationSpec を実装してどう変わったかをRoomに教えてあげる必要があります。
- テーブル名の変更 (
@RenameTable(fromName, toName)
) - テーブルの削除 (
@DeleteTable(name)
) - カラム名の変更 (
@RenameColumn(tableName, fromColName, toColName)
) - カラムの削除 (
@DeleteColumn(tableName, colName)
)
上記変更が必要な場合は、生SQLを書いてねというわけではなくしっかりサポートがあるあたり優しいですね。
また、試してみたところspecの指定が不足していた場合コンパイル時に何が必要か優しく教えてくれます。
error: public abstract class AppDatabase extends androidx.room.RoomDatabase { ^ AutoMigration Failure: Please declare an interface extending 'AutoMigrationSpec', and annotate with the @RenameColumn or @RemoveColumn annotation to specify the change to be performed: 1) RENAME: @RenameColumn( tableName = "Todo", originalColumnName = "isDone", newColumnName = <NEW_COLUMN_NAME> ) 2) DELETE: @DeleteColumn=( tableName = "Todo", deletedColumnName = "isDone" )
複数変更がある場合は以下のように指定してあげれば良いです。
@DeleteColumn( tableName = "Todo", columnName = "isDone" ) @RenameColumn( tableName = "Todo", fromColumnName = "text", toColumnName = "content" ) class V3AutoMigrationSpec : AutoMigrationSpec
生SQLが必要な場合
それ以外の複雑なテーブルの変更は生SQLでマイグレーションを書いてあげる必要があります。 例えば以下のような場合です。
- テーブルを分割/統合する
- テーブルの正規化/非正規化
- Viewに対する変更
Viewは試してみた限り、今回のサポートには入っていないようです。(多分)
生SQLが必要な場合でも、AutoMigration機能と併用することができます。
まとめ
まだ現時点ではalpha版なのでそこは注意ですが、かなりマイグレーションの負担が減るのではないでしょうか。
個人的にはかなり嬉しいアップデートでした。
Fiddler Everywhereを使ってMac上でAndroidアプリのHTTP(S)通信をキャプチャする
導入
Androidアプリを開発していると、アプリ上でどういう通信をしているか覗きたいときありますよね。
そんなときに、よく使われるのがCharlesですが、
みんな大好きFiddlerがMacやLinuxでも使えるFiddler Everywhereとして今年の春頃にリリースされていたのでした。
あまりFiddler Everywhereについての日本語記事がなかったので、
今回はMac上でFiddler Everywhereを使ってAndroidアプリのHTTP(S)通信をキャプチャするやり方について、簡単にお話します。
Fiddler Everywhereのセットアップ
まず、Fiddler Everywhereを公式サイトから適当にダウンロードしてインストールしてください。
起動しましたら、この時点でMac上のHTTP通信が丸裸にされていると思います。
右上の設定ボタンを押して、
HTTPS
の項目で Trust root certificate
します。
さらに Cpature HTTPS traffic
がオンになっているか確認してください。
これでMac上にルート証明書がインストールされて、Mac上でのHTTPS通信が一度Fiddlerで赤裸々にされるようになります。
次に、 Connections
の項目で、ポート番号を確認し、 Allow remote computers to connect
にチェックを入れます。
これで他の端末がFiddlerのプロキシを利用できるようになります。
右下のSaveボタンを押して、Fiddler Everywhereの設定は完了です。
一回、Fiddler Everywhereを再起動しておきましょう。
Android端末のセットアップ
次に、Android端末にプロキシを設定します。
まず、Fiddler Everywhereが動いているMacのIPアドレスを確認しておきます。
一番簡単なのは、System Preferences.appのNetworkで確認する方法でしょうか。
次に、HTTP(S)通信をキャプチャしたいAndroid端末をMacと同じLANに接続しておきます。
そして接続しているWi-Fiの設定を開き、プロキシを手動に切り替え、先ほど確認したIPとポート番号を入力して保存します。
これで当該Android端末で通信される通信がFiddler Everywhereを仲介するようになったので、
ブラウザで適当なHTTP通信を行うとFiddler Everywhereで確認できるようになったと思います。
しかし、ルート証明書をインストールしていないので、
http://ipv4.fiddler:{先ほど確認したポート番号}
にアクセスし、下部リンクからルート証明書をダウンロードして、 適当な名前を付け、端末にインストールさせてください。
これでAndroid上のブラウザで適当なHTTPS通信をすると、Fiddler EverywhereでHTTP(S)通信を閲覧できるようになったと思います。
Androidアプリの設定
とはいえ、プロキシ設定のダイアログに書いてあった通り、ブラウザ以外のアプリではプロキシが利用できないので、
開発しているアプリ上の設定を弄ります。
まず、以下の内容のファイル res/xml/network_security_config.xml
を作成してください。
<?xml version="1.0" encoding="utf-8"?> <network-security-config> <debug-overrides> <trust-anchors> <certificates src="user" /> </trust-anchors> </debug-overrides> </network-security-config>
次に AndroidManifest.xml
の application
に以下のプロパティを追加します。
<application ... android:networkSecurityConfig="@xml/network_security_config"> ...
これで当該アプリをデバッグ実行すると、Fiddler EverywhereでHTTPS通信が見れるようになったはずです。
Firebase Hosting x ReactでOGP対応する
概要
Firebase HostingにデプロイされたReactアプリにて、
Twitterでシェアされた時にページ毎に違うOGPを設定する方法について、備忘録がてら述べます。
経緯
React Helmetを使って動的にOGPを設定しても、TwitterのクローラがJSを実行しないようなので、
ReactプロジェクトでOGPをTwitterで表示する方法として
主に、
- ページ毎にHTMLを事前に生成しておく
- アクセス時に動的にHTMLを生成する
という方法があります。
今回のプロジェクトの都合上、2の方法を選択したのでそのやり方を説明します。
もちろん、お金を使える場合はNetlifyのPrerendering機能を利用したり、
GatsbyJSなどを利用したりしている場合は、1の方法が採用できますね。
Prerendering | Netlify Docs
動的にHTMLを変更する
アクセス時に動的にHTMLを生成する方法としても、
例えばRailsがHTMLを返すシステムの場合、Rails側でリクエストに応じてHTMLを返却すれば問題なさそうです。
ここではFirebase Hostingを使っているケースで、OGPを設定する方法について述べます。
firebase.json の設定
まず、プロジェクトのfirebase.json
で以下のように設定します。
以下の場合/posts/**
にアクセスすると、Firebase FunctionsにデプロイされたaddOgTagsInPost
関数に処理が移譲されます。
その他のページにアクセスすると/index.html
が表示される設定になっています。
ここでsource
やfunction
はよしなに名前を設定してください。
{ "hosting": { ... "rewrites": [ { "source": "/posts/**", "function": "addOgTagsInPost" }, { "source": "**", "destination": "/index.html" } ] }, ... }
関数の実装
上記の例では/posts/**
にアクセスされると、以下の関数が呼ばれるので、
ここでいい感じにHTMLを返してやればいいわけです。
クローラ以外の場合、正規ページにリダイレクトする方法もあるのですが、
OGPが表示されるページと、されないページが存在するのを避けたかったので、
今回は、Firebase Functions側にもHTMLファイルをデプロイしておく方法を採用しました。
以下は例なので、よしなに修正してください。
基本的にはHTMLファイルを読み込んで、</head>
タグの直前にmetaタグを挿入しています。
また、ページに応じて画像のURLを変更しています。
import * as functions from "firebase-functions" import * as fs from "fs" import * as path from "path" export const addOgTagsInPost = functions.https.onRequest((req, res) => { const uid = req.path.split("/")[2] const ogTags = '<meta name="twitter:card" content="summary_large_image"></meta>' + '<meta property="og:title" content="{title}" />' + '<meta property="og:description" content="{description}" />' + `<meta property="og:image" content="https://www.example.com/user_icons/${uid}.png" />` const htmlFile = fs.readFileSync(path.join(__dirname, "./index.html")) const html = htmlFile.toString().replace(`</head>`, `${ogTags}</head>`) res.send(html) })
HTMLファイルのデプロイ
デプロイ前に、デプロイするjsファイルと同じディレクトリ直下に、
フロントエンドで用いているHTMLファイルを設定してください。
package.json
のscripts
に処理を、設定するといいと思います。
問題点
上記のやり方には、以下のような問題点があるので注意してください。
req.path.split("/")[2]
のように/posts/**
の**
の部分の取り方が適当- HTMLが変更された場合に、当該関数の再デプロイが必要
- 忘れがち
- フロントエンドのコードと同じプロジェクトではない場合、どうする?
- Firebase Functionsを挟むので、ロード時間が比較的長くなる
- React-Routerで遷移したなどは、もちろん問題ない
- すでにHTMLにOGPが設定されていた場合、重複するという問題が発生する
</body>
を置き換えるというナイーブな実装
まとめ
以下のようにTwitter上で設定したOGPが表示されました。
※ちなみにこの曲一覧はテスト用なので、本当に僕がよく聴いている曲ではないです。
画像生成については、様々な方法があるので、よしなにやってみてください。
今回は、enPiTという大学の授業でチームで実装したアプリに、OGPを設定しようと思っていろいろやったので、せっかくなので記事化しました。
作成したアプリ↓
https://musicle-app.web.app/
DroidKaigi 2019で登壇しました
DroidKaigi 2019で「いかにしてビットコインを扱うか」というセッションで登壇しました。
題名に関してはポリア著の『いかにして問題をとくか』に影響を受けています。
登壇資料はこちらです。
:thinking_face: が文字化けしてるんですけど、補完してください。
久しぶりの登壇で緊張度がMAXだったんですが、良かったとか面白かったとか言ってくださった人がいてスピーカーとして冥利に尽きますね。
結構尖った内容だったのでCfP出す時は正直どうかなと思ってましたが、結果このテーマで出してよかったなと思っています。
登壇の場数を増やしてもう少し場に慣れたいと切実に感じたので頑張ります。
30分のセッションだと話せなかったことも多かったので、何か質問等ありましたら気軽に@yuikijpまでメンションもしくはDMしてください!
運営、参加者、スポンサーの皆さん、本当にありがとうございました!