Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use one ViewModelFactory to provide for all ViewModels with Dagger [closed]

How can I implement a generic ViewModel Factory to provide for all of my project ViewModels? To be clear, my ViewModels have dependencies (as constructor parameters)

like image 598
David Avatar asked Mar 04 '23 17:03

David


1 Answers

Well, there is one called GithubBrowser and but it is not a tutorial, it is a project. You should know dagger for android to do that. Or, you may check the code below:

@Singleton
class DaggerViewModelFactory @Inject constructor(
    private val creators: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        val creator = creators[modelClass] ?: creators.entries.firstOrNull {
            modelClass.isAssignableFrom(it.key)
        }?.value ?: throw IllegalArgumentException("unknown model class $modelClass")
        try {
            @Suppress("UNCHECKED_CAST")
            return creator.get() as T
        } catch (e: Exception) {
            throw RuntimeException(e)
        }
    }
}

This part will create a "generic" viewmodel for your entire app. In that way, the ViewModel is created with arguments asigned. After that you need to implement the factory module in your Singleton modules, and including it on the component.

@Component(
    modules = [... ViewModelModule::class]
)
interface AppCompoenent{}

Now the fun part:

    @Suppress("unused")
    @Module
    abstract class ViewModelModule {
        @Binds
        @IntoMap
        @ViewModelKey(MyViewModel::class)
        abstract fun bindsMyViewModel(viewModel: MyViewModel): ViewModel

    @Binds
    abstract fun bindsViewModelFactory(factory: DaggerViewModelFactory): ViewModelProvider.Factory
}

Since dagger support multibinding you are free to bind as may ViewModels as you want.

The ViewModelKey:

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

You are basically puting values in a hashmap. These values are your ViewModel.

Hit build! Done.After that you just inject a ViewModelProvider.Facory in your fragment. Than in your ViewModel you can do:

class MyViewModel @Inject constructor(
    private val dependency: YourDependency
) : ViewModel() {}

To make clear what you requested on the comments. First of all, there is no specific need to know what is happening inside the DaggerViewModelFactory, although I don't recommend learning this way because I am a strong fan of "Always knowing what's happening". FYI the DaggerViewModelFactory is just a class that accepts a Map with every class that extends ViewModel as a key, and that class dependencies as a value. When using Provider<T> Dagger knows how to find those dependencies but doesn't yet bring them to you, until you have called the provider.get(). Think of it as just a lazy initialization.

Now check the modelClass.isAssignableFrom(it.key). It just checks if that class really extends ViewModel.

As for your second question, is important to understand the first part. Since Dagger supports multibinding, that means that you can provide dependencies using a Map<Key, Value>. For example, a Map<HomeViewModel, Provider<ViewModel>> will basically tell dagger that give me HomeViewModel's dependencies. Dagger is going to say: How to know which are HomeViewModel dependencies? And you respond: I already have defined a Key for that and it's the HomeViewModel class itself. So you just create an annotation, combine it with @Binds and @IntoMap, and on the background Dagger will just perform a map.put(HomeViewModel::class, AndDependencies).

like image 89
coroutineDispatcher Avatar answered Apr 30 '23 13:04

coroutineDispatcher