This week, I've been playing with service locators in Android. I made this repo with 3 different branches. One of them is using Dagger as a DI tool, the other branches are implementation with KodeIn and Koin.
The app is pretty simple, just retrieves some data from a Cat API, saves them to a local Room persistence and then renders them to the screen:
The screen renders just a the list of cats retrieved by the database after the data are fetched from a remote API. So let us list our dependencies:
1 - First I'm using Retrofit, to fetch the data (and the interface which implements the server request methods)
2- Second I need a database (I always use Room, but in my early beginning of Android development, I remember using Realm and it was awesome too)
3- Since I'm using coroutines, I always keep a data class with coroutine dispatchers to be used in the ViewModel.
4- The famous ViewModelFactory (for simplicity I'm not handling the savedStateHandle in this article)
5- Picasso, to render the images.
I'm comparing these tools in verbosity, simplicity and time to set up.
Koin as a service locator is the easiest to get in with. The docs are pretty simple and it provides a lot of features, making it the perfect tool for Android in particular. However, it has some things I don't really like. Something I like to call the pre-declaration of the constructor. Notice this code here:
These lines are what I love and what I hate about Koin. First of all, I really love that it provides it's own feature about the ViewModel factory. But it has some perks, IMO:
1- First of all when it comes to naming, if you think about it, one keyword is single (so it's a scope), and the other is a factory (doesn't give a message about the scope, but it's a factory), and the other is a viewmodel (definitely not a scope). This makes it a little bit chaotic, mixing the concept of scopes with factory patterns.
2- The second thing is declaration of the EntranceRepository(get(), get()) which is basically definition of a constructor. This is boilerplate IMO. And correct me if I'm wrong, but I assume it uses some kind of reflection (in which I don't know much), in order to invoke constructor in runtime. If you don't provide the EntranceRepository like that, you are most likely to break the app in runtime.
I'm not handling the part of field injection, because it looks the same in all DI tools. The constructor injection in Android is a little bit more tricky than the other parts.
The same thing applies to KodeIn when it comes to speed of set up. It's fantastic. Furthermore, the lazy initialisation of dependencies looks really great to me.
1- However, this is what I found suspicious in KodeIn docs:
To use Kodein, you need an Android context. For that, View Models need to implement AndroidViewModel.
I'm pretty sure not many would agree to do that, and here are some good reasons about it.
Otherwise, you would need to keep your ViewModelFactory pattern, and KodeIn hasn't a pre-built feature for that, you have to do it manually. The pre declaration of constructor problem, does apply for KodeIn too.
2- Another thing I noticed in KodeIn field injection, was the by kodein() delegation which does create a little bit of confusion because the kodein() method is a different extension method for Activities, Fragments etc.
My own idea of a service locator in Android:
Must note that this is only an idea and it's totally immature, but I find compile time safer than runtime. Therefore, annotations are perfectly solving a case of DI in the JVM world. If we forget Dagger for a moment, the next best example would be Spring framework with its' @Autowire injection keyword.
Anyways, there could be library which could be a mix between Dagger and other service locators. I still imagine something like this:
This is far from explained or implemented, and I am still learning about code generation, but a Dagger without components would be awesome as a service locator (hopefully this is not Dagger1, because I've never seen it 😅) .
Using KodeIn or Koin in your codebase is pretty reasonable thing to do, if it solves your problem. But this article was more about to state that they are not as simple as developer state they are. Furthermore, I still think that Dagger takes a little more time to implement (and hopefully that's the hardest thing in Dagger, trust me) , but the generation of boilerplate comes from the framework and giving less work to the developer.
If you want to know more about service locators, dependency injection and IoC in particular, please refer here.
Full repository here.