Making a subcomponent inherit depedencys from different components - dagger-2

Lately i've been working a lot with Dagger 2 and I'm having this particular use case that I'm not able to do the way I want to for different reasons:
1) It's not possible; 2) I'm just not understanding the concepts behind scopes and components/subcomponents.
I have 3 Components: AppComponent, DBComponent, ActivityComponent.
I need to have my activity to inherit dependencies from AppComponent and DBComponent. This is my following setup:
AppComponent:
#Singleton
#Component(modules = { AppModule.class }) {
EventBus bus()
ActivityComponent plus(ActivityModule activityModule);
}
DatabaseComponent:
#Database
#Component(dependencies = AppComponent.class, modules = DatabaseModule.class) {
ActivityDependency activityDependency();
}
ActivityComponent:
#PerActivity
#Subcomponent(modules = ActivityModule.class) {
void inject(MainActivity activity);
}
When I inject bus() everything works fine but as soon as I try to inject ActivityDependency() it simply does not work. I cannot figure out why...
Please note: I've read about almost all the posts in here and outside explaining scopes, components and subcomponents and still I can't get my head wrapping around how to achieve what I wanted.
Note 2: I know one way to fix this which is to add DatabaseModule.class as a module in the AppComponent and remove DatabaseComponent from the equation. But I feel this will make AppComponent with too much information and this is not really the go to way.

inherit dependencies from AppComponent and DBComponent.
Wrong. You need to inherit dependencies from AppComponent, and the contents of the DBComponent should be a module of AppComponent.
Note 2: I know one way to fix this which is to add DatabaseModule.class as a module in the AppComponent and remove DatabaseComponent from the equation. But I feel this will make AppComponent with too much information and this is not really the go to way.
Yes, it is the go-to way if you're using scoped dependencies.
Think of dependency inheritance like the extends keyword in Java.
You cannot extend multiple classes with the same class, can you?
Similarly, you cannot extend multiple scoped components either.
So if you want your graph to provide bindings for a given scope, then your component needs to have all the modules for that given scope. And #Database isn't really a scope (think lifecycles), that's a renamed #Singleton.

Related

Dagger 2 inject subcomponent.builder

I'm seeing some code where a SubComponent.Builder is being injected. For instance:
class MyClass #Inject constructor(val mySubcomponentBuilder: MySubComponent.Builder) {
fun someFunc(knownAtRuntime: String) {
mySubcomponentBuilder.withSomethingIKnowAtRuntime(knownAtRuntime).build()
}
}
Why do we (and where/when should we) do this (i.e. inject subcomponent.builders)?
Is this sort of like assisted injection where we can provide instances that are needed on the graph that we only know during runtime?
Yes, a subcomponent builder is sort of like assisted injection, but for a whole subgraph of injectable Dagger dependencies.
What are subcomponents?
Subcomponents are additional components that derive from parent components. In Guice they are analogous to child injectors; in Dagger 1 they are analogous to subgraphs. They let you add additional bindings on top of the parent bindings, as specified through additional modules.
In the sense that they allow bindings from different graphs, they can be compared to component dependencies, but they are generated at the same time as the parent component and can transparently use bindings defined in the parent component. The dagger.android and Hilt packages both use subcomponents.
Each component can have their own scope, and subcomponents aren't an exception: you might see a #Singleton ApplicationComponent with a subcomponent of #RequestScope RequestComponent for servers or #ActivityScope ActivityComponent for Android. Each request or activity would get their own component instance. That would let you inject a #RequestScoped MetricsService or #ActivityScoped User, which would be the same instance within the same request/activity but different instances between the requests/activities.
What are subcomponent builders?
Subcomponents might require modules to be instantiated if they have non-empty constructors, or they might require instances to be bound through #BindsInstance. Subcomponent Builders allow you to specify those just like you would for a Component Builder.
You also have the choice of using a Subcomponent.Factory, analogous to Component.Factory but with all of the Builder parameters as the arguments to one method call.
Why would I inject subcomponent builders? What are the alternatives?
Though you can declare subcomponents as factory methods specified on the parent component, this prevents Dagger from pruning unused dependencies because it can't see who is requesting the subcomponent. By declaring subcomponents on modules and injecting their builders into the graph, Dagger can remove entire subcomponents if you don't call them at all.

How to use AndroidInjector.Factory?

I'm migrating from Dagger 2.21 and Builder is changed to Factory. How is AndroidInjector.Factory supposed to be used? I have this component
interface AppComponent : AndroidInjector<MainApplication> {
#Component.Factory
interface Factory : AndroidInjector.Factory<MainApplication> {
fun create(#BindsInstance emailId: String) : AppComponent
}
}
But it fails with "error: #Component.Factory types must have exactly one abstract method. Already found: create(T)".
AndroidInjector.Factory already seems to have a create which binds MainApplication instance. How am i supposed to bind another value? The only workaround i found is not to extend from AndroidInjector.Factory like this but then why have AndroidInjectory.Factory?
interface AppComponent : AndroidInjector<MainApplication> {
#Component.Factory
interface Factory {
fun create(#BindsInstance instance: MainApplication,
#BindsInstance emailId: String) : AppComponent
}
}
You don't need (and as you noticed you actually can't) to extend AndroidInjector.Factory when you have additional parameters needed to create the component.
You should extend AndroidInjector.Factory when you are using dagger-android to inject dependencies into android components using AndroidInjection.inject(). To make this injection possible you need to follow some rules, in particular your component's factory should extend AndroidInjector.Factory.
AndroidInjector.Factory defines AndroidInjector<T> create(#BindsInstance T instance), which will be used by dagger-android to instantiate this subcomponent, so you can't add any additional parameters.
Update:
Dagger and dagger-android are two different libraries. Dagger is a general DI framework, it does not contain any android-specific code. Component.Factory comes from dagger itself and you are free to add a method with any number of parameters, which will be used to create your subcomponent.
Dagger-android in its turn is an android-specific library which greatly reduces boilerplate code that you need to write by generating it. But you loose some flexibility and have to follow the structure defined by the library. Here your factory method cannot have any additional parameters and the factory should extend AndroidInjector.Factory.
Generally you can also mix both approaches in one project, but I would not recommend to do so to make your code consistent.

Why Dagger Component requires #Scope annotation

When you want to provide scoped dependency, Dagger requires to annotate Component with this Scope too. What is the reason for that?
#Singleton // <- for what?
#Component(modules = [MyModule::class])
interface MyComponent {
//...
}
#Module
interface MyModule {
#Binds
#Singleton
fun provideDependency(impl: DepImpl): Dep
}
Scoping in Dagger means "give this binding the same lifetime as the component that contains it", so the component itself holds onto the instance so it can provide the same instance repeatedly.
Components are also hierarchical: through subcomponents and component dependencies you can have multiple components coexisting in the same application. In a typical Android app, you might have an Application component that provides bindings for several Activity components, and and each of those Activity components provides bindings for several Fragment components.
Though Dagger could theoretically infer scopes based on which module installs it, instead of requiring you to specify which scope applies to which component, this makes it more difficult to work with classes that declare scope and #Inject annotations without an explicit Dagger binding. If you're in an Activity component that inherits bindings from an Application component (often "singleton"), and an Activity binding relies on an #ApplicationScope-annotated class with an #Inject constructor, how would Dagger know to put it in the Application component instead of the Activity component? This might also make your code harder for humans to understand—I know I'd have trouble following it.

Using dependency injection in a Game of Life to inject new rules

I have a course project that is a refactor of Game of Life in Scala. It's a
simple pure Scala + SBT project. One of my tasks is to extract the game logic
from GameEngine class and make easier to add new rules, as Conway, Highlife or
anything like that. I turned GameEngine into an abstract class and made all
classes that inherit from it implement methods to decide when cells should die or
reborn. When my game is starting, I have this code:
def addGameMode(gameMode:GameEngine) {
modes += gameMode
}
addGameMode(ConwayEngine)
addGameMode(EasyMode)
addGameMode(HighLife)
addGameMode(Seeds)
And my classes that depends on the GameEngine rule receives it as a
construct parameter. When I need to change the game rule, I use an setter method, like
the code below:
class GameView( var gameEngine: GameEngine, modes: MutableList[GameEngine] ) extends JFXApp {
...
def setGameEngine(g: GameEngine) {
gameEngine = g
}
...
}
I don't know if this approach is correct but, from what I learned, the dependency
injection is correct. But, for my teacher, it isn't. And there my problems began.
According to him, the dependency should be declared in a .xml file and treated by
an external lib. He recommended Spring but I don't know how I should implement
the dependency injection in simple Scala + SBT project using a web framework.
Hopefully, I can ignore this recommendation to use Spring and use another lib.
I found MacWire but I don't know how to use it
to solve this problem. Can someone help me?

Does afBedSheet provide facets to tag classes as Services and methods as Route handlers?

I'm playing with Fantom's afBedSheet framework and in its documentation here, the example goes...
using afIoc
using afBedSheet
class HelloPage {
Text hello(Str name, Int iq := 666) {
return Text.fromPlain("Hello! I'm $name and I have an IQ of $iq!")
}
}
class AppModule {
#Contribute { serviceType=Routes# }
static Void contributeRoutes(OrderedConfig conf) {
conf.add(Route(`/index`, Text.fromPlain("Welcome to BedSheet!")))
conf.add(Route(`/hello/**`, HelloPage#hello))
}
}
...
The contributeRoutes method above starts becoming hard to read and maintain when more and more Routes are added, especially when route handlers come from different classes.
I'm doing this differently: on each Service class I'm adding a static method that returns a list of Routes handled by its methods, like in this example:
using afBedSheet
class Info {
static Route[] routes() {[
Route(`/info`, #all),
Route(`/info/pod`, #podAll),
Route(`/info/pod/name`, #podName),
Route(`/info/pod/version`, #podVersion),
]}
Text all() {
Text.fromJson(["This application blah blah blah"])
}
Text podAll() {
pod := Pod.of(this)
return Text.fromPlain("$pod.name $pod.version.toStr")
}
Text podName() {
Text.fromPlain(Pod.of(this).name)
}
Text podVersion() {
Text.fromPlain(Pod.of(this).version.toStr)
}
}
Then my AppModule looks like this
using afIoc
using afBedSheet
class AppModule {
#Contribute { serviceType=Routes# }
static Void contributeRoutes(OrderedConfig conf) {
Info.routes.each { conf.add(it) }
AnotherService.routes.each { conf.add(it) }
YetAnotherService.routes.each { conf.add(it) }
...
}
I'm trying to keep the AppModule clean and the Route definition and handler mapping closer to the implementing class. I expect this to make services/routes easier to maintain, but I'm not sure whether it is a good or bad idea. Benefits that I find doing this are
If I add a route handler method to a class, I declare the Route on the same class
Since route handler methods are part of the same class, I only have to type the slot name (e.g. #podVersion instead of Info#podVersion) which to me seems easier to read.
But as I said, I'm only playing with afBedSheet and I'd like to know from someone who has done a real production project with this framework if there is a good reason to declare the routes in the AppModule class, as the example shows.
Also, if what I'm doing is Ok or good, I'm wondering if there are (or if it would be a good idea to add) facets to change my Info class above to something more like:
using afBedSheet
#Service // Assuming there is such facet to let afBedSheet discover services
class Info {
#Route { `/info` } // <- Route handler facet
Text all() {
Text.fromJson(["This application blah blah blah"])
}
#Route { `/info/pod` }
Text podAll() {
pod := Pod.of(this)
return Text.fromPlain("$pod.name $pod.version.toStr")
}
#Route { `/info/pod/name` }
Text podName() {
Text.fromPlain(Pod.of(this).name)
}
#Route { `/info/pod/version` }
Text podVersion() {
Text.fromPlain(Pod.of(this).version.toStr)
}
}
If facets like these don't exist, I guess there must be good reasons to keep routes declaration in the AppModule and I'd like to know what they are.
In this (well worded question) I'm reading 2 distinct sub-questions:
Is it okay to use static methods to declare BedSheet routes?
Is it okay to create Route facets for BedSheet?
The common theme between the two questions is on-going clarity and maintenance. This can be quite a personal 'thing' and, like the proverbial cat, there is more than one way to skin it.
Anyhow, to address them individually:
Q). Is it okay to use static methods to declare BedSheet routes?
A). Yes, it is fine (but read on...)
Q). Is it okay to create Route facets for BedSheet?
A). In short, yes. In long...
As this (paraphrased) question implies - neither BedSheet nor IoC have any facets for declaring services or route handler methods. This is largely because it is not felt that such facets and associated services are 'core' enough to be included in the frameworks.
Keeping the configuration of routes and services in an AppModule means it is easy to find and keep track of - especially for newcomers to the code base.
On larger projects, a de-centralised configuration through facets can cause minor maintenance headaches. For if using facets, the only way to discover what services you have is through a textual search. The same applies to routes. A new comer trying to make sense of the project would have to wade through various pages of search results. Whereas a mere glance at an AppModule would herald the same understanding.
The word can is deliberately chosen because with enough coding diligence, be it class naming or directory structure, services and routes become logically grouped and are easy to find.
The Tales Framework has facets for declaring routes, but has this to say about them in
Externalising Routes:
Defining routes along with methods as facets is cool and quick, but it has the
following disadvantages:
Does not clearly define the order in which routes will be picked up
You cannot see all the routes that your app defines in one place.
So declaring services and routes with facets is not bad, just be wary of it. That said, I've been thinking about implementing a REST API based on facets, similar to JAX-RS (Java API for RESTful Services)!
As an aside, facet configuration is trivial to implement; for example if you had a facet called #Service that you placed on IoC service classes (or mixins) then you could just add the following line to your bind method:
const class AppModule {
static Void bind(ServiceBinder binder) {
AppModule#.pod.types .findAll { it.hasFacet(Service#) }.each { binder.bind(it) }
}
}
To sum up:
If you are to be solely responsible for a code base, or are working on a smaller project, then using facets is fine. If maintenance is to shared by others, or if the project is non-trivial, then I'd consider keeping the configuration in either a single AppModule, or in multiple modules as defined by the #SubModule facet.