Dagger Hiltを使ってJavaで書かれたFragmentにViewModelをDIする
/ 7 min read
Updated:Table of Contents
はじめに
皆さん、Dagger Hilt使ってますか?私は存在自体は知っていたのですが、業務で MVVM 化 + Kotlin 化をするときに、Context周りの DI を楽にしたいなと思い、ようやく勉強を始めました。Dagger Hilt を調べるとよくフル Kotlin のサンプルが多いような気がします。ですが、私が実際に業務に携わっているコードは、ほとんど Java なのであまり参考にならないこともあります。例えば、Kotlin だと ktx が使えますが Java では使えなかったり…
そんな中、Java で書かれた Fragment に対してどうやって ViewModel を DI するか調べたので記事として公開しておきます。あと、ApplicationContextを UseCase 層に DI する方法についても書いています。
実装
今回サンプルに使ったコードは、このリポジトリで公開しています。今回使用した Hilt のバージョンは以下の通りです。また既に Hilt のセットアップも終わっていて、Application クラスや Activity や Fragment にもアノテーションを設定しているとします。Hilt のセットアップはこちらを参照。
dependencies { implementation 'com.google.dagger:hilt-android:2.42' kapt 'com.google.dagger:hilt-compiler:2.42'}早速ですが、Java の Fragment に ViewModel を DI する方法は以下の通りです。
@AndroidEntryPointpublic class SampleFragment1 extends Fragment {
SampleViewModel sampleViewModel; // (色々省略) @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
sampleViewModel = new ViewModelProvider(this).get(SampleViewModel.class); } // (色々省略)}最初は Hilt を分かってなさすぎて、Fragment で ViewModel に@Injectをつけて DI しようとしていました…何かを見てこうやっていたのですが、何を見たのか…Google 公式の Hilt の説明では、きちんとViewModelProviderを使ったコードもありました。しっかり公式を見ようと心に誓いました。
@AndroidEntryPointpublic class SampleFragment1 extends Fragment {
// よくわからないコード @Inject SampleViewModel sampleViewModel; // (色々省略) @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
} // (色々省略)}次に UseCase 層にApplicationContextを DI する方法です。今回は、@Providesではなく、@Bindsを使う方法でやっています(正直まだ自分の中で使い分けが定まっていません…)。
interface SampleUseCase { fun isGranted(): Boolean}
class SampleUseCaseImpl @Inject constructor( // あらかじめ@ApplicationContextを準備してくれてるのでそれを使う @ApplicationContext private val applicationContext: Context): SampleUseCase {
@RequiresApi(33) override fun isGranted(): Boolean { return (ContextCompat.checkSelfPermission( applicationContext, Manifest.permission.POST_NOTIFICATIONS, ) == PackageManager.PERMISSION_GRANTED ) }}
// 今回はViewModelでだけ使いたかったのでViewModelComponentにします// けど他の画面でも使うことを考えるとSingtonComponentでも良い気がしています@Module@InstallIn(ViewModelComponent::class)abstract class SampleModule {
@Binds @ViewModelScoped abstract fun bindSampleUseCase( sampleUseCaseImpl: SampleUseCaseImpl ): SampleUseCase}ApplicationContextの場合は、既に Hilt 側で@ApplicationContextを準備されているのでこれを使います。アプリのプッシュ通知の権限許可の処理を行いたいのでActivityContextではなく、ApplicationContextを使うようにしています。
@HiltViewModelclass SampleViewModel @Inject constructor( private val useCase: SampleUseCase ) : ViewModel() {
fun isGranted(): Boolean { return useCase.isGranted() }
}ViewModel では、インターフェイスで定義したSampleUseCaseを DI します。
最後に
Hilt では、最初の取っ掛かりが、かなり難しいですが慣れると作業になってきます(多分)。これは Rx や Combine と同じだなと思いました。未だに Rx や Combine のオペレータはよく使うのしか覚えてないし、Hot と Cold の記憶も薄らいで来たし…そいうところも含めて同じだなとなりました。ただ慣れると楽だ〜とはなります。Context の DI とかは本当かなり楽だなと実感しました。普通だとthisやらgetContext、getApplicationContextをいい感じに DI しないといけないところをアノテーションで定義するだけでいいのはすごいです(Hilt 初心者なので的外れのこと言ってるかも…)。
そもそもなんで Kotlin ではなく、Java の Fragment に DI したかったのかというと、業務の Fragment が大規模過ぎ(ビジネスロジックとかがかなり入り込んでいるため)て、Fragment を Kotlin 化するのが大変なためです。一旦ビジネスロジックを MVVM 化 + Kotlin 化する方針で進めています。そして、Java の Fragment に DI して少しずつ Fragment の Java コードを剥がしているためです。剥がした方は Coroutines などを使っていて、もちろん ViewModel も Kotlin で書いています。Coroutines は Java では使えないのでlifecycle-livedata-ktxを使ってasLiveDataでLiveDataとして公開し、Fragment の Kotlin 化を少しでも楽にしようと色々やっています。そんな背景があってこの記事のような実装を調べていたのでした。
今後は Hilt の理解を深めるためにも、@Providesと@Bindsの使い分けなども勉強していかねばという気持ちです。