Dependency injection in GWT using Dagger 2
Dependency injection is a software development concept where objects are provided with all the objects or values than they need for creation. GWT users have been familiar with GIN, but this last has been deprecated and is not supported anymore, so applications using GIN currently need really to say farewell. Dagger is the new dependency injection framework for GWT. For those unfamiliar with the framework, Dagger was aimed to provide DI for Android, but is now used for general purpose DI. It was adapted to GWT as well. In this post, we will do a brief introduction to Dagger and how to set up DI for a GWT project using Dagger.
What’s in it for GWT?
Unlike GIN, which uses Generators ( which will be removed from GWT some time in the future), Dagger uses compile time annotation processors. Projects using Dagger will go through less trouble when upgrading the GWT version. On the other hand, DI usually introduces complexity, so it’s kind of difficult to debug errors happening during the injection. GIN stack traces are known to be unreadable sometimes. One of Dagger’s goals is to reduce this shortcoming. Dagger’s generated code is close to code written by human, so understanding what happens under the hood can be easier, and therefore the developer will have less headaches when debugging.
Using Dagger in a GWT project:
-
- Dependencies
<dependency> <groupId>javax.inject</groupId> <artifactId>javax.inject</artifactId> <version>1</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.google.dagger</groupId> <artifactId>dagger-gwt</artifactId> <version>2.5</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.google.dagger</groupId> <artifactId>dagger-compiler</artifactId> <version>2.5</version> <scope>provided</scope> </dependency>
Dagger requires javax.inject annotations to be on the classpath when compiling. Moreover, the Dagger module needs to be added to the .gwt.xml:
<inherits name="dagger.Dagger"> </inherits>
- Annotation processor
- Dependencies
If you are using maven then, you need to use a version higher than 3.5.1 of the compiler plugin if you want the annotation compiler to be automatically executed when the compile goal is called. Otherwise you will need to specify both annotationProcessors and annotationProcessorsPaths in the plugin configuration. Optionally, the dagger-compiler compiler can be removed from dependencies and added to annotationProcessorsPaths, as specified by Thomas Broyer in SO :
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.5.1</version> <configuration> <source>1.7</source> <target>1.7</target> <annotationProcessorPaths> <path> <groupId>com.google.dagger</groupId> <artifactId>dagger-compiler</artifactId> <version>${dagger.gwt.version}</version> </path> </annotationProcessorPaths> </configuration> </plugin>
It’s also worth noting that, under dev mode, the annotation processor need to be rerun each time the injected classes change. Under maven, the annotation processor can be run using process-classes goal. I have not tested Gradle, but the concepts should be the same using Gradle as well.
-
- Simple dependency injection
Suppose that we have a service that compresses images. The service depends on two other services: a service that downloads the image, and a service that uploads the image after compressing. All the objects have zero args constructors.
public class ImageCompressor { @Inject public ImageDownloader downloader; @Inject public ImageUploader uploader; @Inject public ImageCompressor(){ } public void compress(String url) { downloader.download(url); GWT.log("compressing image"); uploader.upload(url); } }
public class ImageDownloader { @Inject public ImageDownloader() { } public void download(String url) { GWT.log("downloading image at " + url); } }
public class ImageUploader { @Inject public ImageUploader() { } public void upload(String url) { GWT.log("uploading compresesed image at " + url); } }
-
- Defining a module
if you need special setup for constructing an object such as setting some values, or specifying constructor arguments then you need to create a module. Suppose that we need to supply a timeout value for our ImageDownloader Object:
public class ImageDownloader { int timeout; //@Inject we cannot use inject on the constructor anymore public ImageDownloader(int timeout) { this.timeout = timeout; } public void download(String url) { GWT.log("downloading image at " + url); } }
Then we will need to specify a module that provides our ImageDownloader:
@Module public class ImageCompressionModule { @Provides public ImageDownloader getImageDowloader(){ return new ImageDownloader(15); }
-
- Defining the App component
Now that we defined our module, and objects, we will create the DI component that will be used to obtain injected objects instances.
@Component(modules=ImageCompressionModule.class) public interface AppComponent { ImageCompressor getImageCompressor(); }
-
- Using the injected objects
An instance of our app component can be obtained in the following way:
AppComponent component = DaggerAppComponent.builder() .imageCompressionModule(new ImageCompressionModule()) .build();
If you are using an IDE, then you will notice that it complains about the DaggerAppComponent. This is pretty normal because DaggerAppComponent is only available after running the annotation processor.
finally, we can use our object :
ImageCompressor compressor = component.getImageCompressor(); compressor.compress("http://www.g-widgets.com/GWTcon.jpg");
Result:
downloading image at http://www.g-widgets.com/GWTcon.jpg compressing image uploading compressed image to http://www.g-widgets.com/GWTcon.jpg
Wrap-up:
Dagger 2 is the next generation dependency injection for GWT. We have seen basic features of the framework in this post. More advanced DI features can be found in Dagger’s main users guide: https://google.github.io/dagger/users-guide. Dagger’s GWT version works in the same way as the backend version: the code can work on both client and server side, so it may be useful to port the DI to the backend in case there are issues to benefit from debugging in the JVM.
Full code is available at: https://github.com/zak905/dagger2-gwt-example
Reference: | Dependency injection in GWT using Dagger 2 from our JCG partner Zakaria Amine at the G-Widgets blog. |