My issue with the Android-specific pattern is, if you use their AndroidInjection
class, there is no way to members inject other objects besides Activities
/Fragments
/custom views/adapters, except with the Application Component. This is because you cannot get a reference the the Subcomponent
(AndroidInjector
) used to inject Activities
/Fragments
.
This makes injecting Dialogs (if you use DialogFragments
).
The AndroidInjection
class seems to support just the core Android types.
What follows is not an answer to your question, but an explanation why you shouldn't be asking this question at all.
You should avoid injections into custom Views
in general. The reasons for this are listed in this article.
Advantages of using Method Injection in this case [injection into custom Views] are:
- Dependencies will need to be propagated from top level component (Activity or Fragment)
- Method Injection does not open door to Single Responsibility Principle violation
- No dependency on the framework
- Better performance
The first advantage might come as a surprise because propagation from
top level component is harder than adding annotation to fields, and
involves more boilerplate code. This is surely a bad thing, right?.
Not in this case. In fact, there are two good aspects associated with
such a propagation of dependencies. First of all, the dependencies
will be visible at the top level component. Therefore, just by looking
at e.g. Fragment‘s fields, the reader of the code will immediately
understand that this Fragment shows images. Such optimizations for
readability makes the system more easily maintainable in the long
term. Secondly, there are not many use cases in which sub-classes of
View need additional dependencies. The fact that you need to actually
work in order to provide these dependencies will give you a bit of
time to think about whether providing them is a good design decision
to start with.
The second advantage is related to collaborative construction. You
might be very experienced software engineer yourself, but you’ll
probably have also less experienced teammates. Or it is possible that
you’ll leave the project one day, and the guy who will take over will
not be as good as you. By injecting one single dependency using a
framework, you basically open a door for other injections. Imagine
that some data from SharedPreferences becomes required in custom View
in order to e.g. fix a bug. One of the less experienced developers
might decide that it is a good approach to inject SharedPreferences
into custom View directly. Doing this violates Single Responsibility
Principle, but that developer might not even be aware of such a
concept. Therefore, in the long term, such injection “backdoors” can
reduce design quality and lead to long debug sessions.
The third advantage of using Method Injection with custom Views is
that you don’t couple the View to dependency injection framework. Just
imagine that few years from now you (or some other poor guy) need to
replace the framework. The fact that you’ll probably have tens of
Activities and Fragments to start with will make your life miserable.
If you’ll have additional tens or hundreds of custom Views to handle,
then it might bring you into suicidal mood.
The last (but not least) advantage is performance. One screen can
contain one Activity, several Fragments and tens of custom Views.
Bootstrapping this number of classes using dependency injection
framework might degrade application’s performance. It is especially
true for reflection based frameworks, but even Dagger carries some
performance cost.
In addition, I advice to avoid the new injection method that involves AndroidInjection
class. It is discussed in this video tutorial.
First, you should think over Vasily's answer.
But let's think for a moment how we did this before Dagger Android?
We built a subcomponent from the component that was taken from the Application
class. Later, we could use this subcomponent in order to inject fields, for example, of a custom view.
So, we'll try to do the exact same thing now.
Suppose, our aim is to inject MyAdapter
class into a MyButton
:
public class MyButton extends AppCompatButton {
@Inject MyAdapter adapter;
public MyButton(Context context) {
super(context);
...
}
}
And let's make the adapter have a dependency on the activity Context
, not application Context
:
public class MyAdapter {
@Inject
public MyAdapter(@Named("activity") Context context) {
}
}
Let's start with the custom Application
class.
MyApplication.java
public class MyApplication extends DaggerApplication {
@Inject
DispatchingAndroidInjector<Activity> dispatchingActivityInjector;
public static MySubcomponent mySubcomponent;
@Override
protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
return DaggerAppComponent.builder()
.create(this);
}
}
AppComponent.java:
@Component(modules = {AndroidSupportInjectionModule.class, ActivityBindingModule.class, AppModule.class})
@Singleton
public interface AppComponent extends AndroidInjector<MyApplication> {
@Component.Builder
abstract class Builder extends AndroidInjector.Builder<MyApplication> {
}
}
AppModule.java
@Module
abstract class AppModule {
@Binds
@Singleton
@Named("app")
abstract Context providesContext(Application application);
}
ActivityBindingModule.java
@Module(subcomponents = MySubcomponent.class)
public abstract class ActivityBindingModule {
@Binds
@IntoMap
@ActivityKey(MainActivity.class)
abstract AndroidInjector.Factory<? extends Activity>
bindMainActivityInjectorFactory(MySubcomponent.Builder builder);
}
AndroidSupportInjectionModule.java
is shipping with dagger itself. If you do not use classes from support package (i.e. android.support.v4.app.Fragment
instead of android.app.Fragment
), then use AndroidInjectionModule.java
.
MySubcomponent.java
@ActivityScope
@Subcomponent(modules = {SubcomponentModule.class/*, other modules here, if needed */})
public interface MySubcomponent extends AndroidInjector<MainActivity> {
void inject(MyButton button);
@Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<MainActivity> {
public abstract MySubcomponent build();
}
}
SubcomponentModule.java
@Module
abstract class SubcomponentModule {
@Binds
@ActivityScope
@Named("activity")
abstract Context toContext(MainActivity activity);
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
@Inject
MySubcomponent subcomponent;
@Override
protected void onCreate(Bundle savedInstanceState) {
// Will inject `subcomponent` field
AndroidInjection.inject(this);
// Saving this component in a static field
// Hereafter you are taking responsibility of mySubcomponent lifetime
MyApplication.mySubcomponent = subcomponent;
super.onCreate(savedInstanceState);
setContentView(new MyButton(this));
}
}
Having all of these, now here's how MyButton
will look like:
public class MyButton extends AppCompatButton {
@Inject MyAdapter adapter;
public MyButton(Context context) {
super(context);
MyApplication.mySubcomponent.inject(this);
}
}
I admit that this looks hacky and certainly not an approach to stick to. I'm happy to see a better approach.
This is because you cannot get a reference the the Subcomponent
(AndroidInjector
) used to inject Activities
/Fragments
.
You can always just inject the component itself. Just add a field for the component to your Activity / Fragment and let Dagger inject it along with the rest.
// will be injected
@Inject MainActivitySubcomponent component;
The issue of whether the dagger-android classes like AndroidInjector
should support injection inside Views or not has been discussed in the following Github issue:
https://github.com/google/dagger/issues/720
Quoting from one of the library authors:
There is both a philosophical point and logistical/implementation point to be made here.
First, it's not fully clear to us that injecting views is the right thing to do. View objects are meant to draw, and not much else. The controller (in a traditional MVC pattern) is the one which can coordinate and pass around the appropriate data to a view. Injecting a view blurs the lines between fragments and views (perhaps a child fragment is really the appropriate construct instead?)
From the implementation perspective, there is also a problem in that there isn't a canonical way to retrieve the View's parent Fragments (if any), or Activity to retrieve a parent component. There have been hacks suggested to build in that relationship, but so far we haven't seen anything that seems to suggest that we could do this correctly. We could just call View.getContext().getApplicationContext() and inject from there, but skipping the intermediate layers without any option for something in between is inconsistent with the rest of our design, and probably confusing to users even if it works.
This reinforces the opinion expressed in Vasily's answer.
To add further, people often seem to want to inject model-layer dependencies inside their custom views. This is a bad idea as it goes against the software engineering principle of separation of concerns.
The correct solution for associating a view and a model is to write an adapter like the adapters for RecyclerView and ListView. You can inject the model-layer dependency at the Fragment or Presenter level and set the adapter there.