class SlideshowViewModel : ViewModel() {
@Inject lateinit var mediaItemRepository : MediaItemRepository
fun init() {
What goes here?
}
アプリをテストしやすくするために、Dagger2を学習しようとしています。問題は、私はすでにKotlinを統合していて、Androidアーキテクチャーコンポーネントに取り組んでいます。コンストラクターインジェクションが望ましいと理解していますが、ViewModel
ではこれは不可能です。代わりに、注入するためにlateinit
を使用できますが、注入方法を理解するのに途方に暮れています。
Component
のSlideshowViewModel
を作成して、それを注入する必要がありますか?または、Application
コンポーネントを使用しますか?
グラドル:
apply plugin: 'com.Android.application'
apply plugin: 'kotlin-Android'
kapt {
generateStubs = true
}
dependencies {
compile "com.google.dagger:dagger:2.8"
annotationProcessor "com.google.dagger:dagger-compiler:2.8"
provided 'javax.annotation:jsr250-api:1.0'
compile 'javax.inject:javax.inject:1'
}
アプリケーションコンポーネント
@ApplicationScope
@Component (modules = PersistenceModule.class)
public interface ApplicationComponent {
void injectBaseApplication(BaseApplication baseApplication);
}
BaseApplication
private static ApplicationComponent component;
@Override
public void onCreate() {
super.onCreate();
component = DaggerApplicationComponent
.builder()
.contextModule(new ContextModule(this))
.build();
component.injectBaseApplication(this);
}
public static ApplicationComponent getComponent() {
return component;
}
ViewModelのコンストラクター注入を有効にすることができます。 Googleサンプル をチェックして、Javaでの実行方法を確認できます。 (更新:プロジェクトをKotlinに変換したため、このURLは機能しなくなりました)
Kotlinで同様のことを行う方法は次のとおりです。
ViewModelKeyアノテーションを追加します。
import Android.Arch.lifecycle.ViewModel
import Java.lang.annotation.Documented
import Java.lang.annotation.ElementType
import Java.lang.annotation.Retention
import Java.lang.annotation.RetentionPolicy
import Java.lang.annotation.Target
import dagger.MapKey
import kotlin.reflect.KClass
@Suppress("DEPRECATED_Java_ANNOTATION")
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@MapKey
internal annotation class ViewModelKey(val value: KClass<out ViewModel>)
ViewModelFactoryを追加します。
import Android.Arch.lifecycle.ViewModel
import Android.Arch.lifecycle.ViewModelProvider
import javax.inject.Inject
import javax.inject.Provider
import javax.inject.Singleton
@Singleton
class ViewModelFactory @Inject constructor(
private val creators: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
var creator: Provider<out ViewModel>? = creators[modelClass]
if (creator == null) {
for ((key, value) in creators) {
if (modelClass.isAssignableFrom(key)) {
creator = value
break
}
}
}
if (creator == null) {
throw IllegalArgumentException("unknown model class " + modelClass)
}
try {
return creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
ViewModelModuleを追加します。
import dagger.Module
import Android.Arch.lifecycle.ViewModel
import dagger.multibindings.IntoMap
import dagger.Binds
import Android.Arch.lifecycle.ViewModelProvider
import com.bubelov.coins.ui.viewmodel.EditPlaceViewModel
@Module
abstract class ViewModelModule {
@Binds
@IntoMap
@ViewModelKey(EditPlaceViewModel::class) // PROVIDE YOUR OWN MODELS HERE
internal abstract fun bindEditPlaceViewModel(editPlaceViewModel: EditPlaceViewModel): ViewModel
@Binds
internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
}
コンポーネントにViewModelModuleを登録します
アクティビティにViewModelProvider.Factoryを挿入します。
@Inject lateinit var modelFactory: ViewModelProvider.Factory
private lateinit var model: EditPlaceViewModel
ModelFactoryを各ViewModelProviders.ofメソッドに渡します。
model = ViewModelProviders.of(this, modelFactory)[EditPlaceViewModel::class.Java]
以下は、必要なすべての変更を含むサンプルコミットです。 ビューモデルのコンストラクターインジェクションのサポート
学習中に作成したリポジトリを参照してください dagger + kotlin
基本的に、UIレイヤーへのViewModelFactoryインスタンスが必要です。それを使用して、ビューモデルを作成します。
@AppScope
class ViewModelFactory
@Inject
constructor(private val creators: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>)
: ViewModelProvider.Factory {
@SuppressWarnings("Unchecked")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
var creator = creators[modelClass]
if (creator == null) {
for (entry in creators) {
if (modelClass.isAssignableFrom(entry.key)) {
creator = entry.value
break
}
}
}
if (creator == null) throw IllegalArgumentException("Unknown model class" + modelClass)
try {
return creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
ViewModelModuleは次のようになります(すべてのビューモデルを格納する場所です)。
@Module
abstract class ViewModelModule {
@AppScope
@Binds
@IntoMap
@ViewModelKey(YourViewModel::class)
abstract fun bindsYourViewModel(yourViewModel: YourViewModel): ViewModel
// Factory
@AppScope
@Binds abstract fun bindViewModelFactory(vmFactory: ViewModelFactory): ViewModelProvider.Factory
}
次に短剣マップキーを作成します
@Documented
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
internal annotation class ViewModelKey(val value: KClass<out ViewModel>)
次に、UIレイヤーでファクトリをインジェクトし、ViewModelProvidersを使用してビューモデルをインスタンス化します
class YourActivity : BaseActivity() {
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
lateinit var yourViewModel: YourViewModel
override fun onCreate(savedInstanceState: Bundle?) {
...
...
(application as App).component.inject(this)
}
override fun onStart() {
super.onStart()
yourViewModel = ViewModelProviders.of(this, viewModelFactory).get(YourViewModel::class.Java)
// you can now use your viewmodels properties and methods
yourViewModel.methodName()
yourViewModel.list.observe(this, { ... })
}
Daggerによって挿入できるRepository
クラスと、そのように定義されたMyViewModel
に依存するRepository
クラスがあると仮定します。
class Repository @Inject constructor() {
...
}
class MyViewModel @Inject constructor(private val repository: Repository) : ViewModel() {
...
}
これでViewModelProvider.Factory
実装を作成できます:
class MyViewModelFactory @Inject constructor(private val myViewModelProvider: Provider<MyViewModel>) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return myViewModelProvider.get() as T
}
}
ダガーの設定はそれほど複雑に見えません:
@Component(modules = [MyModule::class])
interface MyComponent {
fun inject(activity: MainActivity)
}
@Module
abstract class MyModule {
@Binds
abstract fun bindsViewModelFactory(factory: MyViewModelFactory): ViewModelProvider.Factory
}
以下は、実際の注入が行われるアクティビティクラス(フラグメントの場合もあります)です。
class MainActivity : AppCompatActivity() {
@Inject
lateinit var factory: ViewModelProvider.Factory
lateinit var viewModel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// retrieve the component from application class
val component = MyApplication.getComponent()
component.inject(this)
viewModel = ViewModelProviders.of(this, factory).get(MyViewModel::class.Java)
}
}
いいえ。viewModelを宣言(使用)するコンポーネントを作成します。通常はアクティビティ/フラグメントです。 viewModelには依存関係(mediaitemrepository)があるため、ファクトリが必要です。このようなもの:
class MainViewModelFactory (
val repository: IExerciseRepository): ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(p0: Class<T>?): T {
return MainViewModel(repository) as T
}
}
次に短剣部分(アクティビティモジュール)
@Provides
@ActivityScope
fun providesViewModelFactory(
exerciseRepos: IExerciseRepository
) = MainViewModelFactory(exerciseRepos)
@Provides
@ActivityScope
fun provideViewModel(
viewModelFactory: MainViewModelFactory
): MainViewModel {
return ViewModelProviders
.of(act, viewModelFactory)
.get(MainViewModel::class.Java)
}
以下のコードで試してください:
@Provides
@Singleton
fun provideRepository(): Repository {
return Repository(DataSource())
}
私は、これをより簡単でよりクリーンにするライブラリを作成しました。マルチバインディングや工場の定型文は必要ありませんが、実行時にViewModel
をさらにパラメータ化する機能も提供します。 https://github.com/radutopor/ViewModelFactory
@ViewModelFactory
class UserViewModel(@Provided repository: Repository, userId: Int) : ViewModel() {
val greeting = MutableLiveData<String>()
init {
val user = repository.getUser(userId)
greeting.value = "Hello, $user.name"
}
}
ビューで:
class UserActivity : AppCompatActivity() {
@Inject
lateinit var userViewModelFactory2: UserViewModelFactory2
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user)
appComponent.inject(this)
val userId = intent.getIntExtra("USER_ID", -1)
val viewModel = ViewModelProviders.of(this, userViewModelFactory2.create(userId))
.get(UserViewModel::class.Java)
viewModel.greeting.observe(this, Observer { greetingText ->
greetingTextView.text = greetingText
})
}
}