0

In my current Android project I have a feature A that display feature B, and now I need to be able to display feature A from feature B. Which create a circle feature dependency, generating a StackOverflow error on build time.

@Subcomponent(modules = [SubComponentA.Module::class])
interface SubComponentA {

    fun plus(module: Module): SubComponentB

    @dagger.Module
    class Module {
        // Provide stuff
    }
}

-------------

@Subcomponent(modules = [SubComponentB.Module::class])
interface SubComponentB {

    fun plus(module: Module): SubComponentA

    @dagger.Module
    class Module {
        // Provide stuff
    }
}

Is there a way to achieve this Dagger graph without a build time error? Thanks!

Guimareshh
  • 1,214
  • 2
  • 15
  • 26
  • How strong is the dependency between features A and B? Do they just need to be able to create each other, or does feature B require dependencies that are scoped to feature A and vice versa? – Nitrodon Aug 16 '22 at 21:12
  • If A produces B and B produces A, I imagine it would be difficult to get an instance of either one to act as the other's parent. Is there anything to be gained by having A act as B's parent (sub)component? Why not have your Component inherit from both of them and provide the factory? – Jeff Bowman Aug 17 '22 at 05:01
  • Maybe try something with Lazy? https://stackoverflow.com/questions/44709685/how-to-resolve-a-circular-dependency-while-still-using-dagger2 – Yavor Mitev Aug 17 '22 at 07:12
  • To add more context: Feature A is an article that can open another article or a Feature B, which is a detail view of a Hike. Inside the Feature B (Hike detail) we can access to an article (Feature A) and so on. – Guimareshh Aug 17 '22 at 11:24

1 Answers1

1

If A produces B and B produces A, I imagine it would be difficult to get an instance of either one to act as the other's parent. That's not a problem, though: Dagger components do not have to represent the exact same ownership and access chain that your model objects or application UI represents. The important part of the Dagger graph is whether the objects you want are directly injectable and whether they have the correct Dagger-managed lifetime ("scope").

You clarified in the comments:

To add more context: Feature A is an article that can open another article or a Feature B, which is a detail view of a Hike. Inside the Feature B (Hike detail) we can access to an article (Feature A) and so on.

If the Article and Hike aren't directly related to each other in a nesting or ownership sense—you might start the app and navigate directly to either Articles or Hikes—then I would have the main Component act as the owner of both Subcomponents, such that neither Subcomponent is the parent of the other. Because Subcomponents can access all the bindings of their parent component tree, you'll be able to inject a SubcomponentA builder/factory1 from Component, SubcomponentA, or SubcomponentB, and you'll likewise be able to inject a SubcomponentB builder/factory from Component, SubcomponentA, or SubcomponentB. You won't be able to get to SubcomponentA bindings from SubComponentB (i.e. get to Article subcomponent Dagger bindings from the Hike subcomponent) or vice versa, but of course you can use a Module field or @BindsInstance binding to pass details about the Article or Hike you just navigated from. You could even pass the subcomponent instance itself, but in your position I'd probably just keep data model objects or identifiers to avoid keeping a long memory-expensive chain of objects.

If it is the case that Articles have zero or more Hikes and every Hike has exactly one Article, and that the Hike has reason to directly access all the Dagger bindings ("ArticleInteractionLogger", maybe) associated with its parent Article, then that's a good reason that SubcomponentB would be a subcomponent of SubcomponentA. However, then you won't be able to get to a Hike (SubcomponentB) instance without first getting an Article (SubcomponentA) instance, and navigating to a different Article means you would not inject the bindings directly from the Hike subcomponent you were just in.

All that said, it sounds like your motivation for subcomponents is cross-navigation, in which case I'd just leave the Dagger object graph out of it, have both Subcomponents installed on the parent Component, and save the history elsewhere—as subcomponent @BindsInstance fields or in a separate NavigationHistoryManager class of your own design.


Note 1: You're using the plus abstract factory method model from Dagger 1, but it is more idiomatic to define a Builder or Factory that you can directly inject. This avoids having to keep or inject the Component or Subcomponent instance directly to get to the plus method (which could be named anything). However, to use this you'll need to specify the Subcomponent in the subcomponents attribute of the @Module annotation for a Module on your parent Component.

@Subcomponent(modules = [SubComponentA.Module::class])
interface SubComponentA {

    // Remove: fun plus(module: Module): SubComponentB

    @dagger.Module class Module { /* ... */ }

    @Subcomponent.Factory
    interface Factory {
        fun create(module: Module): SubComponentA
    }
}

@Subcomponent(modules = [SubComponentB.Module::class])
interface SubComponentB {

    // Remove: fun plus(module: Module): SubComponentA

    @dagger.Module class Module { /* ... */ }

    @Subcomponent.Factory
    interface Factory {
        fun create(module: Module): SubComponentB
    }
}
Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
  • 1
    Thank you @Jeff Bowman! I used the solution to have a parent component hosting the two SubComponents, and it works like a charm. About the Factory/Builder, it's part of a migration I need to address in my project later on! – Guimareshh Aug 23 '22 at 14:46