KServiceLocator is a lightweight, efficient service locator for Android written in Kotlin. It allows you to easily manage your dependencies and make them accessible anywhere in your application. This is a simpler and more robust version of KOIN and can be used on simple projects and also complex projects based on needs.
You can also read this medium article that explains the usage: https://medium.com/@bawender.y/service-locator-implementation-in-android-63d3a735b553
Hilt is a good DI library but is overly complex and feels like an overkill for a simpler that don't have a ton of complexity in them. All we want to do is get the dependencies that we declare. Koin seems like a good alternative, but in their quest to become a full fledged DI I think they made the solution a bit complicated. Loading and unloading dependencies can be unpredictable and after seeing a few crashes because of this, I decided to write my own implementation.
Moreover this follows a service locator pattern rather than standard DI approach.
Distribution is done through jitpack.
Step 1. Add it in your root build.gradle at the end of repositories:
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
Step 2. Add the dependency
dependencies {
implementation 'com.github.BobFactory:KServiceLocator:LATEST_VERSION_TAG'
}
Usage of this library is fairly similar to how Koin dependency declaration is done but with some caveats.
The library is preshipped with a ServiceLocator
object, which acts as a container for all the dependencies.
Use the object to declare dependencies.
class MainApp: Application() {
override fun onCreate() {
super.onCreate()
with(ServiceLocator) {
//Context object
androidContext(this@MainApp)
single { Dependency1() }
factory { Dependency1() }
viewModel { SomeViewModel() }
}
}
}
You can access the dependency anywhere in the application by simply calling get<Type>()
function provided in the package. If you want to invoke the dependency in a lazy fashion then use the by inject()
delegate.
val dep1: SomeClass = get()
val dep2: SomeClass = get()
val dep3: SomeClass by inject()
val dep4: SomeClass by inject()
Most likely you are going to have dependencies that involve other dependencies. Simple use the get<Type>()
function to invoke them.
class Universe(order: Order, chaos: Chaos)
class Order
class Chaos
with(ServiceLocator){
factory { Order() }
factory { Chaos() }
factory { Universe(get(), get()) } // invokes the value present in the locator object for the depdendency.
}
Note:
As you might have guessed it this is not a lazy generation of dependencies. When you call the get()
function in the above example it is mandatory to register the Order
and Chaos
classes before the Universe
class for the get()
function to look them up. The dependency is immediately registered in a sequential fashion into the ServiceLocator.
You can also register the viewmodels and access them in your composable functions too. You can also pass a savedStateHandle to the viewmodel.
To register a viewmodel use
with(ServiceLocator) {
viewModel { SomeViewModel() }
viewModel { savedStateHandle -> OtherViewModel(savedStateHandle) }
}
To access the viewmodel using the delegate:
class MainActivity : ComponentActivity() {
val vm : MainViewModel by serviceViewModel()
}
For immediately accessing the viewmodel use the getViewModel function:
class MainActivity : ComponentActivity() {
val vm : MainViewModel = getViewModel()
}
For invoking viewmodel inside a compose
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
val vm: MainViewModel = serviceViewModel()
...
}
You can also store the android context into the ServiceLocator object to be used in any part of the application. Please use this functionality carefully, do not place the activity context into the ServiceLocator as it will be considered a memory leak. You can however save the MainActivity context if your app follows the single activity architecture pattern or the context saved is application contex.
class MainApp: Application {
fun onCreate() {
with(ServiceLocator) {
androidContext(this@MainApp)
}
}
}
//Access the context object using the regular get function later
class SomeClass(context: Context = get())
Since there are no module support and the dependencies are immediately loaded into the locator you will have to use comments to seperate the different dependencies, just make sure they follow a serial linear declaration fashion.
with(ServiceLocator) {
//Context
androidContext(context)
//Database
single { Database() }
//Services
factory { UserService(database = get()) }
factory { FirebaseService() }
factory { UserRepository(userService = get(), firebaseService = get()) }
//ViewModels
viewModel { handle -> EditHabitViewModel(handle, get(), get()) }
viewModel { AddHabitViewModel(get()) }
}
MIT License
Copyright (c) 2023 Bawender Yandra
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.