Blueprint Interactor Coroutines¶
This library provides building blocks for writing Interactors (use cases as defined in Clean Architecture) based on Kotlin Coroutines and Flow.
Dependency¶
implementation "io.github.reactivecircus.blueprint:blueprint-interactor-coroutines:${blueprint_version}"
Usage¶
To implement an Interactor we would extend from one of the 2 classes provided:
SuspendingInteractor
for single-shot tasksFlowInteractor
for cold streams
Let’s say we need an Interactor for fetching a list of users from API. Since there can only be 1 response (or error), our Interactor should extend from SuspendingInteractor
:
class FetchUsers(
private val userService: UserService,
coroutineDispatcherProvider: CoroutineDispatcherProvider
) : SuspendingInteractor<EmptyParams, List<User>>() {
override val dispatcher: CoroutineDispatcher = coroutineDispatcherProvider.io
override suspend fun doWork(params: EmptyParams): List<User> {
userService.fetchUsers()
}
}
To execute this Interactor, call the execute(...)
suspend function from a CoroutineScope
:
viewModelScope.launch {
// EmptyParams is a special [InteractorParams] which you can use when the Interactor has no params.
val users: List<User> = fetchUsers.execute(EmptyParams)
}
Note that the CoroutineDispatcherProvider
in the constructor of the Interactor comes from the blueprint-async-coroutines artifact, which encapsulates the threading behavior with a wrapper API.
Now let’s implement another Interactor for updating a user profile. This interactor expects no result and we just need to know the whether it has been completed successfully. So our Interactor should again extend from SuspendingInteractor
:
class UpdateUserProfile(
private val userService: UserService,
coroutineDispatcherProvider: CoroutineDispatcherProvider
) : SuspendingInteractor<UpdateUserProfile.Params, Unit>() {
override val dispatcher: CoroutineDispatcher = coroutineDispatcherProvider.io
override suspend fun doWork(params: Params) {
userService.updateUserProfile(params.userProfile)
}
class Params(internal val userProfile: UserProfile) : InteractorParams
}
To execute this Interactor, call the execute(...)
suspend function from a CoroutineScope
:
viewModelScope.launch {
// this returns a Unit
updateUserProfile.execute(UpdateUserProfile.Params(userProfile))
}
In a reactive architecture we might want to stream any changes to the users persisted in the database to automatically re-render the UI whenever the user list has changed. In this case it makes sense to extend from the FlowInteractor
:
class StreamUsers(
private val userRepository: UserRepository,
coroutineDispatcherProvider: CoroutineDispatcherProvider
) : FlowInteractor<EmptyParams, List<User>>() {
override val dispatcher: CoroutineDispatcher = coroutineDispatcherProvider.io
override fun createFlow(params: Params): Flow<List<User>> {
return userRepository.streamUsers() // this returns a Coroutines Flow
.map { users ->
users.sortedBy { it.lastName }
}
}
}
On the call-side:
streamUsers.buildFlow(EmptyParams)
.onEach { users ->
// propagate value of each Flow emission to LiveData<List<User>>
usersLiveData.value = users
}
.catch {
Timber.e(it)
}
// launch the collection of the Flow in the [viewModelScope] from "androidx.lifecycle:lifecycle-viewmodel-ktx"
.launchIn(viewModelScope)
Please check the Blueprint Coroutines Demo app for more examples of writing and testing Interactors.