Coroutines Flow with Dao and generic Result class - android

I have a Dao class which returns List of Car objects as flow.
#Query("SELECT * FROM Car")
fun getAllCars(): Flow<List<Car>>
In my repository, I can use this Dao as follows
fun getAllCars(): Flow<List<Car>>
//Implementation
override fun getAllCars() = carDao.getAllCars()
I can observe this flow in view model and everything works and life was fine.
Now, after reading the post on Developer Android site about
A generic class that contains data and status about loading this data.
I got inspired, so I read one more post here which discuss about having Result class.
So, I have done some changes to repository and I am not able to solve them.
Error:
suspend fun getSomeData(): Flow<Result<List<Car>>> {
carDao.getAllCars().collect {
if (it.isNotEmpty()) {
return flowOf(Result.Success(it)) //<-- Here I am getting error from IDE
}
else {
val throwable = Throwable()
return flowOf(Result.Failure<List<Car>>(throwable)) //<-- Here I am getting error from IDE
}
}
}
The error is Return is not allowed here and Change to 'return#Collect'
What I want to achieve is:
// At view model side
viewmodelScope.launch {
repo.getSomeData().collect {
if (it == Result.Success) {
//Show data
}
else {
//Show empty screen
}
}
}
Is my approach of implementation of Result is wrong? I am not able to figure out what is wrong. Why I can't just return Flow from a flow

If you want to use Result, you should should return Result < YourClass>. It will look like that :
suspend fun getSomeData(): Result<Flow<List<Car>>> {
return carDao.getAllCars().collect {
if (it.isNotEmpty()) {
Result.Success(flowOf(it))
} else {
Result.Failure(Throwable()))
}
}
}

This is what your function should look like. Note there's no need for it to be a suspend fun.
fun getSomeData(): Flow<Result<List<Car>>> = flow {
carDao.getAllCars().collect {
if (it.isNotEmpty()) {
emit(Result.Success(it))
}
else {
emit(Result.Failure<List<Car>>(Throwable()))
}
}
}
But what it does is nothing more than adding a mapping step, which you can generalize.
fun <T> Flow<List<T>>.toResultFlow(): Flow<Result<List<T>>> = this.map {
if (it.isNotEmpty()) Result.Success(it)
else Result.Failure(Throwable())
}

Related

Koltin Flow is repeating execution using flatMapMerge

I was trying to implement an approach to fetch products from two Data sources (Room & FirebaseFirestore) using Flows.
It was working fine until I noticed that the debugger was returning to the same break point infinitely. When the execution of "ViewmMdel.insertProducts(products)" ends, the debugger returns to Repository.getProducts(//) & repeats.
I changed the approach using only suspending functions & coroutines & works fine but I am curious about how I must to use Flows to implement this approach.
Maybe is only that flatMapMerge is in preview version.
Thanks in advance :D
This one is the implementation:
ViewModel:
fun getProductNames(companyName: String) {
viewModelScope.launch {
repository.getProducts(companyName).catch {
_event.value = AddSaleEvents.ShouldShowLoading(false)
_event.value = AddSaleEvents.ProductsFailureResponse(it.message.toString())
}.collect { products ->
productsList = products
if (products != emptyList<Product>()) {
_event.value = AddSaleEvents.ShouldShowLoading(false)
_event.value = AddSaleEvents.ProductsSuccessfulResponse(products)
insertProducts(products)
} else {
_event.value = AddSaleEvents.ShouldShowLoading(false)
_event.value = AddSaleEvents.ProductsSuccessfulResponse(products)
}
}
}
}
Repository:
#OptIn(FlowPreview::class)
override suspend fun getProducts(compnayName: String): Flow<List<Product>> {
return localDataSource.getProducts().flatMapMerge { list -> // LINE RUNNING INFINITELY
getProductsFromFirebase(list, compnayName)
}.flowOn(Dispatchers.IO).catch {
Log.d("Error", it.message.toString())
}
}
private fun getProductsFromFirebase(products: List<Product>, compnayName: String) = flow {
if (products.isEmpty()) {
remoteDataSource.getProducts(compnayName).collect {
emit(it)
}
} else {
emit(products)
}
}
LocalDataSource with Room:
override suspend fun getProducts(): Flow<List<Product>> = saleDao.getProducts()
Firebase Data Source:
override suspend fun getProducts(company: String): Flow<List<Product>> = flow {
val response = fireStore.collection("products").whereEqualTo("company", company).get()
response.await()
if (response.isSuccessful && !response.result.isEmpty) {
emit(response.result.toObjects(FirebaseProduct::class.java).toEntity())
}
}.catch {
Log.d("Error", it.message.toString())
}
How can I chain the response of a flow to trigger another one inside the MVVM Architecture + Clean Architecture?
6 if it is possible, I want to understand the reason the code is repeating infinitely.
Looks like insertProducts(products) triggers room's DAO.
So localDataSource.getProducts() is a observable read query
Observable queries are read operations that emit new values whenever there are changes to any of the tables that are referenced by the query.
Try to change LocalDataSource
interface SaleDao {
// fun getProducts(): Flow<List<Product>>
suspend fun getProducts(): List<Product>
}

Chaining and mapping kotlin flow

I'm trying to get a result from a flow, that retrieves a list from a room database, and then trying to map the list with another flow inside from another database operation, but I don't know if it is possible and if it is, how to make it, at this time I'm trying to make something like this
fun retrieveOperationsWithDues(client: Long): Flow<List<ItemOperationWithDues>> {
return database.operationsDao.getOperationCliente(client)
.flatMapMerge {
flow<List<ItemOperationWithDues>> {
it.map { itemOperation ->
database.duesDao.cuotasFromOperation(client, itemOperation.id).collectLatest { listDues ->
itemOperation.toItemOperationWithDues(listDues)
}
}
}
}
}
but looks like is not retrieving anything from the collect. Thanks in advice for any help
I think you don't need to use flow builder in flatMapMerge block. For each itemOperation you can call the cuotasFromOperatio() function from the Dao, which returns Flow and use combine() to combine retrieved flows:
fun retrieveOperationsWithDues(client: Long): Flow<List<ItemOperationWithDues>> {
return database.operationsDao.getOperationCliente(client)
.flatMapMerge {
val flows = it.map { itemOperation ->
database.duesDao.cuotasFromOperation(client, itemOperation.id).map { listDues ->
itemOperation.toItemOperationWithDues(listDues)
}
}
combine(flows) { flowArray -> flowArray.toList() }
}
}

Kotlin Coroutines - Suspend function returning a Flow runs forever

I am making a network repository that supports multiple data retrieval configs, therefore I want to separate those configs' logic into functions.
However, I have a config that fetches the data continuously at specified intervals. Everything is fine when I emit those values to the original Flow. But when I take the logic into another function and return another Flow through it, it stops caring about its coroutine scope. Even after the scope's cancelation, it keeps on fetching the data.
TLDR: Suspend function returning a flow runs forever when currentCoroutineContext is used to control its loop's termination.
What am I doing wrong here?
Here's the simplified version of my code:
Fragment calling the viewmodels function that basically calls the getData()
lifecycleScope.launch {
viewModel.getLatestDataList()
}
Repository
suspend fun getData(config: MyConfig): Flow<List<Data>>
{
return flow {
when (config)
{
CONTINUOUS ->
{
//It worked fine when fetchContinuously was ingrained to here and emitted directly to the current flow
//And now it keeps on running eternally
fetchContinuously().collect { updatedList ->
emit(updatedList)
}
}
}
}
}
//Note logic of this function is greatly reduced to keep the focus on the problem
private suspend fun fetchContinuously(): Flow<List<Data>>
{
return flow {
while (currentCoroutineContext().isActive)
{
val updatedList = fetchDataListOverNetwork().await()
if (updatedList != null)
{
emit(updatedList)
}
delay(refreshIntervalInMs)
}
Timber.i("Context is no longer active - terminating the continuous-fetch coroutine")
}
}
private suspend fun fetchDataListOverNetwork(): Deferred<List<Data>?> =
withContext(Dispatchers.IO) {
return#withContext async {
var list: List<Data>? = null
try
{
val response = apiService.getDataList().execute()
if (response.isSuccessful && response.body() != null)
{
list = response.body()!!.list
}
else
{
Timber.w("Failed to fetch data from the network database. Error body: ${response.errorBody()}, Response body: ${response.body()}")
}
}
catch (e: Exception)
{
Timber.w("Exception while trying to fetch data from the network database. Stacktrace: ${e.printStackTrace()}")
}
finally
{
return#async list
}
list //IDE is not smart enough to realize we are already returning no matter what inside of the finally block; therefore, this needs to stay here
}
}
I am not sure whether this is a solution to your problem, but you do not need to have a suspending function that returns a Flow. The lambda you are passing is a suspending function itself:
fun <T> flow(block: suspend FlowCollector<T>.() -> Unit): Flow<T> (source)
Here is an example of a flow that repeats a (GraphQl) query (simplified - without type parameters) I am using:
override fun query(query: Query,
updateIntervalMillis: Long): Flow<Result<T>> {
return flow {
// this ensures at least one query
val result: Result<T> = execute(query)
emit(result)
while (coroutineContext[Job]?.isActive == true && updateIntervalMillis > 0) {
delay(updateIntervalMillis)
val otherResult: Result<T> = execute(query)
emit(otherResult)
}
}
}
I'm not that good at Flow but I think the problem is that you are delaying only the getData() flow instead of delaying both of them.
Try adding this:
suspend fun getData(config: MyConfig): Flow<List<Data>>
{
return flow {
when (config)
{
CONTINUOUS ->
{
fetchContinuously().collect { updatedList ->
emit(updatedList)
delay(refreshIntervalInMs)
}
}
}
}
}
Take note of the delay(refreshIntervalInMs).

I want to call several api and combine the response values of different object types

I want to call several api and combine the response values of different object types.
val itemList : MutableList<BaseItem>
private fun fetchAllData() {
viewModelScope.launch{
val deferreds = listOf(
async { loadData1()},
async { loadData2()}
)
deferreds.awaitAll().forEach {
itemList.add(it)
}
}
}
I want to get a return by combining datatype1 and datatype2 into BaseItem.
Unable to return the callback data from the repository.
I think there's a way to do it using live data. What should I do?
fun loadData1(): ArrayList<DataType1> {
repository.getData1(param, callback) {
onSuccess(List<DataType1>) {
return
}
}
}
fun loadData2(): ArrayList<DataType2> {
repository.getData1(param, callback) {
onSuccess(List<DataType2>) {
return
}
}
}
I'll be waiting for your help.
Well, what I would do is I would switch repository functions to be suspend functions and write the code in synchronized way:
val itemsLiveData = MutableLiveData<BaseItem>()
private fun fetchAllData() = viewModelScope.launch{
try {
val itemList : MutableList<BaseItem>
itemsList.addAll(repository.loadData1(param))
itemsList.addAll(repository.loadData2(param))
itemsLiveData.postValue(itemsList)
} catch(e: Exception) {
// do something with exception
}
}
And if you want to call several Rest API for example, I would go with Retrofit which has built-in support for suspend functions since ver. 2.6.0
https://github.com/square/retrofit/blob/master/CHANGELOG.md#version-260-2019-06-05

Making synchronous calls to Cloud Firestore when running off the main thread

I am building an app based off of the Android Clean Architecture Kotlin version (https://github.com/android10/Android-CleanArchitecture-Kotlin).
Using this architecture, each time you want to invoke a use case, a Kotlin coroutine is launched and the result is posted in the main thread. This is achieved by this code:
abstract class UseCase<out Type, in Params> where Type : Any {
abstract suspend fun run(params: Params): Either<Failure, Type>
fun execute(onResult: (Either<Failure, Type>) -> Unit, params: Params) {
val job = async(CommonPool) { run(params) }
launch(UI) { onResult.invoke(job.await()) }
}
In his example architecture, Mr. Android10 uses Retrofit to make a synchronous api call inside the kotlin couroutine. For example:
override fun movies(): Either<Failure, List<Movie>> {
return when (networkHandler.isConnected) {
true -> request(service.movies(), { it.map { it.toMovie() } }, emptyList())
false, null -> Left(NetworkConnection())
}
}
private fun <T, R> request(call: Call<T>, transform: (T) -> R, default: T): Either<Failure, R> {
return try {
val response = call.execute()
when (response.isSuccessful) {
true -> Right(transform((response.body() ?: default)))
false -> Left(ServerError())
}
} catch (exception: Throwable) {
Left(ServerError())
}
}
'Either' represents a disjoint type, meaning the result will either be a Failure or the object of type T you want.
His service.movies() method is implemented like so (using retrofit)
#GET(MOVIES) fun movies(): Call<List<MovieEntity>>
Now here is my question. I am replacing retrofit with Google Cloud Firestore. I know that currently, Firebase/Firestore is an all async library. I want to know if anyone knows of a method more elegant way of making a synchronous API call to Firebase.
I implemented my own version of Call:
interface Call<T: Any> {
fun execute(): Response<T>
data class Response<T>(var isSuccessful: Boolean, var body: T?, var failure: Failure?)
}
and my API call is implemented here
override fun movieList(): Call<List<MovieEntity>> = object : Call<List<MovieEntity>> {
override fun execute(): Call.Response<List<MovieEntity>> {
return movieListResponse()
}
}
private fun movieListResponse(): Call.Response<List<MovieEntity>> {
var response: Call.Response<List<MovieEntity>>? = null
FirebaseFirestore.getInstance().collection(DataConfig.databasePath + MOVIES_PATH).get().addOnCompleteListener { task ->
response = when {
!task.isSuccessful -> Call.Response(false, null, Failure.ServerError())
task.result.isEmpty -> Call.Response(false, null, MovieFailure.ListNotAvailable())
else -> Call.Response(true, task.result.mapTo(ArrayList()) { MovieEntity.fromSnapshot(it) }, null)
}
}
while (response == null)
Thread.sleep(50)
return response as Call.Response<List<MovieEntity>>
}
Of course, the while loop at the end bothers me. Is there any other, more elegant ways, to wait for the response to be assigned before returning from the movieListResponse method?
I tried calling await() on the Task that is returned from the Firebase get() method, but the movieListResponse method would return immediately anyway. Thanks for the help!
So I found what I was looking for in the Google Tasks API: "If your program is already executing in a background thread you can block a task to get the result synchronously and avoid callbacks" https://developers.google.com/android/guides/tasks#blocking
So my previous problematic code becomes:
private fun movieListResponse(): Call.Response<List<MovieEntity>> {
return try {
val taskResult = Tasks.await(FirebaseFirestore.getInstance().
collection(DataConfig.databasePath + MOVIES_PATH).get(), 2, TimeUnit.SECONDS)
Call.Response(true, taskResult.mapTo(ArrayList()) { MovieEntity.fromSnapshot(it) }, null)
} catch (e: ExecutionException) {
Call.Response(false, null, Failure.ServerError())
} catch (e: InterruptedException) {
Call.Response(false, null, Failure.InterruptedError())
} catch (e: TimeoutException) {
Call.Response(false, null, Failure.TimeoutError())
}
}
Note I no longer need my Thread.sleep while loop.
This code should only be run in a background thread/kotlin coroutine.
This is overengineered, there are several layers trying to do the same thing. I suggest you go back a few steps, undo the abstractions and get into the mood of using coroutines directly. Implement a suspend fun according to this template. You don't need the crutches of Either, handle exceptions in the most natural way: a try-catch around a suspend fun call.
You should end up with a signature as follows:
suspend fun movieList(): List<MovieEntity>
Call site:
launch(UI) {
try {
val list = movieList()
...
} catch (e: FireException) {
// handle
}
}
That's is not the way how firebase works. Firebase is based on callback.
I recommend architecture component's livedata.
Please check the following example.
here is a link: https://android.jlelse.eu/android-architecture-components-with-firebase-907b7699f6a0

Categories

Resources