Assisted Injection in Android

September 15, 2016

Dagger is a JSR-330 dependency injection library that allows you to split up the behaviour of your app from the creation of your dependencies. In most cases, you will be able to use Dagger to create and provide all of your dependencies. There are some situations, however, where you have one class that requires dependencies both from the Dagger’s object graph and the caller at runtime.

Viewholder

A RecyclerView.ViewHolder is an example of a case where we might like to use dependency injection, but can’t because a ViewHolder requires a View that is created at runtime.

// FooViewHolder.java
final class FooViewHolder extends RecyclerView.ViewHolder {
    private final ImageDownloader imageDownloader;

    FooViewHolder(View view, ImageDownloader imageDownloader) {
        super(view);
        this.imageDownloader = imageDownloader;
    }

    public void onBind(Foo item) {
        // Use imageDownloader and bind item to view.
    }
}
// FooAdapter.java
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int index) {
    View view = LayoutInflater.from(viewGroup.getContext())
        .inflate(R.layout.foo_view, parent, false);
    return new FooViewHolder(view, imageDownloader);
}

Ideally, we would be able to use Dagger to create and provide dependencies to our FooViewHolder. Unfortunately, FooViewHolder depends on a view that is not available in the object graph. So instead of using Dagger to create a FooViewHolder directly, we can use it to create a FooViewHolderFactory. This Factory will then allow us to build instances of FooViewHolder.

// FooViewHolderFactory.java
final class FooViewHolderFactory {
    private final ImageDownloader imageDownloader;

    @Inject
    FooViewHolderFactory(ImageDownloader imageDownloader) {
        this.imageDownloader = imageDownloader;
    }

    FooViewHolder create(View view) {
        return new FooViewHolder(view, imageDownloader);
    }
}

We can then use the FooViewHolderFactory to manage the creation of the FooViewHolder.

// FooAdapter.java
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int index) {
    View view = LayoutInflater.from(viewGroup.getContext())
        .inflate(R.layout.foo_view, parent, false);
    return fooViewHolder.create(view);
}

This pattern is known as Assisted Injection.

The only problem with this approach is that we now have the boilerplate around the FooViewHolderFactory. So, instead of writing the FooViewHolderFactory ourselves, let’s have robots do it for us!

AutoFactory

AutoFactory is one of the projects in Google’s Auto family. AutoFactory generates JSR-330 compatible factory implementations and was created primarily to solve the Assisted Injection problem.

Once we add the AutoFactory annotations to our ViewHolder class, it will look something like:

// FooViewHolder.java
@AutoFactory
final class FooViewHolder extends RecyclerView.ViewHolder {
    private final ImageDownloader imageDownloader;

    public FooViewHolder(View view, @Provided ImageDownloader downloader) {
        super(view);
        this.imageDownloader = imageDownloader;
    }

    public void onBind(Foo item) {
        // Use imageDownloader and bind item to view.
    }
}

AutoFactory then uses compile-time code generation to create our FooViewHolderFactory for us.

@Generated(value = "com.google.auto.factory.processor.AutoFactoryProcessor")
final class FooViewHolderFactory {
    private final Provider<ImageDownloader> imageDownloader;

    @Inject
    FooViewHolderFactory(Provider<ImageDownloader> imageDownloader) {
        this.imageDownloader = imageDownloader;
    }

    FooViewHolder create(View view) {
        return new FooViewHolder(view, imageDownloader.get());
    }
}

As you can see the generated version of FooViewHolderFactory looks almost identical to our hand-written version, all we had to do was add a couple of annotations to our existing class.

Why Not?

You can use Dagger too much, and this may well be the definition of too much. There is something to be said for keeping your code simple, abstractions like Dagger and AutoFactory can make the code harder to comprehend. As always, you should weigh up your particular use-case before applying this pattern.

TL;DR

Assisted Injection can be achieved using a combination of AutoFactory and your chosen JSR-330 dependency injection library. You can use Assisted Injection to provide dependencies from your object graph as well as runtime dependencies with minimal hand-written code.

Consider using Dagger and AutoFactory to create instances of classes that require dependencies from both the object graph and the caller at runtime.

See


Profile picture
Written by Angus Morton.
I builds things.