はじめに
daggerバージョンが2.10になったときに、dagger.androidがリリースされたようで、いままでと少し書き方が変わっていたので、メモを書きました。
基本的に下記ページを参考にしています。
https://google.github.io/dagger/android.html
またサンプルコードは、Dagger 2.12 時点によるものです。
dagger.android
MainActivityが依存しているインスタンスを注入できるようにしてみます。
AndroidInjectionModule(あるいは、Support componentを使う場合は AndroidSupportInjectionModule)をアプリケーションコンポーネントに指定してください。
@Singleton @Component(modules = arrayOf( AndroidSupportInjectionModule::class ) ) interface AppComponent : AndroidInjector<App> { @Component.Builder interface Builder { @BindsInstance fun application(application: App): Builder fun build():AppComponent } }AndroidInjector.Builder<YourActivity>を継承している@Subcomponent.Builderを持つAndroidInjector<YourActivity>を実装する@Subcomponentを作成します。@Subcomponent interface MainActivitySubComponent:AndroidInjector<MainActivity> { @Subcomponent.Builder abstract class Builder : AndroidInjector.Builder<MainActivity>() }subcomponentを定義したら、Subcomponent.Builderをバインドするモジュールを定義(ここでは
MainActivityModule)し、@Module(subcomponents = arrayOf(MainActivityModule.MainActivitySubComponent::class)) abstract class MainActivityModule { @Binds @IntoMap @ActivityKey(MainActivity::class) internal abstract fun bindAndroidInjectorFactory( builder: MainActivityModule.MainActivitySubComponent.Builder): AndroidInjector.Factory<out Activity> }そのモジュール(
MainActivityModule)をアプリケーションコンポーネント(ここではAppComponent)に追加します。@Singleton @Component(modules = arrayOf( AndroidSupportInjectionModule::class, MainActivityModule::class ) ) interface AppComponent : AndroidInjector<App> { // 省略 }Appplicationクラスを継承したクラス(ここではApp)でHasActivityInjectorインターフェースを実装し、DispatchingAndroidInjector<Activity>を@Injectてし、activityInjector()メソッドでそれを返します。と、書かれていますが、daggerがBaseクラスを用意してくれていて、
DaggerApplicationクラスを継承して、activityInjector()メソッドを実装すると良いみたいです。つまりopen class App : DaggerApplication() { override fun applicationInjector(): AndroidInjector<out DaggerApplication> { return DaggerAppComponent.builder().application(this).build() } }とします。
最後にAcitivyのonCreateで
super.onCreate()の前にAndroidInjection.inject(this)を呼んでください。とあるのですが、これもDaggerが用意してくれているベースクラス
DaggerAppCompatActivityを継承すれば、AndroidInjection.inject(this)を書くなくてもDaggerAppCompatActivityでやってくれています。
これで基本は完了です。
ViewModel(databinding)のインスタンスを注入できるようにしてみます。
ViewModelクラスを作成します。
class MainViewModel : BaseObservable() {
fun onClickButton(view: View) {
Log.d("MainViewModel", "onclick")
}
}
このViewModelは下記のactivity_main.xmlにバインドしたいため、次のようにlayoutタグを入れています。
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
>
<data>
<variable
name="viewModel"
type="net.kwmt27.codesearch.presentation.viewmodel.MainViewModel"
/>
</data>
<LinearLayout>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="button"
android:onClick="@{viewModel::onClickButton}"
/>
</LinearLayout>
</layout>
なお、ActivityScopeアノテーションは下記のような感じで作成しています。
@Scope
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class ActivityScope
このViewModelをActivityでInjectできるようにするには、コンストラクタに@Injectアノテーションをつけ、Scopeアノテーションを付けます。
@ActivityScope
class MainViewModel @Inject constructor() : BaseObservable() {
fun onClickButton(view: View) {
Log.d("MainViewModel", "onclick")
}
}
このような感じになると思います。そしてInjectしたいActivityのフィールド宣言で@Injectアノテーションを付けます。MainActivityは次のようになります。
class MainActivity : DaggerAppCompatActivity() {
@Inject
lateinit var viewModel: MainViewModel
}
あとは通常どおりにバインディングするだけです。
class MainActivity : DaggerAppCompatActivity() {
@Inject
lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
binding.viewModel = viewModel
}
}
FragmentでInject可能にする
ほとんどActivityと同じですが、ちょっと書いておきます。
MainFragmentクラスを作成します。
@FragmentScope
class MainFragment : DaggerFragment() {
companion object Factory {
val TAG = MainFragment::class.simpleName!!
fun newInstance(): MainFragment = MainFragment()
}
@Inject
lateinit var viewModel: MainFragmentViewModel
private lateinit var binding:FragmentMainBinding
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
binding = FragmentMainBinding.inflate(inflater, container, false)
binding.viewModel = viewModel
return binding.root
}
}
このとき、レイアウトにfragment_main.xmlを、ViewModelとしてMainViewModel.ktを、Fragmentスコープ用にFragmentScope.ktを作成しています。それぞれ次のようになっています。
fragment_main.xml
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" > <data> <variable name="viewModel" type="net.kwmt27.codesearch.presentation.viewmodel.MainFragmentViewModel" /> </data> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:onClick="@{viewModel::onClickButton}" android:text="Fragment Button" /> </RelativeLayout> </layout>MainFragmentViewModel.kt
@FragmentScope class MainFragmentViewModel @Inject constructor() : BaseObservable() { fun onClickButton(view: View) { Log.d("MainFragmentViewModel", "onclick") } }FragmentScope.kt
@Scope @Retention(AnnotationRetention.RUNTIME) @MustBeDocumented annotation class FragmentScope
MainActivityModule.ktと同様にして、MainFragmentModule.ktを作成します。
@Module(subcomponents = arrayOf(MainFragmentModule.MainFragmentSubComponent::class))
abstract class MainFragmentModule {
@Binds
@IntoMap
@FragmentKey(MainFragment::class)
internal abstract fun bindMainFragmentAndroidInjectorFactory(
builder: MainFragmentModule.MainFragmentSubComponent.Builder): AndroidInjector.Factory<out Fragment>
@FragmentScope
@Subcomponent
interface MainFragmentSubComponent: AndroidInjector<MainFragment> {
@Subcomponent.Builder
abstract class Builder : AndroidInjector.Builder<MainFragment>()
}
}
これをAppComponent.ktに差し込むだけです。
@Singleton
@Component(modules = arrayOf(
AndroidSupportInjectionModule::class,
AppModule::class,
MainActivityModule::class,
MainFragmentModule::class // ←ここに入れた
)
)
interface AppComponent : AndroidInjector<App> {
@Component.Builder
interface Builder {
@BindsInstance
fun application(application: App): Builder
fun build():AppComponent
}
}
FragmentをInjectableにする方法は以上です。
最後に
ActivityやFragmentが増えたときに、AppComponentのmodulesが太ってくる問題がありますが、これはまた後のほど考えたいと思います。どこかでincludesみたいなのを見たので、もしかしたら
ActivityModuelsやFragmentModulesみたいなのを作ってそこにまとめて書けるのかもしれません。