Diego
11/03/2021, 8:18 PMdata class UserDTO(val id: String)
data class PetDTO(val name: String)
interface Api {
suspend fun users(): List<UserDTO>
suspend fun pets(userId: String): List<PetDTO>
}
data class User(
val id: String,
val pets: List<Pet>
)
data class Pet(val name: String)
class UsersRepository(api: Api) {
val users: Flow<List<User>> = TODO()
}
In RxJava I would do something like this:
val users: Observable<List<User>> = api.users()
.flatMapIterable { it }
.concatMap { userDto ->
api.pets(userDto.id)
.map { petsListDto ->
User(userDto.id, petsListDto.map { Pet(it.name) })
}
}.toList()
How can I implement UsersRepository
and return the list of `User`s using Kotlin Flow?Nick Allen
11/03/2021, 11:50 PMclass UsersRepository(api: Api) {
suspend fun users(): List<User> = api.users().map { userDto ->
User(userDto.id, api.pets(userDto.id).map { petDto -> petDto.name }
}
val users: Observable<List<User>>
but I suspect you meant:
val users: Single<List<User>>
based on the Api interface and based on you ending the expression with toList() which returns a Single.Diego
11/04/2021, 10:57 AMclass UsersRepository(api: Api) {
val users: Flow<List<User>> = flow {
val usersWithPets = api.users().map { userDto ->
val pets = api.pets(userDto.id).map { petsListDto ->
Pet(petsListDto.name)
}
User(userDto.id, pets)
}
emit(usersWithPets)
}
}
gts13
11/04/2021, 3:21 PMNick Allen
11/04/2021, 4:12 PMFlow
. You wouldn't return a List
if you only have one result in a synchronous method, so why would you return a Flow
here? If you then want to bring it into a Flow
expression, you can do repository::users.asFlow()
.launch
or async
. Here's your code adapted to use async.
class UsersRepository(api: Api) {
val users: Flow<List<User>> = flow {
val usersWithPets = coroutineScope {
val userIdsWithPetsTasks = api.users().map { userDto ->
val petsTask = async {
api.pets(userDto.id).map { petsListDto ->
Pet(petsListDto.name)
}
}
userDto.id to petsTask
}
userIdsWithPetsTasks.map { (userId, petsTask) ->
User(userId, pets.await())
}
}
emit(usersWithPets)
}
}
The api.users().map { ... }
starts a task to get the pets for each user. userIdsWithPetsTasks.map { ... }
waits for the results for each user.
Be careful how many api.pets
calls you are making at the same time if your underlying api doesn't limit concurrency somehow for you. You can limit concurrency with a Semaphore, or using Fan-out.
Side note: emit
is outside coroutineScope
purposely, you need channelFlow { ... }
to emit from a coroutine other that the one the lambda is running in.Diego
11/05/2021, 4:35 PM