Optional Bindings¶
Dagger's @BindsOptionalOf lets a dependency be optional: consumers inject Optional<T> and
decide at runtime what to do when no implementation was provided. This is useful for debug-only
tools, optional integrations, and flavor-specific implementations.
Without Hilt AutoBind you write a module by hand for every optional type:
@Module
@InstallIn(SingletonComponent::class)
interface DebugToolsModule {
@BindsOptionalOf
fun optionalDebugPanel(): DebugPanel
}
Usage¶
Annotate the type that may or may not be provided:
The processor generates:
@Module
@InstallIn(SingletonComponent::class)
internal interface DebugPanel__OptionalModule {
@BindsOptionalOf
public fun optionalDebugPanel(): DebugPanel
}
Consumers inject Optional<DebugPanel>:
import java.util.Optional
class MainViewModel @Inject constructor(
private val debugPanel: Optional<DebugPanel>,
) : ViewModel() {
fun openDebugPanel() {
debugPanel.ifPresent { it.show() }
}
}
If some module in the build provides a DebugPanel, the Optional is present; otherwise it is
empty. Nothing else has to change.
Note
java.util.Optional requires minSdk 24 or core library desugaring. Dagger also accepts
com.google.common.base.Optional, which works wherever Guava is on the classpath, if your
minSdk is lower.
Where the annotation goes¶
@AutoBindsOptionalOf is applied to the optional type itself: an interface or an abstract
class, not to an implementation:
@AutoBindsOptionalOf
interface DebugPanel // correct
@AutoBindsOptionalOf
class DebugPanelImpl // compile error: use @AutoBinds instead
The annotated type must not declare type parameters: a generic type has no single Dagger key.
object declarations, enum class, and inner classes are rejected for the same reason as
concrete classes: none of them describes a type that can be optionally provided.
Choosing the component¶
Component selection works exactly as it does for @AutoBinds:
// explicit component
@AutoBindsOptionalOf(installIn = HiltComponent.Activity)
interface DebugPanel
// custom component defined with @DefineComponent
@AutoBindsOptionalOf(installInCustomComponent = MyCustomComponent::class)
interface DebugPanel
// component inferred from the scope annotation
@AutoBindsOptionalOf
@ActivityScoped
interface DebugPanel
Without any of these, the module is installed in SingletonComponent.
Note
A scope annotation only selects the component. Dagger forbids scoped @BindsOptionalOf
declarations, so the generated function never carries a scope annotation.
Qualifiers¶
Qualifiers on the type are forwarded to the generated declaration, so you can make a qualified dependency optional:
Consumers then inject @Named("debug") Optional<DebugPanel>.
Annotation aliases¶
@AutoBindsOptionalOf works as a meta-annotation, including across modules, see
Annotation Aliases:
@AutoBindsOptionalOf(installIn = HiltComponent.Activity)
@Target(AnnotationTarget.CLASS)
annotation class ActivityOptional
@ActivityOptional
interface DebugPanel
Combining with @AutoBinds¶
Declaring a type optional does not prevent it from being provided. A type can be declared optional
in one module and bound with @AutoBinds in another - consumers simply receive a present
Optional. This is the point of the feature: the consuming code does not need to know which build
variants supply an implementation.