In-depth exploration of Dagger 2
Simple Java example #
This is an extremely basic example to get started with Dagger 2. The Main
class simply logs a
message to the console. However, it does not create a Log
object itself, and relies on Dagger 2 to
provide an object that implements this interface.
Here’s a link to the repo.
The Log
interface is just 1 method.
public interface Log {
void log(String message);
}
Separation of concerns #
The idea here is that there may be many different implementations of this interface, ones that simply write to console.log, and others that might write to the network, or the filesystem, and so forth.
Only the console logging implementation is provided in LogImpl
, but the Main
class is unaware of
this.
Using a Component #
The Main
class imply asks Dagger 2 for a Log
object.
LogComponent logComponent = DaggerLogComponent
.builder()
.logModule(new LogModule())
.build();
Creating a Component #
DaggerLogComponent
is a class that’s generated by Dagger 2, that provides the Log
object to the
Main
class. The @Component
annotation is used to tell Dagger 2 to generate this class, as shown
below.
@Singleton
@Component(modules = {LogModule.class})
public interface LogComponent {
Log providesLog();
void injectDepsIntoFieldsOf(Main main);
}
Dagger 2 field injection limitations #
Notice, the injectDepsIntoFieldsOf(Main)
function is provided as well. This is because the Main
class actually needs the Log
object injected into a field of its. And since Dagger 2 can’t
automatically inject into fields that are marked @Inject
, this method has to be called by Main
in order for it to allow Dagger 2 to inject an object into the field logger
as shown below. This
would not be the case if @Inject
was used on a constructor or method (which Dagger 2 would be able
to handle automatically).
public class Main{
@Inject
public Log logger;
public Main() {
LogComponent logComponent = DaggerLogComponent.create();
logComponent.injectDepsIntoFieldsOf(this);
}
}
This field injection limitation and workaround patterns shows up in Android (since Android system components aren’t created via constructors).
The rest of this example is very straightforwards.
The LogModule
class is tagged with @Module
and it has the responsibility of providing
(@Provides
) an implementation of the Log
interface. And this is where Dagger 2 is told how to
provide an implementation of Log. The consumers of Log
are totally unaware of the underlying
implementation (which is as it should be). This is the only place where you see a mapping from the
interface to it’s underlying implementation! This is great as the underlying implementation can be
swapped out at any time, eg, for testing.
Complex Java example #
Example2 is much more sophisticated than Example1. The idea is to create a service that simply takes a long URL and uses a URL shortening service to turn this into a short URL. The basic code is available here (in Kotlin).
Here’s a link to the repo.
#!/usr/bin/env kscript
//DEPS com.squareup.okhttp3:okhttp:3.11.0
import okhttp3.OkHttpClient
import okhttp3.Request
import java.net.URLEncoder
val arg = when {
args.size > 0 -> args[0]
else -> "https://en.wikipedia.org/wiki/Cache_replacement_policies#Last_in_first_out_(LIFO)"
}
val encodedData = URLEncoder.encode(arg, "UTF-8")
val url = "https://tinyurl.com/api-create.php?url=$encodedData"
print(doGet(url))
fun doGet(url: String): String {
val client = OkHttpClient()
val request = Request.Builder()
.url(url)
.build()
val response = client.newCall(request).execute()
return response.body()!!.string()
}
In this code, all the dependencies are explicitly created by the program. We want to keep the desired functionality but use Dagger 2 to translate this into something more modular and testable. And change the language to Java.
Main class #
The idea is very simple. Create a simple interface UrlShortenService
that allows a long URL to be
converted into a short one, shown below.
interface UrlShortenService { String getShortUrl(String longUrl); }
The code to use this service (via Dagger 2) is also straightforward, shown below.
class Main {
private final UrlShortenService service;
public static void main(String[] args) {
new Main().run();
}
public Main() {
service = DaggerUrlShortenServiceComponent.create().urlShortenService();
}
public void run() {
String longUrl =
"https://en.wikipedia.org/wiki/Cache_replacement_policies#Last_in_first_out_(LIFO)";
String shortUrl = service.getShortUrl(longUrl);
System.out.println(MessageFormat.format("longUrl={0}\nshortUrl={1}",
longUrl,
shortUrl));
}
}
When the Main
class uses the interface, it doesn’t know anything about how it’s implemented or
what its dependencies are. And that is exactly what we want. In order to hide the dependencies and
construction details, we have to create a few modules and a component.
create() vs builder().build() #
Instead of using create()
, if you use builder()
you can pass arguments to any of the module
constructors (if they take any params). This is a way to pass information that is not available in
the dependency graph that Dagger 2 generates. In this case, each module has a default constructor,
so there’s really no need to do it this way, and create()
would suffice.
So the one line in the Main
constructor can be replaced with the following:
public Main() {
UrlShortenServiceComponent component = DaggerUrlShortenServiceComponent.builder()
.networkClientModule(new NetworkClientModule())
.urlShortenServiceModule(new UrlShortenServiceModule())
.serviceProviderModule(new ServiceProviderModule())
.build();
service = component.urlShortenService();
}
Modules #
The modules we create (which are simply classes annotated w/ @Module
) enable the following:
-
NetworkClientModule
- Creation of theOkHttpClient
instance, and providing it to Dagger 2 so that it can inject this where needed (it’s needed inUrlShortenServiceImpl
). -
ServiceProviderModule
Creation of the desired service providers that actually shorten the long URLs. We have two:google.com
andtinyurl.com
. This module names them both via the@Named
annotation, so that the specific one can be used where needed (by theUrlShortenServiceModule
). -
UrlShortenServiceModule
- This takes dependency objects provided by the two modules above and selects which service to use (tinyurl.com
orgoogle.com
) in order to provide an object thatMain
can use (which is an instance ofUrlShortenServiceaImpl
class). It also chooses which class should implement the interface (UrlShortenService
) in case there is more than one implementation (in this example there’s only one).@Injects
andUrlShortenServiceImpl
- An instance of this class is the object that will actually provide the implementation ofUrlShortenService
, that is required byMain
to turn a given long URL into a short one. The@Inject
tag used in the constructor takes objects available from the modules in the section above, and assembles them into the implementation that will be used byMain
. This tag also provides the object that’s created to be served by it’s module to Dagger 2.- Note that this class is not aware of the configuration (via
@Named
) that allowstinyurl.com
orgoogle.com
to be selected. However, the module which exposes this class is aware of this and makes the choice!
Component #
The UrlShortenServiceComponent
is very simple. The hard work is done by the modules that it
composites.
@Singleton
@Component(modules = {NetworkClientModule.class,
ServiceProviderModule.class,
UrlShortenServiceModule.class})
interface UrlShortenServiceComponent {
UrlShortenService urlShortenService();
}
Android example #
The Android example is just a port of the Complex Java Example to Android, with some minor modifications.
Here’s a link to the repo.
Field injection #
The biggest change required for Android is that field injection is needed since Android components (Activity, Fragment, Service, etc) are created by the Android system itself, and this is the only way for Dagger to hook into the creation lifecycle, since constructors for Android components aren’t available.
Dagger 2 Component (interface) needs a method that accepts an Activity instance #
Thus in onCreate()
a method has to be called on the Dagger 2 component (that’s almost identical to
the one from the Complex Java example), so that Dagger 2 can populate all the fields marked with
@Inject
. In order to do this, an instance of the Android component (strongly typed) has to be
passed to the Dagger 2 component. This is done by simply having a single line in the Dagger 2
Component class, which Dagger 2 will create the appropriate source code for. Note that if you have a
Fragment, or a Service, you have to make a method declaration in the interface for each of these
types.
Dagger 2 Component changes #
Here’s the code that goes in the Dagger 2 Component.
@Singleton
@Component(modules = {NetworkClientModule.class,
ServiceProviderModule.class,
UrlShortenServiceModule.class})
interface UrlShortenServiceComponent {
UrlShortenService urlShortenService();
void injectObjectsIntoFieldsOf(MainActivity activity); // <- Needed for Android
}
Activity code #
Here’s the code that goes into the Activity that relies on objects created by the Dagger 2 Component.
public class MainActivity extends AppCompatActivity {
@Inject
UrlShortenService service; // <- This is where Dagger 2 will make
// the injection of an object it
// created into the field
private TextView textOutput;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
loadComponent();
setupUI();
}
private void loadComponent() {
DaggerUrlShortenServiceComponent // <- This is called while in onCreate()
.create() // and it allows the @Inject into the
.injectObjectsIntoFieldsOf(this); // field above possible.
}
...
}
References #
- Excellent tutorial on Dagger 2 and Android tutorial
- SO - @Named
- SO - @Inject constructor params
- OkHttp maven
👀 Watch Rust 🦀 live coding videos on our YouTube Channel.
📦 Install our useful Rust command line apps usingcargo install r3bl-cmdr
(they are from the r3bl-open-core project):
- 🐱
giti
: run interactive git commands with confidence in your terminal- 🦜
edi
: edit Markdown with style in your terminalgiti in action
edi in action