Search code examples
kotlindagger-2

Can you configure Dagger2 to create Component.Factory instead of Component.Builder by default?


I have a dependency graph that consists of many dagger components; divided in 4 layers, and the components depend on parent components as well.

When I define a component; I configure their dependencies, sometimes its just 1 dependency, and sometimes its even up to 6 parent components. Often those parent components are the only dependencies for that new component, so dagger creates a Component.Builder for me which I can use to instantiate the component.

However, with a Component.Builder, as far as I am aware, you dont get compile time checks to validate you provided all requirements in the Builder. With Component.Factory you do get those compile time checks.

So my question, is there a possibility to make the default approach also compile time safe? For example by forcing dagger to generate a factory class instead of a builder class by default?

Example of when I would get a runtime exception as opposed to a compile time error:

// Define 3 layers of components
@AppScope
@Component()
interface AppComponent {
    fun provisionSomething(): Something
}

@EnvironmentScope
@Component(
    dependencies = [AppComponent::class]
)
interface EnvironmentComponent {
    fun provisionEnvironment(): Environment
}

@ActivityScope
@Component(
    dependencies = [AppComponent::class, EnvironmentComponent::class]
)
interface FeatureComponent {
    fun inject(viewModel: MyViewModel)
}

// create components
fun getAppComponent(): AppComponent = TODO("not relevant for example")
fun getEnvironmentComponent(): EnvironmentComponent = TODO("not relevant for example")
fun getFeatureComponent(): FeatureComponent {
    return DaggerFeatureComponent
        .builder()
        .appComponent(getAppComponent())
        //.environmentComponent(getEnvironmentComponent()) // This will compile just fine, but crash only runtime
        .build()
}

As shown, when you forget to update the instantiation of the component, make sure all relevant builder methods are called, the compile time safety is gone.

If i would reconfigure my FeatureComponent to create a Factory myself, I have compiletime safety again. However working in a team, I would like a way to better force this compile time safety; e.g. with a compiler flag that changes the default behavior, or maybe something else.

Thanks.


Solution

  • No, this isn't currently supported, and if it were to exist it would have downsides.

    First and foremost: Ideally we shouldn't be relying on auto-generated Builders in the first place, let alone Factories. As in google/dagger#935, source code generators that generate APIs are seen as harder to use than generators that generate implementations, which is one of the things that influenced the Dagger design that you write an interface (component, subcomponent, builder, factory, etc) and Dagger generates an implementation of it. One of the reasons for this is that for generated interfaces your IDE or build system will need to run the source code generator to determine the API surface itself; if the system only generates implementations, the interface can still be determined without invoking the whole source code generator. Dagger does provide a Builder by default, but the benefits of providing your own (naming control, @BindsInstance, documentation, IDE support) are strong suggestions to write your own anyway.

    Also:

    • There isn't a natural ordering for modules. It could be by simple class name alphabetical order, FQCN alphabetical order, or something else entirely. In order to tell which order to add a new module or dependency, you'd have to check the generated code. (I originally suggested module traversal order as well, depth-first or breadth-first, but imagine how jarring it would be to have the argument order change on you if module order or transitive inclusion were to change.)

    • Modules can be optional, particularly for instantiable modules with default constructors. The current implementation of Factory requires you to pick and does not support multiple factory methods, though the design doc is open to that as an extension. So it is less true for Factories than Builders that there is a single predictable-enough implementation for Dagger to write it for you.

    • Builder and Factory cannot currently coexist, and if they did they'd have to be tracked for consistency about which @BindsInstance bindings they would need to make available. Those are currently defined on either the Builder or Factory.

    • If you were to make a configuration option for Builders vs Factories given that they can't currently coexist, you would need to define it per-class verbosely, or define it per-build and hope nobody is depending on generated Builders and generated Factories in the same build. (You could do both, but it'd make it even more complicated, particularly for reusable modules/components/libraries.)

    In summary, auto-generated factories would have some major usability concerns. They are not currently supported and (though I am not on the Dagger team and am not speaking in any official capacity) I would be surprised if they were anytime soon.


    FWIW, I didn't find a feature request for auto-generated Factory in the google/dagger issues list, but the Factory feature itself was proposed as google/dagger#1317 and has a posted design doc that discusses the applicability and non-applicability of Factory vs Builder.