hiro99ma blog

何か技術的なこと

android: Hilt対応

2024/10/25

ViewModel を継承したクラスに @HiltViewModel を付けて実装しておくと、Activity の方では hiltViewModel() でうまいこと ViewModel を生成してくれる。
では、BLE機器をスキャンする BleScanクラスを ViewModel で生成するならどうするとよいか。
ただし BleScan はコンストラクタで ApplicationContext をパラメータとして受け取るものとする。

見よう見まねだ。

あとは 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 になる可能性があるというところか。

image

< Top page