android: Hilt対応
2024/10/25
ViewModel を継承したクラスに @HiltViewModel を付けて実装しておくと、Activity の方では hiltViewModel() でうまいこと ViewModel を生成してくれる。
では、BLE機器をスキャンする BleScanクラスを ViewModel で生成するならどうするとよいか。
ただし BleScan はコンストラクタで ApplicationContext をパラメータとして受け取るものとする。
見よう見まねだ。
- @Binds を使用してインターフェース インスタンスを注入する
@Bindsと@Providesが載っていたが、@Providesは自分で制御できないモジュールの例になっていたので@Bindsにした- まず、
BleScanクラスをDefaultBleScanという名前にしておく- 名前はなんでもよい。ここではサフィックスに
Implを付けているが、archtecture-templates だとプレフィクスにDefaultと付けていたのでそちらにあわせた。
- 名前はなんでもよい。ここではサフィックスに
BleScanインターフェースを作り、DefaultBleScanに継承させる@Moduleと@InstallIn()を付けたabstract class BleScanModuleを作る@Bindsの付いたabstract fun bindBleScan(DefaultBleScan): BleScanを作る
あとは ViewModel の方で Context を取る必要がなくなったので削除したくらいか。
これだけの記述で、動く。
不思議だ。。。
Android Studio では↑で作ったクラスなどが未使用っぽく見えてしまうが、直接 Hilt/Dagger が参照しているわけではないので仕方ない。
ぜいたくを言えば、BleScan のファイルの中には BLE操作だけのコードだけにして、Hilt に関係するのは別のファイルにしたいという気持ちがある。
そういう場合は、自分で所有していないクラスについても対応できる @Providesを使うとよさそうだ。
@Binds がコンストラクタを紐付けるのに対して @Provides は生成する処理を紐付けるというところか。
@ApplicationContext などもこちらの書けるので、本体側は普通の実装だけ書けば良い。
コンストラクタを直接ではなく、一度生成するメソッドを呼び出すので @Provides の方がちょっとだけ効率が下がるのかな?
下がるという程でもない気はする。そんなにバンバン生成するわけでもないだろうし。
archtecture-templates の di.DatabaseModule もそうやっていた。
こちらは複数メソッドがあるので、一つ上の local.database パッケージにあるクラスを 1箇所で対応するという書き方もできるのだろう。
おまけ
nullable な関数型の呼び出し
BLE機器をスキャンするとコールバックで機器情報が返ってくる。
スキャン中は見つかり次第返ってくる(同じ機器が返ってくるのは RSSI が変わったからかもしれんが)ので、今回の BleScan でもスキャンした結果は上側に返そうと思う。
そのとき、スキャン開始でコールバック関数を付けて呼び出してもらうとする。
コールバック関数を引数に取る関数を higher-order function と呼ぶ。
関数型を戻す関数のことも higher-order function と呼ぶようだ。
OS が結果をコールバックするのはスキャン開始した関数の中ではなかったので、付けてもらったコールバック関数は BleScan で保存しておく。
private var resultCallback: ((String) -> Unit)? = null
これを実行しようと素直に書くと、これはダメだった。
if (resultCallback != null) {
resultCallback(result)
}
関数型の変数を呼び出すのにはinvokeを使うものらしい。
が、単にf(x)でもよいとも書いてある。
invoke は関数ではなく演算子(operator function)らしい(11.1.3 Callables and invoke convention)。
いろいろ書いてあるが深追いしない。。。
resultCallback?.invoke(result)
Android Studio では修正候補がもう1つ出てきて、こちらが優先?だった。
Execute if not null というものらしい。
resultCallback?.let { it(result) }
どちらも if での null チェックは不要なようだ。
でも、if 文で書いても大したことないと思う。
わざわざ指摘するということはなんか理由があるのだろう。
コンテキストが違うので if 文でチェックしたときは null じゃなかったけど呼び出そうとしたら null になる可能性があるというところか。
