使用Dagger 2将属性注入到ViewModel中

时间:2019-01-24 13:36:14

标签: android dagger-2

我尝试学习如何使用Dagger2。请提供以下例外帮助:

  

例外:    UninitializedPropertyAccessException:lateinit属性旅行尚未   已初始化

MainActivityViewModel:

class MainActivityViewModel : ViewModel() {
    private lateinit var tripsLiveData: MutableLiveData<List<Trip>>

    @Inject
    lateinit var trips : List<Trip>

    fun getTrips() : LiveData<List<Trip>> {
        if (!::tripsLiveData.isInitialized){
            tripsLiveData = MutableLiveData()
            tripsLiveData.value = trips
        }
        return tripsLiveData
    }
}

TripModule:

@Module
class TripModule{
    @Provides
    fun provideTrips(): List<Trip> {

        var list = ArrayList<Trip>()
        list.add(Trip(100,10))
        list.add(Trip(200,20))
        return list
    }
}

AppComponent:

@Singleton
@Component(modules = [
    AndroidSupportInjectionModule::class,
    ActivityBuilder::class,
    TripModule::class])
interface AppComponent{
    @Component.Builder
    interface Builder {
        @BindsInstance
        fun application(application: Application): Builder

        fun build(): AppComponent
    }

    fun inject(app: MyApplication)
}

MainActivity:

class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var tripsAdapter: TripsAdapter

    override fun onCreate(savedInstanceState: Bundle?) {

        // Inject external dependencies
        AndroidInjection.inject(this)

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        setupRecyclerView();
        setUpViewModel();
    }

    private fun setupRecyclerView() {
        recycler_view.apply {
            layoutManager = LinearLayoutManager(context)
            adapter = tripsAdapter
        }
    }

    private fun setUpViewModel(){
        val model = ViewModelProviders.of(this).get(MainActivityViewModel::class.java)
        model.getTrips().observe(this, Observer { tripsAdapter.trips = it!! })
    }
}

2 个答案:

答案 0 :(得分:1)

如果要让视图模型成为dagger图的一部分,则需要做几件事-使用dagger的多重绑定(只需一次,对于较新的视图模型,会更容易)。您将创建一个新的viewmodel工厂,该工厂将负责实例化viewmodel。该工厂将成为匕首图的一部分,因此将引用通过匕首提供的任何内容。然后,您可以通过@Inject constructor(anyParameterFromDagger: Param)@Inject lateinit var someParam: Param在视图模型主体中进行构造函数注入。

1)为视图模型类创建限定符

@MustBeDocumented
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)

2)创建一个viewmodel工厂,该工厂从dagger的多重绑定中获取值

@Singleton
class DaggerViewModelFactory @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)
        }
    }
}

3)具有dagger模块,该模块将提供工厂(从第2点开始),然后提供您的视图模型

abstract class YourDaggerModuleWhichThenNeedToBePartOfYourGraphAsIncluded {

    @Binds
    abstract fun bindViewModelFactory(factory: DaggerViewModelFactory): ViewModelProvider.Factory // this needs to be only one for whole app (therefore marked as `@Singleton`)

    @Binds
    @IntoMap
    @ViewModelKey(MainActivityViewModel::class)
    abstract fun bindMainActivityViewModel(vm: MainActivityViewModel): ViewModel // for every viewmodel you have in your app, you need to bind them to dagger
}

4)在活动中,当您获取视图模型时,您需要使用匕首中的工厂:(在以下代码中,位置更改为标记为// TODO的地方)

class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var tripsAdapter: TripsAdapter

    @Inject
    lateinit var viewModelFactory: ViewModelProvider.Factory // TODO this was added to the activity

    override fun onCreate(savedInstanceState: Bundle?) {

        // Inject external dependencies
        AndroidInjection.inject(this)

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        setupRecyclerView();
        setUpViewModel();
    }

    private fun setupRecyclerView() {
        recycler_view.apply {
            layoutManager = LinearLayoutManager(context)
            adapter = tripsAdapter
        }
    }

    private fun setUpViewModel(){
        val model = ViewModelProviders.of(this, viewModelFactory)[MainActivityViewModel::class.java] // TODO this was changed

        model.getTrips().observe(this, Observer { tripsAdapter.trips = it!! })
    }
}

我没有提供将模块添加到dagger组件的代码,因为我希望这是您已经做过的事情。

您可以阅读有关此内容的更多信息,例如在此medium article中(我不是本文的作者):

答案 1 :(得分:0)

@Provides
fun provideTrips(): List<Trip> {

从某种意义上说,我有点警惕,我怀疑直接为您提供此服务确实是Dagger的工作,但我只是认为这是理所当然的事情,而现在忽略它。


您的代码应为:

class MainActivityViewModel @Inject constructor(
    trips: List<Trip>
): ViewModel() {
    private val tripsLiveData: MutableLiveData<List<Trip>> = MutableLiveData()

    init {
        tripsLiveData.setValue(trips)
    }

    fun getTrips() : LiveData<List<Trip>> = tripsLiveData
}

@Module
class TripModule{
    @Provides
    // @Singleton // <-- possibly should be here?
    fun provideTrips(): List<Trip> = listOf(
        Trip(100,10),
        Trip(200,20)
    )
}

@Singleton
@Component(modules = [
    AndroidSupportInjectionModule::class,
    ActivityBuilder::class,
    TripModule::class
])
interface AppComponent{
    @Component.Builder
    interface Builder {
        @BindsInstance
        fun application(application: Application): Builder

        fun build(): AppComponent
    }

    fun inject(app: MyApplication)
}

class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var viewModelProvider: Provider<MainActivityViewModel>

    // this is specifically not marked with `@Inject`
    lateinit var viewModel: MainActivityViewModel

    private val tripsAdapter = TripsAdapter()

    override fun onCreate(savedInstanceState: Bundle?) {
        // Inject external dependencies
        AndroidInjection.inject(this)

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        setupRecyclerView();
        setUpViewModel();
    }

    private fun setupRecyclerView() {
        recycler_view.apply {
            layoutManager = LinearLayoutManager(context)
            adapter = tripsAdapter
        }
    }

    private fun setUpViewModel(){
        viewModel = ViewModelProviders.of(this, object: ViewModelProvider.Factory {
            override fun <T : ViewModel?> create(modelClass: Class<T>): T {
                if(modelClass == MainActivityViewModel::class.java) {
                    @Suppress("UNCHECKED_CAST")
                    return viewModelProvider.get() as T
                }
                throw IllegalArgumentException("Unexpected argument: $modelClass")
            }
        }).get(MainActivityViewModel::class.java)

        viewModel.getTrips().observe(this, Observer { 
            val trips = it ?: return@observe
            tripsAdapter.trips = trips 
        })
    }
}

同样,您应该使用构造函数注入,仅在实际需要时使用Provider<T>从Dagger获取ViewModel,否则通过ViewModelProviders.Factory对其进行初始化,以便实际上从Dagger获取ViewModel