Dagger 2.10+: Inject dependency with activity context in Activities and Fragments - android

I have a class called AlertManager which requires Activity instance to show Toast and AlertDialog.
class AlertManager #Inject constructor(private val activity: Activity) {
fun showToast(message: String) {
Toast.makeText(activity, message, Toast.LENGTH_SHORT).show()
}
}
Now, I want AlertManager as dependency in two activities HomeActivity & ProductsActivity. Currently I have created modules for each Activity like:
#Module
class HomeActivityModule {
#Provides
#ActivityContext
fun provideAlertManager(activity: HomeActivity) = AlertManager(activity)
}
And
#Module
class ProductsActivityModule {
#Provides
#ActivityContext
fun provideAlertManager(activity: ProductsActivity) = AlertManager(activity)
}
And binding them with Dagger like
#Module
abstract class ActivityProvider {
#ContributesAndroidInjector(modules = [HomeActivityModule::class])
#ActivityContext
abstract fun bindHomeActivity(): HomeActivity
#ContributesAndroidInjector(modules = [ProductsActivityModule::class])
#ActivityContext
abstract fun bindProductsActivity(): ProductsActivity
}
Now my questions are:
1) How can I avoid creating modules for each activities and have common ActivityModule which I can bind with whatever Activity I want?
2) Let's say I have a fragment called HomeFragment inside HomeActivity, then how can I inject the same AlertManager instance of HomeActivity inside the fragment?
I am stuck here since quite long and have tried to find a lot over internet but I am unable to find any blog or guide which can help me to achieve what I am looking for. If someone can point me in right direction, I'll be grateful.

1) How can I avoid creating modules for each activities and have common ActivityModule which I can bind with whatever Activity I want?
You can have some sort of AlertManagerModule where you add generic activity.
#Provides
fun provideAlertManager(activity: Activity) = AlertManager(activity)
You still will have to make individual activity modules. One change you can make is:
#Module
abstract class HomeActivityModule {
#Binds
abstract fun providesActivity(activity: HomeActivity) : Activity
}
And then you can add them to the ActivityProvider class:
#Module
abstract class ActivityProvider {
#ContributesAndroidInjector(modules = [HomeActivityModule::class, AlertManagerModule::class])
abstract fun bindHomeActivity(): HomeActivity
#ContributesAndroidInjector(modules = [ProductsActivityModule::class, AlertManagerModule::class])
abstract fun bindProductsActivity(): ProductsActivity
}
2) Let's say I have a fragment called HomeFragment inside HomeActivity, then how can I inject the same AlertManager instance of HomeActivity inside the fragment?
Since you're using DaggerActivity and most likely using DaggerFragment, the fragment instantiated in the HomeFragment can directly get the AlertManager by simply using the #Inject annotation in the fragment provided you add in the HomeActivityModule:
#Module
abstract class HomeActivityModule {
#Binds
abstract fun providesActivity(activity: HomeActivity) : Activity
#FragmentScope
#ContributesAndroidInjector
abstract fun providesHomeFragment() : HomeFragment;
}

Related

Dagger 2 + MVP - single presenter assigned to multiple fragments

I would like to implement a part of an application that takes some steps that would be handled by one presenter. I have declared one scope:
#Scope
annotation class FormScope
next, I wanted to declare a module that would provide necessary dependencies:
#Module
object FormModule {
#JvmStatic
#Provides
fun providesFragmentManager(activity: FragmentActivity): FragmentManager = activity.supportFragmentManager
#JvmStatic
#Provides
fun providesNavigation(fragmentManager: FragmentManager): SobergridCoachingNavigationUnauthorizedFirstStep = SobergridCoachingNavigationUnauthorizedFirstStep(fragmentManager)
#JvmStatic
#Provides
fun providesCoordinator(navigation: NavigationUnauthorized): CoordinatoUnauthorized = CoordinatoUnauthorized(navigation)
#JvmStatic
#Provides
#Reusable
fun providesPresenter(coordinator: CoordinatoUnauthorized): OnboardingFragmentContract.Presenter = FormPresenter(coordinator)
}
and finally, I bind the modules into fragments that I want inject dependencies into:
#Module(includes = [AndroidSupportInjectionModule::class])
abstract class FragmentBindingModule {
#FormScope
#ContributesAndroidInjector(modules = [FormFirstModule::class, FormModule::class])
abstract fun contributesFormFirst(): FormFirstFragment
#ContributesAndroidInjector(modules = [FormSecondModule::class, FormModule::class])
abstract fun contributesFormSecond(): FormSecondFragment
#ContributesAndroidInjector(modules = [FormThirdModule::class, FormModule::class])
abstract fun contributesFormThird(): FormThirdFragment
}
The problem that I encounter is that every single time a new fragment is showed the Dagger creates a new instance of the Presenter. I want to use a single presenter for all of those Fragments. What I do wrong? What should I improve to be able to achieve my goal?
UPDATE
I have also tried annotating my provide method with #Singleton
#JvmStatic
#Provides
#Signleton
fun providesPresenter(coordinator: CoordinatoUnauthorized): OnboardingFragmentContract.Presenter = FormPresenter(coordinator)
but this leads to the compilation error. The last thing that I tried was to put annotations (both #Reusable and #Singleton) before the declaration of the Presenter class. This approach gives me no compilation errors but still, there is more than one instance of the Presenter class.
Move your Presenter provides to FragmentActivity Module with Scope to get the same Presenter for all fragments in Activity
#Module
public class FragmentActivityModule {
//common provides for all fragments
#Provides
#FormScope
public YourPresenter providesYourPresenter() {
return new YourPresenter();
}
....
And your builder have to look like this
#Module
public abstract class ActivityBuilder {
#FormScope
#ContributesAndroidInjector(modules = {FragmentActivityModule.class, Form1FragmentProvider.class
, Form2FragmentProvider.class})
abstract FragmentActivity bindFragmentActivity();
In Form1FragmentModule are provides only for Form1Fragment.
Create FragmentProviders for all fragments Form1FragmentProvider, Form2FragmentProvider...
#Module
public abstract class Form1FragmentProvider {
#ContributesAndroidInjector(modules = Form1FragmentModule.class)
abstract Form1Fragment provideForm1FragmentFactory();
}
Do not forget implement HasSupportFragmentInjector in your FragmentActivity
public class FragmentActivity extends AppCompatActivity implements HasSupportFragmentInjector {
#Inject
DispatchingAndroidInjector<Fragment> fragmentDispatchingAndroidInjector;
....
#Override
public AndroidInjector<Fragment> supportFragmentInjector() {
return fragmentDispatchingAndroidInjector;
}
Do not forget attach AndroidSupportInjection in your Fragments
#Override
public void onAttach(Context context) {
AndroidSupportInjection.inject(this);
super.onAttach(context);
}

Android Dagger get Parent Fragment into child fragment

I have been trying to, unsuccessfully, inject the Parent Fragment into its sub fragments for navigation purposes. I have followed a couple of different posts but I can't seem to understand what am I missing in my implementation.
I have a MainActivity that contains a ViewPager with one such page containing EventsFragment. This fragment in turn has two child fragments EventsListFragment and EventsDetailFragment. I wanted to inject EventsFragment fragment into EventsListFragment so that I can tell it to navigate to EventsDetailFragment.
I know this is probably a repetition of the posts below and I really apologize for that but I honestly cannot see what am I missing. These are the posts I've found:
Dagger 2.10 Android subcomponents and builders
How to create custom scoped modules in dagger 2.10
https://google.github.io/dagger/subcomponents.html
My implementation is as follows:
ApplicationComponent
#Singleton
#Component(modules = [
AndroidSupportInjectionModule::class,
ActivityModule::class,
BaseApplicationModule::class,
ApplicationModule::class])
interface ApplicationComponent : AndroidInjector<AndroidApplication> {
#Component.Builder
abstract class Builder : AndroidInjector.Builder<AndroidApplication>()
}
ActivityModule
#Module(includes = [MainActivityProvider::class])
abstract class ActivityModule{
}
MainActivityProvider
#Module(includes = [EventsFragmentProvider::class])
abstract class MainActivityProvider {
#PerActivity
#ContributesAndroidInjector(modules = [MainActivityModule::class])
abstract fun provideMainActivityFactory(): MainActivity
}
EventsFragmentProvider
#Module(includes = [EventsListProvider::class,
EventsDetailProvider::class])
abstract class EventsFragmentProvider {
#PerFragment
#ContributesAndroidInjector(modules = [EventsFragmentModule::class])
abstract fun provideEventsFragmentFactory(): EventsFragment
}
EventsFragmentModule
#Module
class EventsFragmentModule {
#Binds
abstract fun providesEventListView(eventsFragment: EventsFragment): EventContract.Flow
}
EventsListProvider
#Module
abstract class EventsListProvider {
#PerFragment
#ContributesAndroidInjector(modules = [EventsListModule::class])
abstract fun provideEventsListFragmentFactory(): EventsListFragment
}
EventsFragment
class EventsFragment : DaggerFragment(), EventContract.Flow {
override fun navigateToList() {
addFragment(navigator.getEventsListFragment(context!!))
}
override fun navigateToDetail(id: String) {
println("id = ${id}")
}
...
}
EventContract
interface EventContract {
interface Flow {
fun navigateToList()
fun navigateToDetail(id: String)
}
}
EventsListFragment
class EventsListFragment : DaggerFragment() {
#Inject
lateinit var eventsFlow: EventContract.Flow
...
}
Error
[Dagger/MissingBinding] [dagger.android.AndroidInjector.inject(T)] EventContract.Flow cannot be provided without an #Provides-annotated method.
public abstract interface ApplicationComponent extends dagger.android.AndroidInjector<AndroidApplication> {
^
EventContract.Flow is injected at
EventsListFragment.eventsFlow
EventsListFragment is injected at
dagger.android.AndroidInjector.inject(T)
component path: ApplicationComponent →EventsListProvider_ProvideEventsListFragmentFactory.EventsListFragmentSubcomponent
I may be using a anti-pattern but this is what got me working to this point. Im open to changes that may help me achieve this
The error is caused by the following module:
#Module
class EventsFragmentModule {
#Binds
abstract fun providesEventListView(eventsFragment: EventsFragment): EventContract.Flow
}
Dagger would not provide you with a Fragment and you shouldn't do that.
Moreover, I think you misunderstand the meaning of #ContributesAndroidInjector. It means creating an AndroidInjector for you but proving an instance.
#Module
abstract class EventsListProvider {
#PerFragment
#ContributesAndroidInjector(modules = [EventsListModule::class])
abstract fun provideEventsListFragmentFactory(): EventsListFragment
}
So you should pass your EventsFragment instance into the module like this post instead of using field injection.

How to inject activity into another class

I need to #Inject constructor activity and fragmentManager into my Navigator.class. But I'm getting this error:
AppCompatActivity cannot be provided without an #Inject constructor or
from an #Provides-annotated method.
I've read all similar questions on Stackoverflow. I did everything they suggested, but didn't find the answer.
Navigator.class
#Singleton
class Navigator #Inject constructor(private val activity: AppCompatActivity,
private val fragmentManager: FragmentManager)
BaseActivity.class
abstract class BaseActivity: AppCompatActivity(), HasSupportFragmentInjector{
#Inject
lateinit var fragmentInjector: DispatchingAndroidInjector<Fragment>
#Inject
lateinit var navigator: Navigator
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
}
override fun supportFragmentInjector() = fragmentInjector
BaseModule.class
#Module
abstract class BaseActivityModule {
#Module
companion object {
#JvmStatic
#Provides
#Named(ACTIVITY_FRAGMENT_MANAGER)
#PerActivity
fun activityFragmentManager(activity: AppCompatActivity): FragmentManager =
activity.supportFragmentManager
}
#Binds
#PerActivity
abstract fun activity(appCompatActivity: AppCompatActivity): Activity
#Binds
#PerActivity
abstract fun activityContext(activity: Activity): Context
MainActivityModule.class
#Module(includes = [
BaseActivityModule::class
])
abstract class MainActivityModule {
#Binds
#PerActivity
abstract fun appCompatActivity(mainActivity: MainActivity): AppCompatActivity
AppModule.class
#Module(includes = [AndroidSupportInjectionModule::class])
abstract class AppModule {
#Binds
#Singleton
abstract fun application(app: App): Application
#PerActivity
#ContributesAndroidInjector(modules = [MainActivityModule::class])
abstract fun mainActivityInjector(): MainActivity
First, please make sure to understand what the error means. You're trying to inject AppCompatActivity but Dagger complains that it cannot be provided. So far so good. Please always make sure to include the full error as shown in the link, so that we can more easily see what's going on. In your case this seems to be a scoping issue.
You provide a Navigator through constructor injection as a #Singleton...
#Singleton
class Navigator #Inject constructor(private val activity: AppCompatActivity,
private val fragmentManager: FragmentManager)
And you provide/bind AppCompatActivity from a module with #PerActivity scope.
#Binds
#PerActivity
abstract fun appCompatActivity(mainActivity: MainActivity): AppCompatActivity
So unless you have a really crazy setup, #Singleton will not be able to access #PerActivity. If it could, you'd have a memory leak at the very least, since you'd keep your Activity instance around longer than its lifetime (onCreate -> onDestroy)
To solve your problem you have to either move Navigator down in scope to also be #PerActivity and share the lifecycle of the Activities it references (so that it won't leak) or you have to remove the dependency on your Activity in your constructor. If you really do need it to be #Singleton and reference an Activity you might be able to have a setter for the current Activity, but again, watch out for leaks.

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