Closed soniccat closed 3 years ago
@soniccat I don't quite follow what you're asking and the connection between subcomponents and multi-module projects. Do you have an example or sample project with Cleanse to describe what you're trying to accomplish?
@sebastianv1 Let me paraphrase. I'm about the difference between using subcomponents and dependencies in Dagger2. Here a good explanation about these 2 approaches: https://stackoverflow.com/a/30135139/191027. I got how to do "subcomponents" approach using Cleanse but I didn't find the way for "component dependencies" approach. In my opinion "component dependencies" approach is the right way to do DI in multi-module projects and that's why I was interested in figuring out how to do it using Cleanse.
I've researched the topic a bit. I created a project with several sub projects. The idea was to put a feature in a different project. I started with creating a subcomponent per project here (subcomponents git tag): https://github.com/soniccat/CleanseWithMultiModuleTest/tree/subcomponents (FeatureA and FeatureB). Then I tried to remove the dependency between AppComponent and FeatureAComponent to achieve "component dependencies" relationship. I read the doc more carefully and figured out that "Assisted Injection Feature" seemed the right way to do the "component dependencies" Dagger2 approach. So, that allowed me to write let featureAVC = factoryA.build(app)
instead of let featureAVC = app.featureAVC()
. The diff is here https://github.com/soniccat/CleanseWithMultiModuleTest/commit/05737ebcacfa68ce55692775644ad56204a358e0.
Thank you for your interest :)
@soniccat I think I follow and believe you're asking about the Seed
type that you can pass into a subcomponent?
When you create a subcomponent, you can specify this by setting the Seed
associated type. For example:
struct Dependencies {
let email: String
}
struct MySubcomponent: Cleanse.Component {
typealias Seed = Dependencies
typealias Root = ...
// ...
}
And then when you want to build your subcomponent with the ComponentFactory<MySubComponent>
, you would be required to pass an instance of Dependencies
.
class App {
let subcomponentFactory: ComponentFactory<MySubcomponent>
// ...
func onTap() {
let dependencies = Dependencies(email: "me@gmail.com")
let subcomponentRoot = subcomponentFactory.build(dependencies)
}
}
Assisted Factory solves a similar issue except that whatever you pass into the build
function dependencies aren't stored into the dependency graph while the Seed
on a subcomponent is bound into the subgraph for their subcomponent. Hopefully this helps!
@sebastianv1 yeah, that what I ended up with here https://github.com/soniccat/CleanseWithMultiModuleTest. As I wasn't aware of Guice, "Assisted Injection" term was confusing for me. But now everything is clear.
Thanks for your help
@sebastianv1 sorry for coming back to the same topic. Is it possible now to somehow attach the properties of a seed into a root component dependency graph?
Now I have DefinitionsDeps argument in the init of DefinitionsViewController and manually create DefinitionsVM from DefinitionsDeps. Is it possible to somehow get a newly created DefinitionsVM in the init of DefinitionsViewController without changing DefinitionsDeps and the Rood and the Seed of DefinitionsComponent?:
public protocol DefinitionsDeps {
var connectivityManager: ConnectivityManager { get }
var wordRepository: WordRepository { get }
var idGenerator: IdGenerator { get }
}
public struct DefinitionsComponent: RootComponent {
public typealias Root = DefinitionsViewController
public typealias Seed = DefinitionsDeps
public static func configureRoot(binder bind: ReceiptBinder<DefinitionsViewController>) -> BindingReceipt<DefinitionsViewController> {
bind.to(factory: DefinitionsViewController.init)
}
public static func configure(binder: Binder<Unscoped>) {
}
}
public class DefinitionsViewController: UIViewController, UICollectionViewDelegateFlowLayout {
let vm: DefinitionsVM
init(deps: DefinitionsDeps) {
let vm = DefinitionsVM(
connectivityManager: deps.connectivityManager,
wordRepository: deps.wordRepository,
idGenerator: deps.idGenerator,
state: DefinitionsVM.State(word: nil)
)
self.vm = vm
super.init(nibName: "DefinitionsViewController", bundle: Bundle.main)
}
...
}
Now I want to achieve this:
public class DefinitionsViewController: UIViewController, UICollectionViewDelegateFlowLayout {
let vm: DefinitionsVM
init(vm: DefinitionsVM) {
self.vm = vm
super.init(nibName: "DefinitionsViewController", bundle: Bundle.main)
}
...
}
and I'd expect that I'd need to add something like that... but that's just my assumption:
binder.bind(DefinitionsVM.self).to {
DefinitionsVM(
connectivityManager: $0,
wordRepository: $1,
idGenerator: $2,
state: DefinitionsVM.State(word: nil))
}
@soniccat That's exactly correct! You'd simply create a binding for DefinitionsVM
. The Seed
type DefinitionsDeps
is available within your object graph.
So in your DefinitionsComponent
you would add the binding to the configure(binder:)
function
public struct DefinitionsComponent: RootComponent {
public typealias Root = DefinitionsViewController
public typealias Seed = DefinitionsDeps
public static func configureRoot(binder bind: ReceiptBinder<DefinitionsViewController>) -> BindingReceipt<DefinitionsViewController> {
bind.to(factory: DefinitionsViewController.init)
}
public static func configure(binder: Binder<Unscoped>) {
binder
.bind(DefinitionsVM.self)
.to { (seed: DefinitionsDeps) in
return DefinitionsVM(connectivityManager: seed.connectivityManager, wordRepository: seed.wordRepository, idGenerator: seed.idGenerator, state: DefinitionsVM.State(word: nil))
}
}
This binding would you allow you to directly inject this into your view controller. Once an object is "bound" into a a component, you may freely inject it anywhere within that component or a subcomponent can inject it. However, a parent subcomponent cannot inject a dependency that is bound from within a subcomponent.
@sebastianv1 wow, that works, thanks
Hi. I've tried this lib in iOS and got a question about problems people have with using subcomponents in multi-module projects in Android. This article is a good example: https://proandroiddev.com/using-dagger-in-a-multi-module-project-1e6af8f06ffc. In short, Android devs use "dependencies" feature from Dagger2 instead. They create a separate dependency interface and pass it to create new a feature component. That allows to remove the connection between the AppComponent and a Sub Component. There is my example: https://github.com/soniccat/WordTeacher/blob/main/androidApp/src/main/java/com/aglushkov/wordteacher/androidApp/features/di/DefinitionsComponent.kt. How would you do the same with Cleanse?
Probably for Cleanse that's not a problem and you use it in multi-module projects without any issues. Could you explain the right way of using Cleanse in this case?