How to create generic a ViewModelModule for many classes in Android? - android

I'm using Dagger2 to inject my ViewModel into my fragments. For each fragment I have ViewModelModule as below:
public abstract class GameViewModelModule {
#Binds
abstract ViewModelProvider.Factory bindAppViewModelFactory(AppViewModelFactory factory);
#Binds
#IntoMap
#ViewModelKey(GameViewModel.class)
abstract ViewModel provideGameViewModel(GameViewModel viewModel);
}
How can I create a generic class, instead of creating a separate ViewModelModule for every fragment?

Related

Putting ViewModel in AppComponent (Dagger2)

In Dagger 2, Is putting All ViewModels inside AppComponent is the right place.
Because I check this android google sample, All ViewModel scoped in the app component but I think the view model should be in it is view (Activity, Fragment) scope/subcomponent?
something like this:
#ContributesAndroidInjector(modules = [LoginActivityModule::class])
abstract fun contributeLoginActivity() : LoginActivity
-
#Module
abstract class LoginActivityModule {
#Binds
#IntoMap
#ViewModelKey(LoginViewModel::class)
abstract fun bindLoginViewModel(viewModel: LoginViewModel): ViewModel
}
To avoid memory-leak
I need explanation.

Injecting fragment with activity reference

I have a class DashboardActivity with two fragments, A and B, in a viewpager. Each Fragment has its own ViewModel, AViewModel and BViewModel, respectively.
Now currently I'm creating a Subcomponent for the DashboardActivity, which binds both viewmodel instances and injects them into the fragments via ContributesAndroidFragment.
How could I create a Subcomponent per Fragment, so that dependencies requiring an activity (in this case NavigationController) could still be fullfilled? i.e. How can I create this subcomponent by providing the activity the fragment is being attached to?
Consider that these fragments could be added elsewhere in the app so the attaching activity could be different in runtime.
This is how I've done it:
AppComponent:
#Singleton
#Component(
modules = [
ApplicationModule::class,
AndroidSupportInjectionModule::class,
NetModule::class,
...
ActivityBindings::class
]
)
interface ApplicationComponent : AndroidInjector<MyApp> {
#Component.Factory
interface Factory {
fun create(#BindsInstance application: Application): ApplicationComponent
}
}
ActivityBindings:
#Module
abstract class ActivityBindings {
#ContributesAndroidInjector(modules = [DashboardModule::class])
#PerActivity
abstract fun dashboardModule(): DashboardActivity
...
}
DashboardModule:
#Module(includes = [CommonActivityModule::class])
abstract class DashboardModule {
#Binds
abstract fun bindsActivity(activity: DashboardActivity): AppCompatActivity
#ContributesAndroidInjector
abstract fun contributeFragmentA(): FragmentA
#ContributesAndroidInjector
abstract fun contributesFragmentB(): FragmentB
#Binds
#IntoMap
#ViewModelKey(BViewModel::class)
#PerActivity
internal abstract fun bViewModel(bViewModel: BViewModel): ViewModel
#Binds
#IntoMap
#ViewModelKey(AViewModel::class)
#PerActivity
internal abstract fun aViewModel(aViewModel: AViewModel): ViewModel
}
CommonActivityModule:
#Module(includes = [ViewContainerModule::class, ViewModelBuilder::class])
abstract class CommonActivityModule {
#Binds
#ForActivity
abstract fun bindsContext(activity: AppCompatActivity): Context
#Module
companion object {
#Provides
#PerActivity
#JvmStatic
fun provideNavigationController(activity: AppCompatActivity) = NavigationController(activity)
}
}
PS. I need to be able to inject more fragments from fragmentA and fragmentB, so they should also act as AndroidInjectors

dagger2 and android: load module which injects viewmodel on a map

I've started using Dagger2, so there's still a lot to learn. I'm wondering if someone could point me on the right direction.
So, I've created a module for registering the view models used by my activities. It looks like this:
#Module
abstract class ViewModelModule {
#Binds
internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
#Binds
#IntoMap
#ViewModelKey(MainActivityViewModel::class)
internal abstract fun bindMainActivityViewModel(viewModel: MainActivityViewModel): ViewModel
#Binds
#IntoMap
#ViewModelKey(ShowDetailsViewModel::class)
abstract fun bindShowDetaislViewModel(viewModel: ShowDetailsViewModel): ViewModel
}
ViewModelKey is a simple helper annotation class which looks like this:
#Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
#Retention(AnnotationRetention.RUNTIME)
#MapKey
annotation class ViewModelKey (val value: KClass<out ViewModel>) {
}
The ViewModelModule is loaded by my main app component (used for creating the app):
#Singleton
#Component(
modules=[
AndroidSupportInjectionModule::class,
AppModule::class,
DatabaseModule::class,
NewsServiceModule::class,
JobBindingModule::class,
ViewModelModule::class,
PreferencesModule::class,
ActivityBindingModule::class
]
)
interface AppComponent: AndroidInjector<MyApp> {
#Component.Builder
abstract class Builder: AndroidInjector.Builder<MyApp>()
}
And here's the code for the ActivityBindingModule, responsible for setting up the subcomponents (in this case, activities used by my app):
#Module
abstract class ActivityBindingModule {
#ActivityScoped
#ContributesAndroidInjector()
internal abstract fun mainActivity(): MainActivity
#ActivityScoped
#ContributesAndroidInjector
internal abstract fun showDetailsActivity(): ShowDetailsActivity
}
Internally, each activity is instantiating the view model with code that looks like this (called from within the onCreate method):
//view model code
_viewModel = ViewModelProviders.of(this, viewModelFactory)[ShowDetailsViewModel::class.java]
And, as you'd expect, viewModelFactory is injected as field:
#Inject lateinit var viewModelFactory: ViewModelProvider.Factory
Both view models have external dependencies which are set up on the other modules referenced by the top app component.
And, for the sake of completeness, here's the code for my view model factory:
#Singleton
class ViewModelFactory #Inject constructor(private val viewModels: MutableMap<Class<out ViewModel>, Provider<ViewModel>>) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T
= viewModels[modelClass]?.get() as T
This code works, but it seems like it can be improved. After reading the docs, I'm under the impression that I could refactor my ViewModeModule so that it will simply instantiate my ViewModelFactory and move each of the view model declarations into separate module (so that each of them can be injected only in the "correct" activity).
In order to test this, I've started by moving the ShowDetailsViewModel into a new module which has only one entry:
#Module
internal abstract class DetailsModule {
#Binds
#IntoMap
#ViewModelKey(ShowDetailsViewModel::class)
abstract fun bindShowDetaislViewModel(viewModel: ShowDetailsViewModel): ViewModel
}
After that, the ViewModelModule looks like this:
#Module
abstract class ViewModelModule {
#Binds
internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
#Binds
#IntoMap
#ViewModelKey(MainActivityViewModel::class)
internal abstract fun bindMainActivityViewModel(viewModel: MainActivityViewModel): ViewModel
}
And I've updated the ActivityBindingModule so that in looks like this:
#Module
abstract class ActivityBindingModule {
#ActivityScoped
#ContributesAndroidInjector()
internal abstract fun mainActivity(): MainActivity
#ActivityScoped
#ContributesAndroidInjector(modules = [DetailsModule::class])
internal abstract fun showDetailsActivity(): ShowDetailsActivity
}
Notice that now I'm passing the DetailsModule (which instantiates the ShowDetailsViewModel) to the ContributeAndroidInjector annotation which is applied to the showDetailsActivity method because that view model is only used by that activity.
Now, I'm surely missing something because after doing this, I'm always getting the following exception:
java.lang.IllegalStateException: ViewModelProviders.of(th…ilsViewModel::class.java] must not be null
If I debug the app, I can see that moving the ShowDetailsViewModel into its own model does not register it on the map used by the factory (ie, the map has only one entry, which corresponds to the MainActivityViewModel that is registered in the ViewModelModule.
I thought that moving each view model the declaration into each a module used by a subcomponent should still allow it to be registered in a map injected by a module which is registered with the top component. Am I wrong? What is it that I'm missing to make this work?
Thanks.
The problem lies with ViewModelFactory being #Singleton and that it won't get any of the bindings you add in your subcomponents. Remove the scope from your factory or make it #ActivityScoped (the same scope as the ViewModel for the Activity)
The Activity (#ActivityScoped) has access to the factory (#Singleton), but the factory (#Singleton) does not have access to use or create the ViewModel from a lower scope (#ActivityScoped). So moving the factory to the same scope (#ActivityScoped) would give it access to create the viewmodel in question.

Dagger2: How no to duplicate Module-Component forActivity / Fragments, relying on single Base(Module/Component) for Base(Activity/Fragment) &

Given BaseFragment and its subclasses: DerivedFragmentA, DerivedFragmentB, ...
Let's say, that most of #Inject fields are common for each fragment and hence declared in BaseFragment:
abstract class BaseFragment : DaggerFragment() {
#Inject lateinit var vmFactory: ViewModelProvider.Factory
}
class DerivedFragmentA : BaseFragment()
class DerivedFragmentB : BaseFragment()
...
For each of derived fragments we have to manually create Module-Component pairs, such as:
#Subcomponent
interface DerivedFragmentAComponent : AndroidInjector<DerivedFragmentA> {
#Subcomponent.Builder
abstract class Builder : AndroidInjector.Builder<DerivedFragmentA>()
}
#Module(subcomponents = [DerivedFragmentAComponent::class])
abstract class DerivedFragmentAModule {
#Binds #IntoMap #FragmentKey(DerivedFragmentA::class)
abstract fun bind(builder: DerivedFragmentAComponent.Builder): AndroidInjector.Factory<out Fragment>
}
And install each of them to some outer component, like that:
#Subcomponent(modules = [DerivedFragmentAModule::class, DerivedFragmentBModule::class, ...])
interface MainComponent : AndroidInjector<MainActivity> {
#Subcomponent.Builder
abstract class Builder : AndroidInjector.Builder<MainActivity>()
}
But this is kind of boilerplate.
If we try to make just a single Module-Component for BaseFragment and install only it to the MainComponent - we will get a runtime exception during invocation of AndroidInjector.inject(fragment) method, with the following message:
"No injector factory bound for Class<DerivedFragmentA>. Injector factories were bound for supertypes of DerivedFragmentA: BaseFragment. Did you mean to bind an injector factory for the subtype?"
Is there any way to fix that and avoid code duplication? Or as Dagger-2 strongly relies on classnames, its impossible ?
Injection with Dagger 2 always works with the type you specify. inject(fragment : BaseFragment) will only ever inject the fields of BaseFragment and none of the fields declared in any subclasses. That's just something you have to keep in mind.
You say you would like to just declare one component and inject things into the BaseFragment only, so that's exactly what you can do. Instead of creating a subcomponent for your DerivedFragment you create one for your BaseFragment...
#Subcomponent
interface BaseFragmentComponent : AndroidInjector<BaseFragment> {
#Subcomponent.Builder
abstract class Builder : AndroidInjector.Builder<BaseFragment>()
}
Then you can bind the BaseFragment.Builder to your DerivedFragmentXs.
#Module(subcomponents = [BaseFragmentComponent::class])
abstract class BaseFragmentModule {
#Binds #IntoMap #FragmentKey(DerivedFragmentA::class)
abstract fun bind(builder: BaseFragmentComponent.Builder): AndroidInjector.Factory<out Fragment>
#Binds #IntoMap #FragmentKey(DerivedFragmentB::class)
abstract fun bind(builder: BaseFragmentComponent.Builder): AndroidInjector.Factory<out Fragment>
#Binds #IntoMap #FragmentKey(DerivedFragmentC::class)
abstract fun bind(builder: BaseFragmentComponent.Builder): AndroidInjector.Factory<out Fragment>
}
The important bit is to set #FragmentKey(DerivedFragmentA::class) to reference the subclass, since that's the one Dagger will look up when calling AndroidInjection.inject(fragment).
I'd still recommend you not to use this approach, as you'd end up with a mix of some fragments being fully injected and others where it's just the BaseFragment. This sounds confusing to me.
You could just use #ContributesAndroidInjector instead to generate the boilerplate code for you and properly inject every fragment.

Dagger2 : How to use #Provides and #Binds in same module

I'm using the new Dagger2 (ver 2.11) and I'm using the new features like AndroidInjector, and ContributesAndroidInjector. I have an activity subcomponent,
#Module
abstract class ActivityBuilderModule {
#ContributesAndroidInjector(modules =
{UserListModule.class, MainFragmentModule.class})
#ActivityScope
abstract MainActivity bindsMainActivity();
}
#Module
public abstract class MainFragmentModule {
#ContributesAndroidInjector
#FragmentScope
#FragmentKey(UserListFragment.class)
abstract UserListFragment bindsUserListFragment();
}
And the UserListModule provides dependencies for the fragment. Some of the dependencies I just want to bind the instances , and return , like
#Binds
#ActivityScope
abstract UserListView mUserListView(UserListFragment userListFragment);
Instead of simply just return the dependency , like
#Provides
#ActivityScope
UserListView mUserListView(UserListFragment userListFragment){
return userListFragment;
}
My module contains some #Provides methods as well. Can we use both #Binds and #Provides methods in the same module? I tried as shown below
#Module
public abstract class UserListModule {
#Provides
#ActivityScope
UserListFragment mUserListFragment() {
return new UserListFragment();
}
#Binds
#ActivityScope
abstract UserListView mUserListView(UserListFragment userListFragment);
// other provides and binds methods...
......
.....
}
And it its throwing error
Error:(22, 8) error: dagger.internal.codegen.ComponentProcessor was unable to process this interface because not all of its dependencies could be resolved. Check for compilation errors or a circular dependency with generated code.
Is there any way to do this?
#Binds and #ContributesAndroidInjector methods must be abstract, because they don't have method bodies. That means that they must go on an interface or abstract class. #Provides methods may be static, which means they can go on abstract classes and Java-8-compiled interfaces, but non-static ("instance") #Provides methods don't work on abstract classes. This is explicitly listed in the Dagger FAQ, under the sections "Why can’t #Binds and instance #Provides methods go in the same module?" and "What do I do instead?".
If your #Provides method doesn't use instance state, you can mark it static, and it can go onto an abstract class adjacent to your #Binds methods. If not, consider putting the bindings like #Binds and #ContributesAndroidInjector into a separate class--possibly a static nested class--and including that using the includes attribute on Dagger's #Module annotation.
A little addition to Jeff's solution above:
you may create inner interface instead of static inner class, like this:
#Module(includes = AppModule.BindsModule.class)
public class AppModule {
// usual non-static #Provides
#Provides
#Singleton
Checkout provideCheckout(Billing billing, Products products) {
return Checkout.forApplication(billing, products);
}
// interface with #Binds
#Module
public interface BindsModule {
#Binds
ISettings bindSettings(Settings settings);
}
}
In kotlin, you can leverage companion object
#Module
abstract class MyDaggerModule {
#Binds
abstract fun provideSomething(somethingImpl: SomethingImpl): Something
companion object {
#Provides
fun provideAnotherThing(): AnotherThing {
return AnotherThing()
}
}
}
This is other type solution: Add modules to other module after that you can call top module in your component interface. It can be more efficiency because you can use abstract and static.
Details and examples are below:
For example, we have an component interface and two modules such as ComponentClasses, Module_ClassA and Module_ClassB.
Module_ClassA is:
#Module
public class Module_ClassA {
#Provides
static ClassA provideClassA(){
return new ClassA();
}
}
Module_ClassB is:
#Module
abstract class Module_ClassB {
#Binds
abstract ClassB bindClassB(Fragment fragment); //Example parameter
}
So now, we have two models. If you want use them together, you can add one of them to other. For example: You can add Module_ClassB to Module_ClassA:
#Module(includes = Module_ClassB.class)
public class Module_ClassA {
#Provides
static ClassA provideClassA(){
return new ClassA();
}
}
Finally, you do not need to add both modules to your component class. You can only add your top module on your component class, like that:
ComponentClasses is:
#Component(modules = Module_ClassA)
public interface ComponentClasses {
//Example code
ArrayList<CustomModel> getList();
}
However, you should be careful because you need to add your top module. Thus, Module_ClassA added on ComponentClasses interface.

Categories

Resources