Avoid Nullable dependencies in Dagger2 with @BindsOptionalOf

TL;DR: This blog describes how to gracefully handle @Nullable constructor argument in injectable class, by using the lesser known @BindsOptionalOf feature of Dagger2.

Let’s take a simple ApiService class, that accepts two arguments.
Notice that the second constructor argument of Logger is Nullable.

public class ApiService {

@NonNull private Context mContext;
@Nullable private Logger mLogger;

@Inject
public ApiService(
@NonNull Context context,
@Nullable Logger logger) {
...
}

public void loadData() {
// Make network request here..
if (mLogger != null) {
mLogger.log("Loading complete.");
}
}
}

Feature 1: (Wants logging, so passes correct Logger implementation)

@Module
public interface Feature1Module {
@Binds
Logger bindLogger(Feature1Logger feature1Logger);
...
}

Feature 2: (Doesn’t want logging, so passes null implementation)

@Module
public abstract class Feature2Module {
@Provides
@Nullable

Logger provideLogger() {
return null;
}
...
}

Great, Both Features are happy, as dependencies are properly met. 
But we introduced Nullablility in our ApiService 😢 . Which means everytime we access nullable mLogger field, we need to make sure we wrap it in null check to avoid nasty NullPointerExceptions.

Yes I know, modern IDEs and findBugs tools can easily rescue us from above situations. But using Optional instead of null is a better choice. Many functional programming libraries don’t even allow us to pass null values in the system, and Optional comes really handy dealing with these cases. Eg. RxJava2.

So, Let’s modify our ApiService to get rid of the nasty Nullability and make it support @NonNull Optional instead.

public class ApiService {

@NonNull private Context mContext;
@NonNull private Optional<Logger> mOptionalLogger;

@Inject
public ApiService(
@NonNull Context context,
@NonNull Optional<Logger> optionalLogger) {
...
}

public void loadData() {
// Make network request here..
if (mOptionalLogger.isPresent()) {
mOptionalLogger.get().log("Loading complete.");
}
}
}

Does that mean, now we need to provide Optional<Logger>, instead of just providing Logger in our object graph? 
No.

Dagger2 provides a neat little utility to support doing this out of the box. (Available in Dagger≥2.7). It’s done using @BindsOptionalOf
We need to create a module that provides this Optional binding.

@Module
public interface ApiServiceModule {
@BindsOptionalOf ApiService bindApiServiceOptional();
}

We need to include above module to whichever object graph we want to make ApiService dependency available.

Let’s see how our feature modules change:

Feature1: Include ApiServiceModule, and keep the correct logger binding as it was.

@Module(includes = ApiServiceModule.class)
public interface Feature1Module {
@Binds
Logger bindLogger(Feature1Logger feature1Logger);
...
}

Feature2: Include ApiServiceModule, and remove Nullable provides

@Module(includes = ApiServiceModule.class)
public abstract class Feature2Module {
// @Nullable @Provides method removed!!
...
}

What happens at Runtime:

Feature1:
mLoggerOptional.isPresent() -> returns true
mLoggerOptional.get() -> returns correct Feature1Logger instance.

Feature2:
mLoggerOptional.isPresent() -> returns false
mLoggerOptional.get() -> shouldn’t be invoked.

What can I inject:

You can inject all the below types in your graph, Please refer official documentation for more details. 
It supports both com.google.common.base.Optional and java.util.Optional.

Optional<Foo>
Optional<Provider<Foo>>
Optional<Lazy<Foo>>
Optional<Provider<Lazy<Foo>>>

Thank you for reading, please share the link to spread the knowledge ❤. Click on 👏 if you enjoyed reading this.