Jetpack Compose | Dependency injection with Koin!
Unlock the power of Koin in your projects
What is dependency injection?
Dependency Injection is a software design pattern used in object-oriented programming and software engineering. It is one of the techniques that aim to facilitate the segregation of responsibilities in object-oriented programming. Instead of creating dependencies within the context, objects are able to pass these dependencies to another object that needs it. This helps in disconnecting the code, so it becomes more flexible and manageable. In DI, dependencies are typically through constructor injection, setter/injection, and method injection.
In short, is a crucial notion in software development aimed to achieve the objectives of robustness, testing, and easy adaptability. It is advisable to embrace DI as it enables the development of stronger and scalable software which is also easy to maintain or expand in the future. In Kotlin, Koin is a great DI framework that makes it easy to implement your DI approach in the Kotlin apps you utilize. It minimizes boilerplate code, enhancing the overall developer experience and making the codebase more approachable.
Understanding Koin
What is Koin?
With Koin, you can easily configure and inject dependencies throughout your application, making it more modular and easier to maintain with minimal boilerplate code. It is often referred to as a “pragmatic DI framework” because it’s lightweight, easy to use and provides a DSL (domain-specific language) for defining and resolving dependencies.
What will we build?
To understand Koin and DI principles we will build an simple composable that will display the content fetched from a ViewModel, and will also update values from it.
To achieve this I have this simple viewModel that only returns hardcoded code, but if you want you can inject any other dependencies to make the content more dynamic.
class AppViewModel : ViewModel() {
private val _name = mutableStateOf("Marelso")
val name: State<String> = _name
fun getImage() = "Any image url"
fun updateName(name: String) { _name.value = name }
}
Getting started
If you’re working with Gradle, incorporate Koin dependency into your project’s build.gradle file. You can checkout here for the most up-to-date version. As for my current setup, I’m using the following version:
implementation 'io.insert-koin:koin-androidx-compose:3.2.0'
Your first Koin module!
Create an dependency injection package
When using Koin, we can use modules for holding out dependencies, this allow us to create a more flexible environment. In order to do this, we have to build a new Kotlin file to declare all the modules we need. Koin provide us the “module” method that can be used to declare the new module.
val appModule = module {
viewModel { AppViewModel() }
}
Having incorporated the Koin library and defined your initial module, its time to create an Application to manage these modules. You can use the following snippet as a guide:
class App: Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this@App)
modules(appModule)
}
}
}
When you’ve finished configuring your project to start using Koin, be sure that your AndroidManifest.xml file is correctly configured and pointing to your custom application class. If it’s not the case update the <application> tag accordingly:
<application
android:name=".App" >
</application>
Using dependencies in compose
With our dependencies ready to go, it’s time to do some use to them in our Composables. We can inject the provided ViewModel by using “getViewModel” method from Koin.
@Composable
fun Greeting(modifier: Modifier = Modifier,
viewModel: AppViewModel = getViewModel()
) {
val observableName by remember { viewModel.name }
val painter: ImagePainter = rememberImagePainter(
data = viewModel.getImage(),
builder = {
crossfade(true)
}
)
var text by remember { mutableStateOf("") }
Column (
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
TextField(
value = text,
onValueChange = {
viewModel.updateName(it)
text = viewModel.name.value
},
label = { Text("Enter your name") },
modifier = Modifier
.fillMaxWidth()
.padding(32.dp)
)
Spacer(modifier = Modifier.height(32.dp))
Image(
painter = painter,
contentDescription = "Image Description",
modifier = Modifier
.width(80.dp)
.height(80.dp)
.clip(CircleShape),
contentScale = ContentScale.Crop
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = observableName,
modifier = modifier
)
}
}
Conclusion
When discussing Jetpack compose, the use of dependency injection through Koin brings several benefits by improving the modularity and maintainability of projects developed using the framework. Dependency injection is an fundamental concept in modern software development, promoting flexibility, testability, and separation of concerns. Koin simplifies the configuration and injection of dependencies, minimizing boilerplate code and making your application more modular.
The UI effortlessly interacts with the ViewModel, displaying dynamic content fetched through dependency injection. This approach not only enhances the development experience but also aligns with principles that foster scalable, robust, and maintainable software.
References