Reputation: 1526
I have set up dagger2 dependencies in my app as I understand it and through the many examples. What I have not found is the proper way to use all of the dependencies once they are injected.
Each of the singletons in the module depends on the output of the singleton before it. How is the entire dependency graph used without calling each singleton in turn to get the required inputs?
Given the following:
AppComponent
@Singleton
@Component(modules = {
DownloaderModule.class
})
public interface AppComponent {
void inject(MyGameActivity activity);
}
DownloaderModule
@Module
public class DownloaderModule {
public static final String NETWORK_CACHE = "game_cache";
private static final int GLOBAL_TIMEOUT = 30; // seconds
public DownloaderModule(@NonNull String endpoint) {
this(HttpUrl.parse(endpoint));
}
@Provides @NonNull @Singleton
public HttpUrl getEndpoint() {
return this.endpoint;
}
@Provides @NonNull @Singleton @Named(NETWORK_CACHE)
public File getCacheDirectory(@NonNull Context context) {
return context.getDir(NETWORK_CACHE, Context.MODE_PRIVATE);
}
@Provides @NonNull @Singleton
public Cache getNetworkCache(@NonNull @Named(NETWORK_CACHE) File cacheDir) {
int cacheSize = 20 * 1024 * 1024; // 20 MiB
return new Cache(cacheDir, cacheSize);
}
@Provides @NonNull @Singleton
public OkHttpClient getHttpClient(@NonNull Cache cache) {
return new OkHttpClient.Builder()
.cache(cache)
.connectTimeout(GLOBAL_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(GLOBAL_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(GLOBAL_TIMEOUT, TimeUnit.SECONDS)
.build();
}
MyGameApp
public class MyGameApp extends Application {
private AppComponent component;
private static Context context;
public static MyGameApp get(@NonNull Context context) {
return (MyGameApp) context.getApplicationContext();
}
@Override
public void onCreate() {
super.onCreate();
component = buildComponent();
MyGameApp.context = getApplicationContext();
}
public AppComponent component() {
return component;
}
protected AppComponent buildComponent() {
return DaggerAppComponent.builder()
.downloaderModule(new DownloaderModule("https://bogus.com/"))
.build();
}
}
Upvotes: 0
Views: 963
Reputation: 17095
I'll try to shed some light into this, but there are several ways you can read this. I prefer a bottom up approach - Basically start on what your objects require and work my way up. In this case, I would start at MyGameActivity
. Unfortunately, you didn't paste the code for this, so I'll have to be a bit creative, but that's ok for the purpose of the exercise.
So in your app you're probably getting the AppComponent
and calling inject
for your MyGameActivity
. So I guess this activity has some injectable fields. I'm not sure if you're using there directly OkHttpClient
but let's say you do. Something like:
public class MyGameActivity extends SomeActivity {
@Inject
OkHttpClient okHttpClient;
// ...
}
The way I like to think about this is as follows. Dagger knows you need an OkHttpClient
given by the AppComponent
. So it will look into how this can be provided - Can it build the object itself because you annotated the constructor with @Inject
? Does it require more dependencies?.
In this case it will look into the modules of the component where this client is being provided. It will reach getHttpClient
and realise it needs a Cache
object. It will again look for how this object can be provided - Constructor injection, another provider method?.
It's again provided in the module, so it will reach getNetworkCache
and once more realise it needs yet another dependency.
This behaviour will carry on, until it reaches objects that require no other dependencies, such as your HttpUrl
in getEndpoint
.
After all this is done, your OkHttpClient
can be created.
I think it's easy to understand from this why you can't have cycles in your dependency graph - You cannot create an object A
if it depends on B
and B
depends on A
. So imagine that for some weird reason you'd reach the method getEndpoint
which would depend on the OkHttpClient
from that module. This wouldn't work. You'd be going in circles an never reach an end.
So if I understand your question: How is the entire dependency graph used without calling each singleton in turn to get the required inputs?
It's not. It has to call all the methods to be able to get the singletons. At least the first time they're provided within the same component/scope. After that, as long as you keep the same instance of your component, the scoped dependencies will always return the same instance. Dagger will make sure of this. If you'd for some reason destroy the component or recreate it, then the dependencies wouldn't be the same instances. More info here. In fact this is true for all scopes. Not just @Singleton
s.
However, as far as I can tell you're doing it right. When your application is created you create the component and cache it. After that, every time you use the method component()
you return always the same component and the scoped dependencies are always the same.
Upvotes: 1