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
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 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 {
@Component.Builder
abstract class Builder extends AndroidInjector.Builder {
}
}
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 {
void inject(MyButton button);
@Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder {
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.