Notes of learning Dagger and Dagger-Android
Sources:
Source code:
dependencies {
implement 'com.google.dagger:dagger:2.x'
annotationProcessor 'com.google.dagger:dagger-compiler:2.x'
}
If using classes in dagger.android
:
implement 'com.google.dagger:dagger-android:2.x'
implement 'com.google.dagger:dagger-android-support:2.x' // if using support libraries
annotationProcessor 'com.google.dagger:dagger-android-processor:2.x'
Dagger is essentially a code generator.
public class User{
private int id;
private String username;
public User(int id, String username){
this.id = id;
this.username = username;
}
}
The integer id
and string username
are dependencies of the User
class; bcuz it depends on them to be created.
Dagger takes this general concept to provide dependencies to higher level of abstraction. This makes your code very clear, concise and testable.
One of the things you can do with dagger is injecting a Singleton Pattern for an object.
Creating an Application-level Component class is always required for using Dagger in Android.
For using Dagger we should inherit our BaseApplication
class from DaggerApplication
:
public class BaseApplication extends DaggerApplication {
\\ This is the function returning the AppComponent
@Override
protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
return DaggerAppComponent.builder().application(this).build();
}
}
We can consider the BaseApplication
class as the client and the AppComponent
class as the server.
AppComponent
is actually an interface extending from AndroidInjector<BaseApplication>
:
@Component(
modules = { // Include some additional dependencies.
AndroidSupportInjectionModule.class,
}) // Telling Dagger (code generator): I'm believing this class as a component class
public interface AppComponent extends AndroidInjector<BaseApplication> { // in Android we should extend from AndroidInjector
// Meaning we're injecting BaseApplication class into AppComponent.
// Overridong the Component.builder() method whose code will be created by Dagger from Builder interface.
@Component.Builder
interface Builder{
// Binds an instace of an object to the component at the time of construction.
@BindsInstance
Builder application(Application application);
AppComponent build();
}
}
In general:
- Components = Services
- Activities/Fragments = Clients
You will ALWAYS need an AppComponent and you will ALWAYS need to put it inside your BaseApplication class.
Every Dagger interaction = a client-server interaction in some level.
At veiw-level: Activities and Fragments act as clients; and Components as services.
For this, we should first mark activities as potential clients.
Dagger Module: A place for dependencies to live to add them to components.
@Module
public abstract class ActivityBuildersModule {
@ContributesAndroidInjector //AuthActivity is a potential client that I can inject dependencies into.
abstract AuthActivity contributeAuthActivity();
@Provides //Inject this static dependency into AuthActivity.
static String someString(){
return "this is a test string";
}
}
// Should extend from DaggerAppCompatActivity
public class AuthActivity extends DaggerAppCompatActivity {
private static final String TAG = "AuthActivity";
@Inject // Property to inject into.
String someString;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_auth);
Log.d(TAG, "onCreate: " + someString);
}
}
You ALWAYS will have to declare your activities inside ActivityBuildersModule class.
@Component(
modules = {
AndroidSupportInjectionModule.class, // Always needed for Application-level AppComponent.
...
})
public interface AppComponent extends AndroidInjector<BaseApplication> {
...
}
AppModule
class: where we should put all of the application-level dependencies for the project. e.g. Retrofit Instance, Glide instance and anything that is going to exist and not change for the entire lifetime of the app.
@Provides
annotation: used to declare a dependency. e.g.
@Module
public class AppModule {
@Provides // creates this dependency when this String is injected into an Activity.
static String someString(){
return "this is a test string";
}
}
We use static
for dependencies bcuz documentation recommends; it is more efficient.
a (sub)modulea is unique to the particular component.
- AuthModule -> AuthComponent
- MainModule -> MainComponent
It can be a good convention to name AppModule
class @Provides
methods as providesSth(){...}
.
@Provides
static RequestOptions provideRequestOptions(){
return RequestOptions
.placeholderOf(R.drawable.white_background)
.error(R.drawable.white_background); // If Glide can't load image
}
@Provides
static RequestManager provideGlideInstance(Application application, RequestOptions requestOptions){
// We also have a RequestOptions object available bcuz of the upper function.
return Glide.with(application)
.setDefaultRequestOptions(requestOptions);
}
public class AuthActivity extends DaggerAppCompatActivity {
private static final String TAG = "AuthActivity";
@Inject
Drawable logo;
@Inject
RequestManager requestManager;
private void setLogo(){
requestManager
.load(logo)
.into((ImageView)findViewById(R.id.login_logo));
}
}
Singletons: you provide instances and they exist as long as the app is alive; But not a good idea to mark every object of your app as Singleton: If app get quite big all of these objects lay around in memory -> a lof of overhead.
Scoping is the solution of the problem of having all objects sitting around in memory (not needed of course).
- Singleton is only one scope.
- You can create a scope to only exist as long as some Activity or Component exists.
- You can build a custom scope and tell objects to become instances under finite conditions.
@Sigleton‍
annotation
- makes the AppComponent scoped as application-wide.
- should also be used for dependencies in AppModule.
When the component dies, all of its dependencies will also be destroyed.