web-dev-qa-db-ja.com

Dagger 2 + Kotlin + ViewModelを使用してViewModelを注入する

class SlideshowViewModel : ViewModel() {

@Inject lateinit var mediaItemRepository : MediaItemRepository

fun init() {
    What goes here?
}

アプリをテストしやすくするために、Dagger2を学習しようとしています。問題は、私はすでにKotlinを統合していて、Androidアーキテクチャーコンポーネントに取り組んでいます。コンストラクターインジェクションが望ましいと理解していますが、ViewModelではこれは不可能です。代わりに、注入するためにlateinitを使用できますが、注入方法を理解するのに途方に暮れています。

ComponentSlideshowViewModelを作成して、それを注入する必要がありますか?または、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;
    }
14
easycheese

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]

以下は、必要なすべての変更を含むサンプルコミットです。 ビューモデルのコンストラクターインジェクションのサポート

10
Igor Bubelov

学習中に作成したリポジトリを参照してください 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, { ... })

    }
4
Vaughn Armada

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)
      }

    }
3
azizbekian

いいえ。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)
    }
3
johnny_crq

以下のコードで試してください:

@Provides
@Singleton
fun provideRepository(): Repository {
    return Repository(DataSource())
}
0
Harsh Agrawal

私は、これをより簡単でよりクリーンにするライブラリを作成しました。マルチバインディングや工場の定型文は必要ありませんが、実行時に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
        })
    }
}
0
Radu Topor