Koined from original banner — https://github.com/android/nowinandroid
This year I was speaking at several Android conferences, about Android architecture design with Koin (Silicon Brighton) and about the new Kotlin developer experience since Koin 3.2 (Droicon Berlin and Kotlin Dev Day Amsterdam).
At the same time, the Google Android team was heavily working on gathering best practices for the community, related to the latest development technologies such as Jetpack Compose, Material Design Kotlin Coroutines, and other technologies standards to build Android applications in 2022.
Now In Android is an open-source Android application that covers those best practices and keeps them up to date. The app is maintained by the Android team. A great thanks to them for such resources!
I propose today to take a special tour of Now In Android: a version built with the Koin dependency injection framework. This is a good time to refresh practices with Koin and Modern Android Development, from standard components structure to more advanced cases.
You can find all the related sources at this location: https://github.com/InsertKoinIO/nowinandroid
This article is split into several parts:
Part 1 — Koin setup, application verification, and a first module tour
Part 2 — Common Modules components (database, network, domain …) and feature modules
Part 3 — Setup and code with Koin annotations
… perhaps more 🙂
Injecting with Koin
Koin is a very popular Kotlin dependency injection framework (https://insert-koin.io/), well-known for its ease of use and its capacity to bring elegant and powerful features thanks to the Kotlin language.
In Android, Koin comes magically ready to use out of the box thanks to the Kotlin extensions mechanism. This means that you can easily use Koin functions directly from any Android part.
The Now In Android (aka Nia) application is a vast example to play with. Great content, but where to start? 🤔
Features picture from https://github.com/android/nowinandroid
Architecture & Modules Map
The project proposes an overview of the existing modules. You may take a look at the modularization document. Here is a quick preview of the organization of the current modules:
Module organization — https://github.com/android/nowinandroid
We can explore the project on the following parts:
- App Module — the main module and application entry point
- Common modules — gathering all common components (database, repository, network, domain …)
- Feature modules — implementing each part/screen of the app
- Sync module — dedicated to data resyncing
Gradle Setup & Build logic for Koin
First, let’s setup everything. We use the following Koin dependencies in the project:
koin-android
— Android features (common android parts)koin-androidx-compose
— features related to Jetpack Compose (feature module)koin-androidx-workmanager
— WorkManager features (common data sync module)koin-core
— pure Kotlin components (for Kotlin only common module)koin-test
— verification and testing parts
For this articles series, we will use the latest Koin 3.3 versions. Check the setup page for more details: https://insert-koin.io/docs/setup/koin
The Gradle configuration has been updated to be able to use Koin package directly with the version catalog already established. Check the libs.versions.toml file about Koin artifacts.
The AndroidFeatureConventionPlugin file is also updated to bring koin-androidx-compose
for each feature module.
Starting Koin
Let’s open the NiaApplication class, the application entry point, to see the
onCreate
method written to start Koin with startKoin
function:
Starting Koin
Job Offers
We use several options:
androidLogger
— enable Koin logging on an AndroidandroidContext
— reference the Android Application contextworkManagerFactory
— start WorkManager components declared in Koin
We use the modules()
function to load Koin modules. Here we will load the niaAppModule
.
The call toSync.initialize()
will init the WorkManager to resync data. We will look at these details later.
note: androidLogger is logging in INFO level by default. You can choose the DEBUG level to have more complete information if you need to investigate around Koin.
The Nia Application Module
The first thing we need is one main module that will include all other sub-modules. The NiaAppModule.kt file declares the main Koin module as follow:
The main Koin module
The includes
function uses a list of modules to load with the current module. This also helps Koin flatten all your module graphs and optimizes your application startup.
Below we declare MainActivityViewModel
as a ViewModel in Koin, with the viewModelOf()
function. This function targets directly a class constructor to build.
viewModelOf(::MainActivityViewModel)
This is why we have the use of ::
characters, to identify the constructor of the MainActivityViewModel
class.
We recommend using includes()
function to ensure gathering all app modules into the main one. More than helping organize modules and sub-modules, the includes function will let you verify globally your Koin configuration. Let’s check below.
Verifying the Koin Configuration — verify() your module
One new important feature of Koin introduced recently with Koin 3.3, is the capacity to verify a Koin configuration in a few milliseconds with just a JUnit test. The koin-test
Gradle package is required to have access to such testing features.
How does it work? Use the verify() extension function on a Koin Module. That’s it! Under the hood, This will verify all constructor classes and crosscheck with the Koin configuration to know if there is a component declared for this dependency. In case of failure, the function will throw a
MissingKoinDefinitionException
.
Let’s see the NiaAppModuleCheck.kt file:
verify() on the main Koin module
Launch the JUnit test and you’re done! ✅
As you may see, we use the extraTypes
parameter to list types used in the Koin configuration but not declared directly. This is the case for SavedStateHandle
and WorkerParameters
types, that are used as injected parameters. The Context
is declared by androidContext()
function at start.
The verify()
API is ultra-light to run and doesn’t require any kind of mock/stubb to run on your configuration.
First module & injection from an Activity
Let’s continue our exploration of the project, still in the app module. There is a small module that let you build JankStats
instances for an Activity: the JanksStatsModule
.
Below is the original dagger version:
JankStatsModule in Dagger version
The idea is to create a JankStats
object to let you enable/disable performance tracking in an Activity:
Lazy Dagger Property for JankStats
Using lazyStats
In the Koin version, we would write this part with a simple definition. Let’s open the JankStatsKoinModule file:
JankStats Koin module
Here we have a factory definition, that is building the JankStats object instance from an incoming Activity.
How do we pass an Activity to a definition? We use injected parameters to declare that we will use an Activity as a parameter:
factory { (activity: Activity) -> JankStats.createAndTrack(activity.window, createOnFrameListener()) }
We use a function (lambda block behind factory keyword), to write a Kotlin function that will build our component.
From our MainActivity
, we simply need to declare JankStats
with a call to inject
function as follow:
val lazyStats: JankStats by inject { parametersOf(this) }
The parametersOf
expression passes arguments to your Koin definition.
Lazy inject in Activity with Koin & injected parameters
The lazyStats
property is a real Kotlin lazy type and can be used directly used:
Well … That’s it for this first part. Hope you enjoyed it. See you in the next part about common components and features.
Follow Koin and Kotzilla latest news on http://blog.kotzilla.io/
This article was originally published on proandroiddev.com on December 15, 2022