I have the following code working:
SomeClass
public class SomeClass {
@Inject
@Named("special")
OkHttpClient mOkHttpClient;
public SomeClass(Activity activity) {
((MyApplication) activity.getApplication()).getApplicationComponent().inject(this);
}
}
ApplicationModule
@Module
public class ApplicationModule {
private final Application mApplication;
public ApplicationModule(Application application) {
mApplication = Preconditions.checkNotNull(application);
}
@Provides
@Singleton
Application providesApplication() {
return mApplication;
}
@Provides
@Singleton
SharedPreferences provideCustomSharedPreferences() {
return mApplication.getSharedPreferences("my_custom_file", Context.MODE_PRIVATE);
}
}
ApplicationComponent
@Singleton
@Component(modules = {
ApplicationModule.class,
NetworkModule.class
})
public interface ApplicationComponent {
void inject(SomeClass someClass);
}
MainActivity
public class MainActivity extends AppCompatActivity {
@Inject
SharedPreferences mSharedPreferences;
@Inject
@Named("default")
OkHttpClient mOkHttpClient;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((MyApplication) getApplication()).getActivityComponent().inject(this);
}
}
NetworkModule
@Module
public abstract class NetworkModule {
private static final int DEFAULT_CACHE_SIZE = 10 * 1024 * 1024; // 10 Mib
@Provides
@Singleton
static Cache provideOkHttpCache(Application application) {
return new Cache(application.getCacheDir(), DEFAULT_CACHE_SIZE);
}
@Provides
@Singleton
@Named("default")
static OkHttpClient provideDefaultOkHttpClient(Cache cache) {
OkHttpClient.Builder okHttpClient = new OkHttpClient.Builder();
if (BuildConfig.DEBUG) {
// Add logging interceptor here...
}
return okHttpClient.cache(cache).build();
}
@Provides
@Singleton
@Named("special")
static OkHttpClient provideSpecialOkHttpClient(@Named("default") OkHttpClient okHttpClient) {
return okHttpClient.newBuilder()
// Add .certificatePinner() here...
.build();
}
}
ActivityComponent
@Singleton
@Component(modules = {
ApplicationModule.class,
NetworkModule.class
})
public interface ActivityComponent {
void inject(MainActivity mainActivity);
}
MyApplication
public class MyApplication extends Application {
private ApplicationComponent mApplicationComponent;
private ActivityComponent mActivityComponent;
@Override
public void onCreate() {
super.onCreate();
}
public ApplicationComponent getApplicationComponent() {
if (mApplicationComponent == null) {
mApplicationComponent = DaggerApplicationComponent.builder()
.applicationModule(new ApplicationModule(this))
.build();
}
return mApplicationComponent;
}
public ActivityComponent getActivityComponent() {
if (mActivityComponent == null) {
mActivityComponent = DaggerActivityComponent.builder()
.applicationModule(new ApplicationModule(this))
.build();
}
return mActivityComponent;
}
}
But there are a few things that I don't like and I know they can be improved, with scopes, component dependencies or subcomponents. I can also use custom qualifiers instead of @Named
, but I know that one already.
Here's what I want to do...
I want the scope of
provideSpecialOkHttpClient
to be tied toSomeClass
lifecycle. I know I need scopes for this... But this specialOkHttpClient
depends on the defaultOkHttpClient
instance which itself depends on aokhttp3.Cache
instance. These last two instances are@Singleton
because they can be used everywhere. Only the specialOkHttpClient
instance exists tied toSomeClass
because it's the only place where it will be used. How can I accomplish this with scopes? All my attempts led me to errors because I was using a custom scope, like@SomeClassScope
, and@Singleton
within theApplicationComponent
which itself is a@Singleton
. In a nutshell, how can I have some singleton dependencies tied to the application lifecycle while having other dependencies tied to some other lifecycle (be it anActivity
orSomeClass
like in my example) when they are dependent of singleton dependencies?As you can see I'm calling
new ApplicationModule(this)
twice, defeating the purpose of@Singleton
annotated components and provide methods. How can I make theActivityComponent
dependent on theApplicationComponent
so that I only have to instantiateApplicationModule
once in theApplicationComponent
? Subcomponents? Component dependencies? I couldn't make this work with any approach...
I'm having a hard time grasping all Dagger aspects so if you could provide some code examples when answering, that would really help me a lot visualize and understand how everything ties together.
First let's have a look at what you have so far. The
ActivityComponent
is a little bit strange.@Singleton
represents app-scoped singleton. SinceActivityComponent
injects members that have the scope of an Activity and not the entire app, we probably need a new scope for that Component like this:Now we can change that component:
Notice we have now made it a dependent component of
AppComponent
. We will have to refactorAppComponent
slightly to publish it's bindings to the dependent components.Rationale: we want the
ActivityComponent
to be able to use theOkHttpClient
that is bound inNetworkModule
. However,NetworkModule
is not a module ofActivityComponent
- it is part of the parentAppComponent
. Dependent components do not "automatically" inherit all of the bindings from their parents. In order forActivityComponent
to use theOkHttpClient
as a dependency for the "special"OkHttpClient
it needs to be exposed by the parent component. You can expose a binding to a dependent component by creating a method in the interface with the type you wish to expose. It's not necessary to expose all the bindings, just the very ones that you will use in the dependent components.Now extract a module for the "special"
OkHttpClient
:and compose the
ActivityComponent
with that same module:SomeClass
is basically in the scope of your activity, so you can refactor it to get injected inside your activity by doing this:Now make
SomeClass
a field ofMainActivity
(I am assuming you are using it there because it has a dependency on Activity and it is the only Activity code you have provided):And make sure your
ActivityComponent
provides Activity. To do this you will need a new Module:And compose your ActivityComponent with this module:
Now the consumption of the components needs a little bit of work. Get rid of the
public ActivityComponent getActivityComponent()
inside your Application. ActivityComponents should be generated inside, well, an Activity in order for them to track the scopes and lifecycles correctly.So consuming the component inside your Activity should look something like this:
Finally, to answer your two questions explicitly:
By creating a custom scope (
@PerActivity
), a component that tracks that scope (ActivityComponent
), and using separate modules (SpecialNetworkModule
,ActivityModule
) for the narrower scoped dependencies. In doing this, you will need some form of relationship between the wider-scoped and narrower-scoped components. Which leads well to your next question:As in the above example, using dependent components (subcomponents are also a possibility to consider). In doing this, make sure that wider-scoped components explicitly publish their bindings to their dependent components.