Putting ViewModel in AppComponent (Dagger2) - android

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.

Related

How to create generic a ViewModelModule for many classes in 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?

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.

Dependency injection in non-global activity scope done right. How to provide special MyActivity as Activity

I am using dependency injection to provide global objects (#Singleton) and non global objects for activities only (#ActivityScoped).
Now I wonder if I did it right and if it could be done better. The most interesting part of this DI implementation is the injection of the object SomeManager into 2 different activities with restricted scope
Here is the code
The main app component
#Singleton
#Component(modules = [
ApplicationModule::class,
AndroidSupportInjectionModule::class,
ActivityModule::class,
ManagerModule::class,
...
ClientModule::class])
interface AppComponent : AndroidInjector<App> {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): AppComponent.Builder
fun build(): AppComponent
}
}
The appclication module
#Module
abstract class ApplicationModule {
#Binds
#Singleton
internal abstract fun bindContext(application: Application): Context
}
The module for the activities
#Module
abstract class ActivityModule {
#ActivityScoped
#ContributesAndroidInjector(modules = [MainActivityModule::class])
internal abstract fun mainActivity(): MainActivity
#ActivityScoped
#ContributesAndroidInjector(modules = [LoginActivityModule::class])
internal abstract fun loginActivity(): LoginActivity
}
And now I want to inject a new SomeManager to the LoginActivity and a new one to the MainActivity.
The approach is having a module for each activity like you see above in the #ContributesAndroidInjector(modules... annotation. The implementations of the 2 files look like this
#Module
object MainActivityModule {
#JvmStatic
#Provides
#ActivityScoped
internal fun provideSomeManager(activity: MainActivity, apiClient: ApiClient) =
SomeManager(activity, apiClient)
}
And
#Module
object LoginActivityModule {
#JvmStatic
#Provides
#ActivityScoped
internal fun provideSomeManager(activity: LoginActivity, apiClient: ApiClient) =
SomeManager(activity, apiClient)
}
The Question:
1) Now the LoginActivityModule and MainActivityModule look very similar. Is there a better approach to provide SomeManager to both activities without making them #Singleton and withouth creating a module for each activity (becaues SomeManager only needs an Activity, not a special sublcass)? I had something in mind like that it only takes an Activity instead of a specific XXXActivity. But how can I tell dagger to provide the XXXActivity as Activity
2) And beside that optimizing in 1), is this a correct implementation?
Update 1
I have solved it by the following implementation. Is this the right way to do this?
Module that provides the MainActivity as Activity
#Module
object MainActivityModule {
#JvmStatic
#Provides
#ActivityScoped
internal fun provideAsActivity(activity: MainActivity): Activity = activity
}
Module that provides the MainActivity as Activity
#Module
object LoginActivityModule {
#JvmStatic
#Provides
#ActivityScoped
internal fun provideAsActivity(activity: LoginActivity): Activity = activity
}
A Manager module that is only #ActivityScoped
#Module
object ManagerModule2 {
#JvmStatic
#Provides
#ActivityScoped
internal fun provideSomeManager(activity: Activity, apiClient: ApiClient) =
SomeManager(activity, apiClient)
}
And the Android injector for the activities
#Module
abstract class ActivityModule {
#ActivityScoped
#ContributesAndroidInjector(modules = [ManagerModule2::class, MainActivityModule::class])
internal abstract fun mainActivity(): MainActivity
#ActivityScoped
#ContributesAndroidInjector(modules = [ManagerModule2::class, LoginActivityModule::class])
internal abstract fun loginActivity(): LoginActivity
}
Now the LoginActivityModule and MainActivityModule look very similar. Is there a better approach to provide SomeManager to both activities without making them #Singleton? I had something in mind like that it only takes an Activity instead of a specific XXXActivity.
Yes, you could do that. You can replace the dependency on the specific Activity and replace it with Activity or Context (depending on your actual needs) and move that declaration into a separate module, which you could include in both of your ActivityModules.
#Module
object SomeManagerModule {
#JvmStatic
#Provides
#ActivityScoped
internal fun provideSomeManager(activity: Activity, apiClient: ApiClient) =
SomeManager(activity, apiClient)
}
And either include it with the module or add it to ContributesAndroidInjector.
#Module(includes = [SomeManagerModule::class])
object MainActivityModule { /* ... */ }
// or
#ActivityScoped
#ContributesAndroidInjector(modules = [LoginActivityModule::class, SomeManagerModule::class])
internal abstract fun loginActivity(): LoginActivity
And you could even remove the need for a module completely by using Constructor Injection.
#ActivityScoped
SomeManager #Inject constructor(activity: Activity, apiClient: ApiClient)
Either way you would have to bind / provide your xxxActivitys as a Activity somewhere so that Dagger can find them.
And beside that optimizing in 1), is this a correct implementation?
Looks good to me. You said you wanted a new manager per Activity, so #ActivityScoped seems the correct choice. You could possibly remove the scope completely if you don't have to ensure that there is only ever one per Activity-scoped component, but this depends on your exact usecase.

Categories

Resources