Skip to content

Instantly share code, notes, and snippets.

@tinmegali
Created July 14, 2017 17:08
Show Gist options
  • Save tinmegali/899aca5d240d3a92efd060f87831a32e to your computer and use it in GitHub Desktop.
Save tinmegali/899aca5d240d3a92efd060f87831a32e to your computer and use it in GitHub Desktop.
Injecting ViewModel with Dagger2 on Android using Kotlin
class App : Application(), HasActivityInjector {
@Inject lateinit var activityInjector: DispatchingAndroidInjector<Activity>
override fun activityInjector(): AndroidInjector<Activity> {
return activityInjector
}
override fun onCreate() {
super.onCreate()
DaggerAppComponent.builder()
.build()
.inject(this)
}
}
package com.tinmegali.daggerwithkotlin.dagger
import com.tinmegali.daggerwithkotlin.App
import com.tinmegali.daggerwithkotlin.dagger.activities.ActivitiesModule
import com.tinmegali.daggerwithkotlin.dagger.fragments.FragmentsModule
import com.tinmegali.daggerwithkotlin.dagger.viewModels.ViewModelModule
import dagger.Component
import dagger.android.AndroidInjectionModule
import javax.inject.Singleton
@Singleton
@Component(modules = arrayOf(
AndroidInjectionModule::class,
ViewModelModule::class
))
interface AppComponent {
fun inject(app: App)
}
class MainActivity : LifecycleActivity() {
//...
@Inject lateinit var viewModelFactory: ViewModelProvider.Factory
var mainViewModel: MainActivityModel? = null
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this) // Dagger
super.onCreate(savedInstanceState)
//..
mainViewModel = ViewModelProviders.of(this, viewModelFactory)
.get(MainActivityModel::class.java)
}
}
class MainActivityModel
@Inject constructor(
private var noteDAO: NoteDAO,
private var userDAO: UserDAO
): ViewModel(), AnkoLogger {
private var notesData: LiveData<List<Note>>? = null
fun getNotes( ): LiveData<List<Note>>? {
return notesData
}
fun subscribeToNotesDBChanges( callback: OnSync ) {
doAsync {
notesData = noteDAO.findAllNotedObservable()
info("Notes received.")
callback.notesReceived()
}
}
public interface OnSync {
fun notesReceived( )
}
}
package com.tinmegali.daggerwithkotlin.dagger.viewModels
import com.tinmegali.daggerwithkotlin.MainActivityModel
import dagger.Component
@Component( modules = arrayOf(
ViewModelModule::class
))
interface ViewModelComponent {
// inject your view models
fun inject( mainViewModel: MainActivityModel )
}
package com.tinmegali.oauth2restclient.dagger
import android.arch.lifecycle.ViewModel
import android.arch.lifecycle.ViewModelProvider
import javax.inject.Inject
import javax.inject.Provider
import javax.inject.Singleton
@Suppress("UNCHECKED_CAST")
@Singleton
class ViewModelFactory @Inject
constructor(
private val creators: Map<Class<out ViewModel>,
@JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {
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)
}
}
}
package com.tinmegali.daggerwithkotlin.dagger.viewModels
import android.arch.lifecycle.ViewModel
import java.lang.annotation.Documented
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import dagger.MapKey
import kotlin.reflect.KClass
@MustBeDocumented
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@Retention(RetentionPolicy.RUNTIME)
@MapKey
internal annotation class ViewModelKey(val value: KClass<out ViewModel>)
package com.tinmegali.daggerwithkotlin.dagger.viewModels
import android.arch.lifecycle.ViewModel
import android.arch.lifecycle.ViewModelProvider
import com.tinmegali.daggerwithkotlin.MainActivityModel
import com.tinmegali.oauth2restclient.dagger.ViewModelFactory
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoMap
@Module
abstract class ViewModelModule {
@Binds
@IntoMap
@ViewModelKey( MainActivityModel::class )
// Bind your View Model here
abstract fun bindMainViewModel( mainViewModel: MainActivityModel ): ViewModel
@Binds
abstract fun bindViewModelFactory( factory: ViewModelFactory):
ViewModelProvider.Factory
}
@patriksvrlo
Copy link

I tried to implement dagger according this sample but I got error:
Error:[dagger.android.AndroidInjector.inject(T)] java.util.Map<java.lang.Class<? extends android.arch.lifecycle.ViewModel>,? extends javax.inject.Provider<android.arch.lifecycle.ViewModel>> cannot be provided without an @Provides-annotated method.

Finally after added @JvmSuppressWildcards annotation in ViewModelFactory's constructor error dissapeared.

Thank you.

@shaohui10086
Copy link

What about the ViewModelComponent? Where the usage about it.

@LeoDroidCoder
Copy link

did you solve it?

@aaghan
Copy link

aaghan commented Apr 18, 2020

to solve the issue reported by @patriksvrlo,
modivy your class as follows:

@Module
abstract class ViewModelFactoryModule {
    @Binds
    internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory

    @Binds
    @IntoMap
    @ViewModelKey(BaseSetupViewModel::class)
    abstract fun bindsBaseSetupViewModel(baseSetupViewModel: BaseSetupViewModel): ViewModel

}
class BaseSetupViewModel:BaseViewModel { @Inject constructor() }

The reason for the issue is that it didn't find anything to bind at beginning, here you need to bind all the viewmodelkeys.

@aaghan
Copy link

aaghan commented Apr 18, 2020

I couldn't get through the runtime exception that is thrown by the code,
java.lang.IllegalArgumentException: Unknown ViewModel class
what is the reason for this?
Same thing works for fragment but not activity.

@goforbg
Copy link

goforbg commented Dec 13, 2020

Thank you needed to add @JvmSuppressWildcards

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment