Usage of Dagger2 outside Android

Viewed 232

I've recently started to learn Dagger. In order to do that, i've decided to write a simple console application to get the feeling of how various dagger features (like modules, component, subcomponents and component dependencies) fit together in an app architecture. As I don't really understeand it and given how hard it is to find an application sample created with dagger2 which is not Android app, i've decided to open a question here.

The first and probably most important question is: is dagger2 even ment to be used outside android?

If yes, then lets consider a simple application architecture: we have the data layer, service layer and ui layer

Data layer might consist of some kind of facade: (Following code snippets will be written in Kotlin)

class Entity(var id: Int)

interface Repository {

    fun findEntityById(id: Int): Entity?

    fun deleteEntity(entity: Entity): Boolean

    fun saveEntity(entity: Entity): Boolean

    fun findAllEntities(): List<Entity>

}

Then i could have a couple of implementations of this facade:

class InMemoryRepository @Inject constructor() : Repository {
    private val entities: MutableList<Entity> = LinkedList()

    override fun findEntityById(id: Int): Entity? = entities.firstOrNull { it.id == id }

    override fun deleteEntity(entity: Entity) = entities.remove(entity)

    override fun saveEntity(entity: Entity) = entities.add(entity)

    override fun findAllEntities(): List<Entity> = LinkedList(entities)
}

For which i would have modules:

@Module
interface InMemoryPersistenceModule {

    @Singleton
    @Binds
    fun bindRepository(rep: InMemoryRepository): Repository
}

Service layer would be simpler:

@Singleton
class Service @Inject constructor(repository: Repository) {
    fun doSomeStuffToEntity(entity: Entity) {}
}

@Singleton
class AnotherService @Inject constructor(repository: Repository) {
    fun doSomeStuffToEntity(entity: Entity) {}
}

But it gets a little bit unlcear when it comes to the UI layer. Lets say i have some kind of android-like activity:

interface Activity : Runnable

And some kind of class that manages those activities:

class UserInterfaceManager {

    val activityStack: Stack<Activity> = Stack()
    val eventQueue: Queue<Runnable> = LinkedList()

    fun startActivity(activity: Activity) = postRunnable {
        activityStack.push(activity)
        activity.run()
    }

    fun postRunnable(callback: () -> Unit) = eventQueue.add(callback)

    fun stopActivity() { TODO() }
    
    //other

}

How does dagger fit into this scenario? The articles i have read about the the dagger with android suggest createing the application component to inject my activites:

@Singleton
@Component(modules = [InMemoryPersistenceModule::class])
interface ApplicationComponent {
    
    fun injectSomeActivity(activity: SomeActivity)
    // and more
}

But then, where would the injection go to? It does't really make sense to put it in the UserInterfaceManager as Activities will most likely need an instance of it, which would create a circular dependency. I also do not like the idea of the component being obtained from some kind of static method/property and injecting the activity from inside of it at the startup, as it creates duplicate lines of code in each activity.

Also, where do components and subcomponents fit in this kind of architecture? Why not create the separate component for the data layer and expose just the repository and declare it as a dependency of the app component which would further isolate the details from abstraction? Maybe i should declare this component a dependcy of a service component which would enforce the layer architecure, as components can only use the types exposed in component interface? Or maybe i should use compoenent only when i need a custom scope and use the modules everywhere elsewhere?

I just overally think I am missing the bigger picture of the dagger. I will be really greatefull for answers, explanations and links to articles and other resouces that will let me understeand it better.

1 Answers

From the perspective of an Android developer, I fully understand your point. I asked myself this question too. The way how you construct an object in plain Java/Kotlin world is a little bit different. The main reason is due to the fact basic Android components (Activity/Fragment) don't allow constructor injection.

The answer to your question is, though, pretty straightforward. The Dagger Component is responsible for object creation, and you, as a developer, control what objects specific component provides. Let's use it in your scenario and provide some of the objects you might be interested in:

@Singleton
@Component(modules = [InMemoryPersistenceModule::class])
interface ApplicationComponent {
    val service: Service
    val anotherService: AnotherService
}

ApplicationComponent should be understood as a component for your whole application. It's not related to Android's Application class in any way. Now, you can simply create your component and let Dagger instantiate your objects:

val component = DaggerApplicationComponent.create()
val anotherService: AnotherService = component.anotherService
val service: AnotherService = component.service
Related