android: compose で system service はどこでもらうか (2)
2024/10/23
あらすじ
BLE スキャンするために getSystemService()
をどこで呼ぶと良いか悩む私。
呼ぶにしても Context
がいるため、その面倒さが何にしてもネックになることに気付く。
検討していると DI(依存性注入)ライブラリを使うとパラメータによる直接の引渡しをしないで済みそうな気配を感じる。
前回の最後は Hilt という Android 向けの DIライブラリの使い方を調べていた。
@Composable invocations can only happen from the context of a @Composable function
パラメータを Int
から Context
にするとエラーになった。
“invocation” は「祈り」とか「実施」のような意味。call とは違うのか。
私は「実行する」(execute)か「呼び出す」(call)しか思いつかんな。
- 英語「invocation」の意味・使い方・読み方 - Weblio英和辞書
- c# - What’s the difference between “call” and “invoke”? - Stack Overflow
- Invoke vs Call a Function – Learn the Difference - CodeSweetly
そういうのはともかく、コンテキストが違うと呼び出せないそうだ。
ここのラムダ式はコールバックみたいなものだと思っていたがコンテキストが違うのか。。。
私の場合もこちらと同じように composable関数の方でLocalContext.current
を保持してラムダ式の中で渡すことでエラーが出なくなった。
下の方ではこのContext
を保持するわけではなく BluetoothManager
のインスタンスを取得するのに使う。
BluetoothManager
–> BluetoothAdapter
–> BluetoothLeScanner
という流れだ。
下の方で保持するのだが Activity の作り直しが発生すると Context
も変更になるのだろうか?
デバッガで確認したところ、composable 関数での Localcontext.current
は MainActivity
だった。
(Activity
もApplication
もandroid.app
パッケージ。)
ではApplicationContext
はどこから取得したら良いだろうか。
しばし悩んだが、Activity.getApplicationContext()
で取得できるので同じように LocalContext.current.applicationContext
でよい。
DI の便利さ
昨日 Hilt で DI した(という使い方で良いのか?)のは Context
だった。
が、これは単に ScanViewModel
のコンストラクタ引数を Context
と書いて、呼び出す ScanScreen
からも Activity の context を引数に書いただけで、全然 Hilt を導入したよさがない。
そもそもどうなるのが便利なんだっけ?
- HiltViewModelで始めるAndroidの依存性注入のお話 #JetpackCompose - Qiita
- DIライブラリ「Hilt」のセットアップ&使い方(Kotlin) #Android - Qiita
1つしか ViewModel がないとありがたみがわからないようだ。
複数 ViewModel があって、それぞれがまた Repository やらなんやらを使うようなところを参照するようなところが便利になるらしい。
それ以外にも、よく使うようなパラメータについては組み込み済みのアノテーションがあって楽して使えるようだ。
そう、今回は Context
を上から直接もらうように書かなくても何とかなるんじゃないの、というのが目的だったのだ。
Context をどこかからもらう
というわけで、苦労した Hilt で ViewModel をパラメータ付きにする箇所を、パラメータをもらわないで済むように変更する。
(“ApplicationContext” というユーザがいたので、アノテーション @ApplicationContext
を git コメントに書いたらその人へのリンクになったじゃないか。。。)
ViewModel
の定義はこうなった。
@HiltViewModel
だけ@Inject
Context
のパラメータに@ApplicationContext
を付けるScanViewModelFactory
は削除- もらった
Context
でBluetoothLeScanner
のインスタンスをもらってスキャンできるようにしておく
@HiltViewModel
class ScanViewModel @Inject constructor(
@ApplicationContext context: Context
): ViewModel() {
呼び出したのは ScanScreen
側。
context
を引数にせず、単にhiltViewModel()
でインスタンスを作る(型はScanViewModel
)
これだけである。
ブレークポイントで止めると context
は MainApplication
になっていたので指示通りだ。
ちゃんとスキャンも動作したので Context
に問題はないだろう。
ScanViewModel
のコンストラクタ引数に 1つ Int
を追加したところ、前の assisted injection に対応せずパラメータを追加したときと同じエラーが出た。
Context
は全然出てこなかった。
そういうものなんだ。
DI再履修
なんか、これくらいだったらコンストラクタに Context
を渡しても大して差がないような気がしてきた。
パラメータが増えてきたら面倒かもしれないが、たぶんそういう目的じゃないな。
- Android での依存関係インジェクション - Android Developers
- メリット
- コードを再利用できる
- リファクタリングが容易になる
- テストが容易になる
- メリット
DI の話と、DI ライブラリの話を混ぜ込んで考えてしまったようだ。
コンストラクタで「これ使ってね」と依存しているインスタンスを投げ込むのも DI なのだった。
あとは、そういうしくみが手動なのか自動なのかという話なのだな。
もうちょっと学習が必要なので、今日はここまで。
外部からインスタンスを受け入れるようにすれば DI というわけでもなさそうだ。