anddev
anddev

Reputation: 206

New Architecture with Dagger and Kotlin

I have a problem with New Architecture components in Kotlin, when I create ViewModel component in recomended way (in onCreate() method) the result is as suposed:

Here is the way i create this

override fun onCreate(savedInstanceState: Bundle?) {
    AndroidInjection.inject(this)
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_list)

    val arrayMap = ArrayMap<Class<out ViewModel>, ViewModel>()
    arrayMap.put(ListViewModel::class.java, ListViewModel(webApi, repoDao))
    val factory = ViewModelFactory(arrayMap)
    listViewModel = ViewModelProviders.of(this, factory).get(ListViewModel::class.java)

    listViewModel.items.observe({ this.lifecycle }) {
        Toast.makeText(this, it?.joinToString { it + " " } ?: "null", Toast.LENGTH_SHORT).show()
    }

But when I have used Dagger for inject ListViewModel I got new instance of ListViewModel every time Activity was recreated. Here is a code of Dagger ListActivityModel.

@Module @ListActivityScopeclass ListActivityModule {
@Provides
@ListActivityScope
fun provideListViewModel(webApi: WebApi, repoDao: RepoDao, listActivity: ListActivity): ListViewModel {
    val arrayMap = ArrayMap<Class<out ViewModel>, ViewModel>()
    arrayMap.put(ListViewModel::class.java, ListViewModel(webApi, repoDao))
    val factory = ViewModelFactory(arrayMap)
    val result =  ViewModelProviders.of(listActivity, factory).get(ListViewModel::class.java)
    return result
}

} Then ListActivity onCreate() method looks like:

override fun onCreate(savedInstanceState: Bundle?) {
    AndroidInjection.inject(this)
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_list)
    listViewModel.items.observe({ this.lifecycle }) {
        Toast.makeText(this, it?.joinToString { it + " " } ?: "null", Toast.LENGTH_SHORT).show()
    }
}

And there is what I have notice after logging:

D/ListActivity: ---> onCreate() ListActivity: = [com.example.dom.app.new_arch.ListActivity@a0f2778]
D/ListActivity: ---> onCreate() listViewModel: = [com.example.dom.app.new_arch.ListViewModel@54a8e51]

//Activity orientation changes

E/ViewModelStores: Failed to save a ViewModel for com.example.dom.app.new_arch.ListActivity@a0f2778
D/ListActivity: ---> onCreate() ListActivity: = [com.example.dom.app.new_arch.ListActivity@6813433]
D/ListActivity: ---> onCreate() listViewModel: = [com.example.dom.app.new_arch.ListViewModel@55cf3f0]

The error I have received :

ViewModelStores: Failed to save a ViewModel for

comes from Android class HolderFragment with package android.arch.lifecycle.

There is something what I missed working with Dagger and new arch components?

Upvotes: 1

Views: 472

Answers (3)

John Michael Pirie
John Michael Pirie

Reputation: 141

The issue has to do with the order of dagger injection and activity creation. The view model implementation relies on a non-visual fragment for identity. By injecting the viewModelProvider before the activity has completed onCreate it is unable to complete this association.

Since super.onCreate does not likely depend on things you are injecting try injecting after the call to super.onCreate and you should be fine.

I had this exact same issue and solved it by this change in order.

Specifically from your code instead of:

override fun onCreate(savedInstanceState: Bundle?) {
    AndroidInjection.inject(this)
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_list)

go with:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    AndroidInjection.inject(this)
    setContentView(R.layout.activity_list)

JP

Upvotes: 1

Damia Fuentes
Damia Fuentes

Reputation: 5503

I don't use AndroidInjection.inject() because it creates a new Dagger component. I create an Dagger Component in the Application class and I use that component instance to call inject in all other places of the app. This way your singletons are initialized only one time.

Upvotes: 0

mcassiano
mcassiano

Reputation: 315

The way I do this is by only providing the ViewModelFactory using Dagger. Then it gets injected in the activity and you call ViewModelProviders.of(listActivity, factory).get(ListViewModel::class.java) from there. The reason your approach doesn't work is that AndroidInjection.inject() will create the ViewModel before onCreate, which leads to undefined behavior.

Also see: https://github.com/googlesamples/android-architecture-components/issues/202

Upvotes: 0

Related Questions