Blog Infos
Author
Published
Topics
Published
Topics

Koined from original banner — https://github.com/android/nowinandroid

Now In Android is an open-source Android application that covers Modern Android Development best practices. The project is maintained by the Google Android team.

I propose to continue our tour with the version built with the Koin dependency injection framework. This is a good time to refresh practices, from standard components structure to more advanced cases.

For this article, I propose now to use Koin Annotations instead of Koin DSL to configure all the app’s components injection. This is interesting to see how much it can improve the experience in terms of writing.

Prepare yourself, we have many things to see together 👍 🚀

⚠️ The version used below is not yet public. @KoinWorker is coming soon in next release

Previously in part 3, we saw how to setup and use Koin annotations with Koin dependency injection framework. It’s really easy as the Koin annotation processor can detect many cases, and generate your dependency injection configuration really quickly. It’s now time to dive into more components of the NowInAndroid project.

You will have the reference for all the content to browse the code. Also, everything is available online on the Github repo: https://github.com/InsertKoinIO/nowinandroid/

Common Data Layers

Following the part 2 article, we will now go through the common core components that will be used by the features later. But this time, the configuration will be done with annotations.

As a reminder, the Nia app is developed using Jetpack Compose and uses repository & use-case components:

  • Repository to access data (network, database …)
  • Usecase to handle business logic

The module that is gathering all those common components is theDataKoinModule.kt module:

@Module(includes = [DaosKoinModule::class, DataStoreKoinModule::class, NetworkKoinModule::class, DispatchersKoinModule::class, DataUtilModule::class])
@ComponentScan("com.google.samples.apps.nowinandroid.core.data.repository")
class DataKoinModule

This module is making several things:

  • scans all repository classes defined in @ComponentScan
  • includes modules that are declaring sub-data layers components

Each repository class is simply tagged with @Single annotation like this:

@Single
class OfflineFirstAuthorsRepository(
    private val authorDao: AuthorDao,
    private val network: NiaNetworkDataSource,
)

You can find all the repository classes in the source code data package.

Database Storage

For the database storage layer, we need to declare our Room database instance via a function using the Room API builder like this:

@Module
class DatabaseKoinModule {

    @Single
    fun database(context: Context) =
        Room.databaseBuilder(context, NiaDatabase::class.java, "nia-database")
            .build()
}

The context parameter here is the Android Context instance from Koin.

In a second module, we can reuse our NiaDatabase instance below in DAOs:

@Module(includes = [DatabaseKoinModule::class])
class DaosKoinModule {

    @Single
    fun authorDao(niaDatabase: NiaDatabase) = niaDatabase.authorDao()

    @Single
    fun topicDao(niaDatabase: NiaDatabase) = niaDatabase.topicDao()

    @Single
    fun newsResourcesDao(niaDatabase: NiaDatabase) = niaDatabase.newsResourceDao()
}

That’s it! Our Database Layer is ready to be injected.

Datasource Components — Datastore & Networking

This layer defines Datasources which are components that abstract the calls to different sources of data. E.g: remote web service, local data storage, and so on. Therefore, the UI doesn’t need to know where the data comes from. It just calls the interface defined here.

We are defining severasl kind of usages with NiaNetworkDatasource:

interface NiaNetworkDataSource {
    suspend fun getTopics(ids: List<String>? = null): List<NetworkTopic>

    suspend fun getAuthors(ids: List<String>? = null): List<NetworkAuthor>

    suspend fun getNewsResources(ids: List<String>? = null): List<NetworkNewsResource>

    suspend fun getTopicChangeList(after: Int? = null): List<NetworkChangeList>

    suspend fun getAuthorChangeList(after: Int? = null): List<NetworkChangeList>

    suspend fun getNewsResourceChangeList(after: Int? = null): List<NetworkChangeList>
}

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

, , ,

Inject your Jetpack Compose Application with Koin

Koin (insert-koin.io) is the Kotlin dependency injection framework. The Android community knows Koin very well as they have been using it since 2017. What they appreciate the most is its versatility and ease of use.
Watch Video

Inject your Jetpack Compose Application with Koin

Arnaud Giuliani
Koin Project Lead

Inject your Jetpack Compose Application with Koin

Arnaud Giuliani
Koin Project Lead

Inject your Jetpack Compose Application with Koin

Arnaud Giuliani
Koin Project Lead

Jobs

First, we need to declare a default Coroutine dispatcher directly in a module:

@Module
class DispatchersKoinModule{
    
    @Single
    fun dispatcher() = Dispatchers.IO
}

In a test environment, you simply have to redefine a CoroutineDispatcher type to specify your needed one. Just add the new definition and it will override the existing one.

The network module is declaring NiaNetworkDatasource, and is organized into 2 flavors:

  • demo — with local data
  • prod — for online data

The NetworkKoinModule includes the right flavour implementation:

@Module(includes = [FlavoredNetworkKoinModule::class])
class NetworkKoinModule {

    @Single
    fun json() = Json { ignoreUnknownKeys = true }
}

Demo flavor in Network module

 

The demo flavour uses Datastore API and Protobuff API is used to store local data to display as an offline-first architecture.

@Module(includes = [DispatchersKoinModule::class])
@ComponentScan("com.google.samples.apps.nowinandroid.core.network.fake")
class FlavoredNetworkKoinModule{

    @Single
    fun assetManager(context: Context) = FakeAssetManager(context.assets::open)
}

Below is the demo datasource implementation declared as a singleton:

@Single
class FakeNiaNetworkDataSource(
    private val ioDispatcher: CoroutineDispatcher,
    private val networkJson: Json,
    private val assets: FakeAssetManager = JvmUnitTestFakeAssetManager,
) : NiaNetworkDataSource

The online version is declared with the following module:

@Module(includes = [DispatchersKoinModule::class])
@ComponentScan("com.google.samples.apps.nowinandroid.core.network.retrofit")
class FlavoredNetworkKoinModule

This module will scan the Retrofit implementation:

@Single
class RetrofitNiaNetwork(
    networkJson: Json
) : NiaNetworkDataSource

One last part is about Datastore persistence API, used to declare local data storage. Check the Datastore Persistence Module that is declaring the required components for NiaPreferencesDatasource.

Domain & Features Modules

Before running our features, we have some use-case components using the DataKoinModule. Those use-cases components are reusable business logic components. They are defined from the DomainKoinModule:

@Module(includes = [DataKoinModule::class])
@ComponentScan
class DomainKoinModule

You can note that we don’t specify what package to scan. This means that the module will scan in the current package and sub-packages for annotated components:

Each usecase component is declared with @Factory annotation. This asks Koin to create a new instance each time we need it.

@Factory
class GetFollowableTopicsStreamUseCase(
    private val topicsRepository: TopicsRepository,
    private val userDataRepository: UserDataRepository
)

Why not a singleton instance? Because those usecase components will be used with a ViewModel, following the Android lifecycle. Making them as a singleton, we would take a risk to have references to a ViewModel that are destroyed by the application.

Finally, we are ready to use all of this in our Feature module. Each will then include DomainKoinModule or DataKoinModule to benefit from the common components:

@Module(includes = [DomainKoinModule::class,StringDecoderKoinModule::class])
@ComponentScan("com.google.samples.apps.nowinandroid.feature.author")
class AuthorKoinModule

By scanning the right package in our module, we will be able to declare our ViewModel instances like this:

@KoinViewModel
class AuthorViewModel(
    savedStateHandle: SavedStateHandle,
    stringDecoder: StringDecoder,
    private val userDataRepository: UserDataRepository,
    authorsRepository: AuthorsRepository,
    getSaveableNewsResourcesStream: GetSaveableNewsResourcesStreamUseCase
) : ViewModel()
Sync Worker — Offline data sync with WorkManager

Finally, we need to declare our SyncWorker components, to asynchronously prepare offline content. This consists of a module:

@Module
@ComponentScan
class SyncWorkerKoinModule

The following definitions will be scanned by the module.

@Single
class WorkManagerSyncStatusMonitor(
    context: Context
) : SyncStatusMonitor

And the SyncWorker component declared with @KoinWorker annotation. This will generate the equivalent of worker { } DSL:

@KoinWorker
class SyncWorker (
    private val appContext: Context,
    workerParams: WorkerParameters,
    private val niaPreferences: NiaPreferencesDataSource,
    private val topicRepository: TopicsRepository,
    private val newsRepository: NewsRepository,
    private val authorsRepository: AuthorsRepository,
    private val ioDispatcher: CoroutineDispatcher,
) : CoroutineWorker(appContext, workerParams), Synchronizer

SyncWorker will be declared with Workmanager Koin factory. This one has to be activated at the start like this:

Koin start in NiaApplication class

 

Koin Annotations — Cheat Sheet

Hope you enjoyed the walkthrough NowInAndroid application with Koin dependency injection and annotations. You will find below the last cheat sheet we’ve made.

This article was previously published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
This is the second article in an article series that will discuss the dependency…
READ MORE
blog
I develop a small multi-platform(android, desktop) project for finding cars at an auction using…
READ MORE
blog
Now In Android is an open-source Android application that covers Modern Android Development best…
READ MORE
blog
In this blog post, We’ll delve into the powerful trio of Jetpack Compose, Ktor,…
READ MORE
Menu