Paco1
Paco1

Reputation: 818

How to use Dagger 2 BindsOptionalOf

I am building an Android app using Dagger 2 which supports Bluetooth if it's available. I want to inject the BluetoothAdapter dependency with Dagger.

I know about one way of injecting null values with Dagger, which is to annotate the Provider method in the Module, the dependency declaration in the Component and the parameter at the injection site with @Nullable. But to make it clearer that the BluetoothAdapter is an optional dependency (the rest of the app can also work without BT and should also work on the emulator), I wanted to declare the dependency as an Optional<BluetoothAdapter> as described in the official docs.

I have a Provider method in my module:

@Provides
static BluetoothAdapter providesBluetoothAdapter(MainApplication application) {
    ...
}

and a corresponding declaration in the component:

BluetoothAdapter bluetoothAdapter();

As per the instructions, I changed the injection sites to Optional<BluetoothAdapter>, made my module abstract and added the following abstract method in the module:

@BindsOptionalOf abstract BluetoothAdapter optionalBluetoothAdapter();

However it still fails with a java.lang.NullPointerException: Cannot return null from a non-@Nullable @Provides method exception when run on an emulator. That's when I thought that maybe I misunderstood the purpose of @BindsOptionalOf and removed the BluetoothAdapter bluetoothAdapter(); declaration from my component to see if it depends on whether the dependency is declared in the component. Still doesn't work.

What am I missing? Is it possible to accomplish what I am trying to do with an optional binding since methods annotated with @BindsOptionalOf must be abstract?

Upvotes: 5

Views: 5682

Answers (1)

Jeff Bowman
Jeff Bowman

Reputation: 95704

You're not quite using @BindsOptionalOf the way it's meant to be used. @BindsOptionalOf is best for bindings that may be present or absent at compile time, and as such is best for reusable modules. It won't help you for runtime presence or absence.

Let's say that you are building a reusable library:

@Module public abstract class BluetoothAdapterModule {
  @Provides
  static BluetoothAdapter providesBluetoothAdapter(MainApplication application) {
    ...
  }
}

@Module public interface FooModule {
  // This consumes BluetoothAdapter when it happens to be present.
  @BindsOptionalOf BluetoothAdapter bindOptionalBluetoothAdapter();

  // Assume the Foo implementation injects Optional<BluetoothAdapter>.
  @Binds Bar bindBar(Foo foo);
}

Here, when FooModule and BluetoothAdapterModule are bound into the same Component, there will be a binding for BluetoothAdapter. In a different Component or application where you haven't included BluetoothAdapterModule, there would be no binding for BluetoothAdapter, and if you were to depend on BluetoothAdapter directly from Foo (including @Nullable BluetoothAdapter) your compilation would fail.

However, with @BindsOptionalOf, you can rely on Dagger to inform you whether or not your BluetoothAdapter is present: You inject Optional<BluetoothAdapter> instead. If the binding is available, the Optional will be "present" and get will return a BluetoothAdapter; and if it is not available, the Optional will be "absent". (Of course, as described in the @BindsOptionalOf docs, you can also inject Optional<Provider<BluetoothAdapter>> and Optional<Lazy<BluetoothAdapter>> and so forth to further control when and where your object is instantiated.)

In those cases, @BindsOptionalOf is required only to confirm to Dagger that it should manage the creation of that Optional instance; otherwise, it would throw a compile-time exception on the assumption that you've made a mistake and forgotten to bind an Optional instance you've created.

To reflect the presence or absence of Bluetooth known only at runtime, you'll need to use @Nullable or a separate injectable BluetoothAdapterHolder you write. You could also bind an Optional<BluetoothAdapter> explicitly as mentioned above, creating it explicitly with the presence or absence characteristics that match your needs. At that point, you wouldn't use @BindsOptionalOf because you're not relying on Dagger to reflect the presence/absence: You're controlling it yourself.

Upvotes: 19

Related Questions