Fallback / default value for @BindsInstance object

Viewed 31
// Previously, our Api looks like this:
@Component(modules = ApiManagerModule.class)
public interface ApiComponent {
  
  @Component.Builder
  interface Builder {
    ApiComponent build();
  }

  ApiManager apiManager();
}

@Module
public interface ApiManagerModule {
  @Binds
  ApiManager bindApiManager(ApiManagerImpl impl);
}

// User uses our API like:
ApiComponent apiComponent = 
    DaggerApiComponent.builder().build();
ApiManager apiManager = apiComponent.apiManager();
apiManager.doApiThings();

For some reason, we want to add an option object to our api. We made following changes:

@Component(modules = ApiManagerModule.class)
public interface ApiComponent {
  
  @Component.Builder
  interface Builder {
    ApiComponent build();
    
    // **new**
    @BindsInstance
    Builder setApiOptions(ApiOptions options);
  }

  ApiManager apiManager();
}

@Module
public interface ApiManagerModule {
  @Binds
  ApiManager bindApiManager(ApiManagerImpl impl);
}

// **new**
@AutoValue
public abstract class ApiOptions {
  //...
  
  @AutoValue.Builder
  public abstract static class Builder {
    //...
  }
}

// User will now use our Api like:
ApiComponent apiComponent = DaggerApiComponent.builder()
    .setApiOptions(
        ApiOptions.builder()
            /*setters*/
            .build());
ApiManager apiManager = apiComponent.apiManager();
apiManager.doApiThings();

However, one issue is this is a breaking change. Users already integrated to our API has to make changes to set the @BindsInstance option object.

My question is, is it possible to provide fallback value for the @BindsInstance option object? We want to have something like this:

ApiComponent apiComponent = 
    DaggerApiComponent.builder()
    // when `setApiOptions()` is not called, a default ApiOptions is created and provided.
    .build();
ApiManager apiManager = apiComponent.apiManager();
apiManager.doApiThings();
1 Answers

You could make @BindsInstance @Nullable:

For builders, @BindsInstance methods must be called before building the component, unless their parameter is marked @Nullable, in which case the component will act as though it was called with a null argument. Primitives, of course, may not be marked @Nullable.

At that point you would need to @Inject a @Nullable ApiOptions to help Dagger understand that the value could be null, or you could create a general fallback here:

private static final ApiOptions DEFAULT_OPTIONS = ApiOptions.builder()
    /* ... */
    .build();

/** Define a qualifier. You could also use @Named("defaults") etc. */
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public static @interface WithDefaults {}

@Provides
@WithDefaults
ApiOptions provideApiOptions(@Nullable ApiOptions options) {
  if (options == null) return DEFAULT_OPTIONS;
  return options;
}

Naturally, you could also do the reverse, using @BindsInstance @Nullable @HasDefaults options so you can inject an unqualified non-nullable ApiOptions where you need it:

@Qualifier @Retention(RetentionPolicy.RUNTIME) public static @interface HasDefaults {}

@Provides ApiOptions provideApiOptions(@Nullable @HasDefaults ApiOptions options) {
  if (options == null) return DEFAULT_OPTIONS;
  return options;
}
Related