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:

  1. NetworkClientModule - Creation of the OkHttpClient instance, and providing it to Dagger 2 so that it can inject this where needed (it’s needed in UrlShortenServiceImpl).

  2. ServiceProviderModule Creation of the desired service providers that actually shorten the long URLs. We have two: google.com and tinyurl.com. This module names them both via the @Named annotation, so that the specific one can be used where needed (by the UrlShortenServiceModule).

  3. UrlShortenServiceModule - This takes dependency objects provided by the two modules above and selects which service to use (tinyurl.com or google.com) in order to provide an object that Main can use (which is an instance of UrlShortenServiceaImpl 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).

    1. @Injects and UrlShortenServiceImpl - An instance of this class is the object that will actually provide the implementation of UrlShortenService, that is required by Main 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 by Main. This tag also provides the object that’s created to be served by it’s module to Dagger 2.
    2. Note that this class is not aware of the configuration (via @Named) that allows tinyurl.com or google.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

Related Posts