Skip to main content

Are service locators in Android actually simple?


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.
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:

val myModule = module{
      ...
      single {
            CoroutineDispatchers(
                Dispatchers.Main,
                Dispatchers.IO
            )
        }

        factory {
            EntranceRepository(get(), get())
        }

        viewModel {
            EntranceFragmentViewModel(get(), get())
        }
      ...
    }
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.

KodeIn

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:

@Holder
class MyApplication: Application(){
 
  override fun onCreate(){
   ..
   MyApplicationHolder.init(this) //after compilation
  }
  
  @Single
  fun provideDependency1() : Dependency1{
    return Dependency1.builder().setItsThings(things).build() 
  }
  
  @SomeScoped
  fun provideDependency3(): Dependency3{
    return Dependency3.builder().setItsThings3(things3).build()
  }
  
  @Single
  @ByFactory
  fun provideViewModel() : MyViewModel{
    return MyViewModel(dependency1, dependency2)
  }
}

@Single //which would be just a @Inject + @Singleton equivalent
class Dependency2(){
}

//ViewModels

@Single //A ViewModel factory would be generated for you by the "framework"
class MyViewModel(d1: D1, d2: D2) : ViewModel(){
  
}
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 😅) .

Conclusion:

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.

Stavro Xhardha

Popular posts from this blog

Modularizing your Android app, breaking the monolith (Part 1)

Inspired by a Martin Fowlers post about Micro Frontends, I decided to break my monolithic app into a modular app. I tried to read a little more about breaking monolithic apps in Android, and as far as I got, I felt confident to share my experience with you. This will be some series of blog posts where we actually try to break a simple app into a modularized Android app.

Note: You should know that I am no expert in this, so if there are false statements or mistakes please feel free to criticize, for the sake of a better development. 

What do you benefit from this approach:
Well, people are moving pretty fast nowadays and delivery is required faster and faster. So, in order to achieve this, modularising Android apps is really necessary.You can share features across different apps. Independent teams and less problems per each.Conditional features update.Quicker debugging and fixing.A feature delay doesn't delay the whole app. As per writing tests, there is not too much difference about…

What I learned from Kotlin Flow API

I used to check the docs and just read a lot about flows but didn't implement anything until yesterday. However, the API tasted really cool (even though some operations are still in Experimental state).Prerequisites: If you don't know RxJava it's fine. But a RxJava recognizer would read this faster.Cold vs Hot streamsWell, I really struggled with this concept because it is a little bit tricky. The main difference between cold and hot happened to be pretty simple: Hot streams produce when you don't care while in cold streams, if you don't collect() (or RxJava-s equivalent subscribe()) the stream won't be activated at all. So, Flows are what we call cold streams. Removing the subscriber will not produce data at all, making the Flows one of the most sophisticated asynchronous stream API ever (in the JVM world). I tried to make a illustration of hot and cold streams: Since I mentioned the word asynchronous this implies that they do support coroutines also. Flows vs…

From Gson to Moshi, what I learned

There is no doubt that people are getting away from GSON and I agree with those reasons too. The only advantage GSON has over other parsing libraries is that it takes a really short amount of time to set up. Furthermore, the most important thing is that Moshi is embracing Kotlin support.

First let's implement the dependency:
implementation("com.squareup.moshi:moshi:1.8.0") It's not a struggle to migrate to Moshi. It's really Gson look-a-like. The only thing to do is annotate the object with @field:Json instead of @SerializedName (which is Gsons way for JS representation):

data class User( //GSON way @SerializedName("name") val name: String, @SerializedName("user_name") val userName: String, @SerializedName("last_name") val lastName: String, @SerializedName("email") val email: String ) data class User( //Moshi way @field:Json(name = "name") val name: String, @field:Json(name = "user_name…