Live Data issue inside Loop - android

I have an IOTCamera function inside viewModel with Loop. The function should repeatedly call the repository GET function based on the "selectedSlot" parameter with a delay of 1 minute. My problem here is the loop(repeat()) is not working properly. It's working for the first iteration. But the second iteration never gets called.
fun getIOTCameraData(repository: MainRepository, selectedSlot: Int)= viewModelScope.launch(Dispatchers.Default){
repeat(selectedSlot){
repository.getIOTCameraData(1, 10).asFlow().collect {data->
if (responseStatusIdentification(data)) {
_iotCameraData.postValue(data.data)//Update live data instance for UI
}
}
delay(60000)
}
}
The repository function will call the Retrofit GET API and it will collect the data.
suspend fun getIOTCameraData(page: Int, perPage: Int) = liveData<Resource<IOTCameraResponse>> {
emit(Resource.loading())
try {
val response = iotCameraService?.getIOTCameraData(token = IOT_CAMERA_AUTH, page = page, perPage = perPage)
emit(Resource.success(response))
} catch (e: Exception) {
emit(Resource.error(e.message.toString()))
}
}
Please update if anyone know the reason for this.

The call to collect never returns. If you just need to get a single value and end collecting then you should call first() instead.
Like this:
val data = repository.getIOTCameraData(1, 10).asFlow().first {
responseStatusIdentification(it)
}
_iotCameraData.postValue(data.data)

Related

Is it necessary to use .flowOn(Dispatchers.IO) inside repository class?

I'm trying to implement One Tap, so I have created this function:
override fun oneTapSgnInWithGoogle() = flow {
try {
emit(Result.Loading)
val result = oneTapClient.beginSignIn(signInRequest).await()
emit(Result.Success(result))
} catch (e: Exception) {
emit(Result.Error(e.message))
}
}
//.flowOn(Dispatchers.IO)
And some programmer told me that I need to add .flowOn(Dispatchers.IO) to the above function, so it can be correct. My code work correct without it. Here is how I call this function in the ViewModel:
fun oneTapSignIn() = viewModelScope.launch {
repo.oneTapSignInWithGoogle().collect { response ->
oneTapSignInResponse = response
}
}
Is it really necessary to do that? I'm really confused.
You're calling beginSignIn which returns a Task, so it does its own stuff in the background. Now Task.await is suspending, not blocking, so it won't block the current thread while waiting for the task.
Therefore, the body of your flow doesn't contain any blocking stuff, so there is no reason to use flowOn(Dispatchers.IO) here.

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).

Why can't I call kotlin suspend function under lambda function

Let me start with example code snippets
suspend fun executeLive(result: MutableLiveData<Person>) {
val response = ... //suspend api request
mediatorLiveData.removeSource(response)
mediatorLiveData.addSource(response) {
result.value = sortData(it) // sortData is also suspend function which sortData at Dispatcher.Default
}
}
In this example, sortData can't call under lambda function(in this case addSource).And also I already declare executeLive as suspend, that why suspend api request can start at first. But sortData function show compile time error
Suspend function can only be called from a coroutine body
So how do I change my code structure to solve this problems?
Update: Is there any article about this?
A lambda is generally a callback function. Callback functions are so called because we wrap a block of code in a function, and pass it to someone else (or some place else) to be executed. It is a basic inversion of control where the code is not for you to execute, but someone else to do it (example the framework).
For example when you set a onClickListener on a button, we don't know when it will get called, we pass a lambda for the framework which takes care of the user interaction to call the specified action.
In your case similarly the suspend function is not calling the sortdata, it is passing it to the mediatorLiveData object to call it in its own context. It is not necessary the lambda you passed would be called from a coroutine body, as such this is not allowed.
You can solve this by converting the mediatorLiveData.addSource call into a suspending call itself with suspendCoroutine:
suspend fun executeLive(result: MutableLiveData<Person>) {
val response = ... //suspend api request
mediatorLiveData.removeSource(response)
val data = suspendCoroutine<TypeOfData> { cont ->
mediatorLiveData.addSource(response) { cont.resume(it) }
}
result.value = sortData(data)
}
I've used TypeOfData as a placeholder for whatever the type of data emitted by response is. Note that this will only work if the you're intending for a single emission to happen, though.
If you need to track multiple values, you can experiment with callbackFlow:
suspend fun executeLive(result: MutableLiveData<Person>) {
val response = ... //suspend api request
mediatorLiveData.removeSource(response)
callbackFlow<TypeOfData> {
mediatorLiveData.addSource(response) { offer(it) }
awaitClose { mediatorLiveData.removeSource(response) }
}
.collect { result.value = sortData(it) }
}

Why coroutine's await function doesn't finish?

I am trying coroutines to make network request using Retrofit on background thread. First of all, I changed Retrofit's Call to Deferred. Here's how it looks like:
#GET("some_endpoint")
fun getData(#Query("id") id: Int): Deferred<JsonObject>
So, in order to use above function, I created suspend function and used await. Here's how it looks:
suspend fun getNetworkData(id: Int): Resource<JsonObject> {
try {
val data = api.getData(id).await()
return Resource.success(data)
} catch (e: Exception) {
return Resource.error()
}
}
But, when I debug my app, await never finishes. Break point never comes to return statement. So, I decided to replace Deferred to Retrofit's Call again. And, instead of await, I decided to use Retrofit's execute. Of course, I removed suspend keyword.
Here's how it looks now:
#GET("some_endpoint")
fun getData(#Query("id") id: Int): Call<JsonObject>
fun getNetworkData(id: Int): Resource<JsonObject> {
try {
val data = api.getData(id).execute()
return Resource.success(data.body())
} catch (e: Exception) {
return Resource.error()
}
}
Then, it worked like a charm. I successfully got my data. But, I want to understand why this happened. In others words, why coroutine await call didn't finish or even didn't run catch block, when Retrofit's Call and execute finished task successfully?
If it is needed, I provide how I call above getNetworkData function:
launch {
val result = withContext(Dispatchers.IO) { repo.getNetworkData(id) }
//do something
}

Implementing search that pushes results to list as soon as they become available using rxJava

I need to implement a search on a large data set that can take some time to complete on mobile devices. So I want to display each matching result as soon as it becomes available.
I need to fetch all available data from a data store that decides whether to get them from network or from the device. This call is an Observable. As soon as the data from that Observable becomes available I want to loop over it, apply a search predicate and notify any Observers for any match found.
So far my idea was to use a PublishSubject to subscribe to and call its onNext function every time the search finds a new match. However I can't seem to get the desired behavior to work.
I'm using MVVM + Android Databinding and want to display every matched entry in a RecyclerView so for every onNext event that is received by the observing viewModel I have to call notifyItemRangeInserted on the RecyclerView's adapter.
class MySearch(val dataStore: MyDataStore) {
private val searchSubject = PublishSubject.create<List<MyDto>>()
fun findEntries(query: String): Observable<List<MyDto>> {
return searchSubject.doOnSubscribe {
// dataStore.fetchAll returns an Observable<List<MyDto>>
dataStore.fetchAll.doOnNext {
myDtos -> if (query.isNotBlank()) {
search(query, myDtos)
} else {
searchSubject.onNext(myDtos)
}
}.subscribe(searchSubject)
}
}
private fun(query: String, data: List<MyDto>) {
data.forEach {
if (it.matches(query)) {
// in real life I cache a few results and don't send each single item
searchSubject.onNext(listOf(it))
}
}
}
fun MyDto.matches(query: String): Boolean // stub
}
-
class MyViewModel(val mySearch: MySearch, val viewNotifications: Observer<Pair<Int, Int>>): BaseObservable() {
var displayItems: List<MyItemViewModel> = listOf()
fun loadData(query: String): Subscription {
return mySearch.findEntries(query)
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(this::onSearchResult)
.doOnCompleted(viewNotifications::onCompleted)
.doOnError(viewNotifications::onError)
.subscribe()
}
private fun onSearchResult(List<MyDto> data) {
val lastIndex = displayItems.lastIndex
displayItems = data.map { createItem(it) }
notifyChange()
viewNotifications.onNext(Pair(lastIndex, data.count()))
}
private fun createItem(dto: MyDto): MyItemViewModel // stub
}
The problem I have with the above code is that with an empty query MyViewModel::onSearchResult is called 3 times in a row and when the query is not empty MyViewModel::onSearchResult isn't called at all.
I suspect the problem lies somewhere in the way I have nested the Observables in findEntries or that I'm subscribing wrong / getting data from a wrong thread.
Does anyone have an idea about this?

Categories

Resources