How call the same function with join at Kotlin Coroutines? - kotlin

That is what i'm trying to do; first showing items with loading progress in my ui, when is the api request returned as success then i'm manipulating my list for pass to recyclerview adapter.
So i need call repeatedly updateAndSetAdapterList function in coroutines.
Here is my code that calling same function in Fragment;
lifecycleScope.launch(Dispatchers.IO) {
// Videos
viewModel.dashboardVideosFlow.collectLatest { flow ->
when (flow) {
...
is VideosFlow.DataReceived -> {
val row = SectionVideosBodyData(flow.videos)
updateAndSetAdapterList(1, row) <----
}
}
}
}
lifecycleScope.launch(Dispatchers.IO) {
// Questions
viewModel.dashboardQuestionsFlow.collectLatest { flow ->
when (flow) {
....
is QuestionsFlow.DataReceived -> {
val row = SectionQuestionsBodyData(flow.questions)
updateAndSetAdapterList(3, row) <----
}
}
}
}
And this is the function that does something first on background thread after on main thread;
private suspend fun updateAndSetAdapterList(indexAt: Int? = null, row: BaseAdapterData? = null) {
lifecycleScope.launch(Dispatchers.IO) {
val newRows = mutableListOf<BaseAdapterData>()
newRows.addAll(rows)
if (indexAt != null && row != null) {
newRows.removeAt(indexAt)
newRows.add(indexAt, row)
}
withContext(Dispatchers.Main) {
dashboardAdapter.submitList(newRows)
}
rows = newRows
}
}
I want to use synchronous api call functions but i want to use the ui update function as asynchronous that called from these synchronous functions.

Related

How to remove item from mutableList in kotlin

I am scanning a list and adding an unique item in mutableList. Scanning a item through ScanCallback but below example is using for Kotlin Flow for better understanding and make a simple use case. I am giving an example of emiting different types of item.
Basically I want to remove items from the specific condtions :-
when flow data is finished to emit new values.
when emiting an item, if we no longer receive an item within 30 sec then we remove the item from the list.
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.runBlocking
class ItemList {
val scanResultList = mutableListOf<ScanResults>()
fun doSomething(): Flow<ScanResults> = flow {
(0..20).forEach {
delay(200L)
when (it) {
in 10..12 -> {
emit(ScanResults(Device("item is Adding in range of 10 -- 20")))
}
in 15..18 -> {
emit(ScanResults(Device("item is Adding in range of 15 -- 18")))
}
else -> {
emit(ScanResults(Device("item is Adding without range")))
}
}
}
}
fun main() = runBlocking {
doSomething().collectLatest { value ->
handleScanResult(value)
}
}
private fun handleScanResult(result: ScanResults) {
if (!scanResultList.contains(result)) {
result.device?.name?.let {
if (hasNoDuplicateScanResult(scanResultList, result)) {
scanResultList.add(result)
println("Item added")
}
}
}
}
private fun hasNoDuplicateScanResult(value: List<ScanResults>, result: ScanResults): Boolean {
return value.count { it.device == result.device } < 1
}
data class ScanResults(val device: Device? = null)
data class Device(val name: String? = null)
}
I am not adding Set because in SnapshotStateList is not available in jetpack compose.
I'll try to reword the problem in simple terms. I'll say the input is a Flow of some imaginary data class DeviceInfo so it's easier to describe.
Problem:
There is a source flow of DeviceInfos. We want our output to be a Flow of Set<DeviceInfo>, where the Set is all DeviceInfo's that have been emitted from the source in the past 30 seconds.
(If you want, you can convert this output Flow into State, or collect it and update a mutablestateListOf with it, etc.)
Here is a strategy I thought of. Disclaimer: I haven't tested it.
Tag each incoming DeviceInfo with a unique ID (could be based on system time or a UUID). Add each DeviceInfo to a Map with its latest ID. Launch a child coroutine that delays 30 seconds and then removes the item from the map if the ID matches. If newer values have arrived, then the ID won't match so obsolete child coroutines will expire silently.
val sourceFlow: Flow<DeviceInfo> = TODO()
val outputFlow: Flow<Set<DeviceInfo>> = flow {
coroutineScope {
val tagsByDeviceInfo = mutableMapOf<DeviceInfo, Long>()
suspend fun emitLatest() = emit(tagsByDeviceInfo.keys.toSet())
sourceFlow.collect { deviceInfo ->
val id = System.currentTimeMillis()
if (tagsByDeviceInfo.put(deviceInfo, id) == null) {
emitLatest() // emit if the key was new to the map
}
launch {
delay(30.seconds)
if (tagsByDeviceInfo[deviceInfo] == id) {
tagsByDeviceInfo.remove(deviceInfo)
emitLatest()
}
}
}
}
}

How to invoke function based on condition of iterated value of Mono<List<String>> without using subscribe()?

I want to invoke a function that will notify the admin about some information missing, but I do not want to subscribe to this Mono, because I will subscribe to it later. The problem is I have some log which is called inside doOnSuccess() and when I use subscribe() and then build a response where I zip listOfWords value, the same log is logged twice and I do not want a code to behave that way.
Is there any way to retrieve that value in checkCondition() in a way that will not invoke doOnSuccess() or should I use some other function in merge() that can replace doOnSuccess()?
Should I use subscribe() only once on given Mono or is it allowed to use it multiple times?
Thank you in advance!
The functions are called in the presented order.
Code where log is called:
private fun merge(list1: Mono<List<String>>, list2: Mono<List<String>>) =
Flux.merge(
list1.flatMapMany { Flux.fromIterable(it) },
list2.flatMapMany { Flux.fromIterable(it) }
)
.collectList()
.doOnSuccess { LOG.debug("List of words: $it") }
Code where subscribe is called:
private fun checkCondition(
listOfWords: Mono<List<String>>,
) {
listOfWords.subscribe {
it.forEach { word ->
if (someCondition(word)) {
alarmSystem.notify("Something is missing for word {0}")
}
}
}
}
Code where response is built:
private fun buildResponse(
map: Mono<Map<String, String>>,
list1: List<SomeObject>,
listOfWords: Mono<List<String>>
): Mono<List<Answer>> {
val response = Mono.zip(map, Mono.just(list1), listOfWords)
.map { tuple ->
run {
val tupleMap = tuple.t1
val list = tuple.t2
val words = tuple.t3
list
.filter { someCondition(words) }
.map { obj -> NewObject(x,y) }
}
}

Wait for all volley request in a for loop

In my function, I need to return a list that is populated by a for loop with some Volley Request. So I need to wait that all of these requests to be terminated before return the list.
I think I need the async CoroutineScope to do this work but I don't know how can I wait for all of that response.
This is my code:
suspend fun getListOfAbility(pokemon: Pokemon) : MutableList<Ability> {
val listOfAbility: MutableList<Ability> = emptyList<Ability>() as MutableList<Ability>
CoroutineScope(Dispatchers.IO).launch {
/**
* get the pokemon json
*/
val pokemonJsonObjectRequest = JsonObjectRequest(
Request.Method.GET,
"$pokemonUrl${pokemon.id}",
null,
{
/**
* onResponse
*
* get the list of pokemon abilities
*/
val abilitiesJO = it.getJSONObject("abilities")
val abilityObjectType = object : TypeToken<List<PokemonGson.AbilityObjectGson>>() { }.type
val abilityListGson = Gson().fromJson<List<PokemonGson.AbilityObjectGson>>(abilitiesJO.toString(), abilityObjectType)
/**
* for each ability listed on pokemon info get the full Ability Object
*/
for((index, abilityObjectGson) in abilityListGson.withIndex()) {
val abilityJsonObjectRequest = JsonObjectRequest(
Request.Method.GET,
abilityObjectGson.ability.url,
null,
{
abilityJson ->
/**
* onResponse
*
* get the full ability info
*/
val abilityType = object : TypeToken<AbilityGson>() { }.type
val abilityGson = Gson().fromJson<AbilityGson>(abilityJson.toString(), abilityType)
/**
* fill the Ability entry of listOfAbility with the correct language
*/
val ability = Ability(abilityGson, abilityListGson[index].is_hidden)
listOfAbility.add(ability)
},
{
/**
* onError
*/
Log.d("POKEMON", "Pokemon ability error")
}
)
requestQueue.add(abilityJsonObjectRequest)
}
},
{
/**
* onError
*/
Log.d("POKEMON", "Pokemon request error")
}
)
requestQueue.add(pokemonJsonObjectRequest)
}
//wait
return listOfAbility
}
To use callback-based code in a suspend function, you need to convert it to a suspend function using suspendCoroutine or suspendCancellableCoroutine. So in this case to replace the action of creating a JSONObjectRequest and listener, queuing it to the RequestQueue, and waiting for it somehow, I would create a suspend function like this:
suspend inline fun RequestQueue.getJSONObjectOrNull(
method: Int,
url: String,
jsonRequest: JSONObject?,
crossinline onError: (VolleyError)->Unit = {}
): JSONObject? = suspendCancellableCoroutine { continuation ->
val request = JsonObjectRequest(
method,
url,
jsonRequest,
{ result: JSONObject -> continuation.resume(result) },
{ error ->
onError(error)
continuation.resume(null)
}
)
add(request)
continuation.invokeOnCancellation { request.cancel() }
}
It directly returns the JSONObject result, or null if there's a failure. You can optionally run a callback on errors in case you want to log it.
Then you can use it to write a more sequential version of your function instead of the callback-based version. You can use the pattern of coroutineScope { async { list.map { ... } } }.awaitAll() to convert each item of a list to something else using parallel coroutines.
Here is an untested version of your function. I am having it return an empty list on failure. You could alternatively return null on failure, which might be more useful so the calling function can decide to do something differently when there's a failure.
private fun VolleyError.logDebug() {
Log.d("POKEMON", "Pokemon request error: $this")
}
suspend fun getListOfAbility(pokemon: Pokemon): List<Ability> {
val pokemonJsonObject = requestQueue.getJSONObjectOrNull(Request.Method.GET, "$pokemonUrl${pokemon.id}", null, VolleyError::logDebug)
pokemonJsonObject ?: return emptyList()
val abilitiesJO = pokemonJsonObject.getJSONObject("abilities")
val abilityObjectType = object : TypeToken<List<PokemonGson.AbilityObjectGson>>() {}.type
val abilityListGson: List<Wrapper> = Gson().fromJson<List<PokemonGson.AbilityObjectGson>>(
abilitiesJO.toString(),
abilityObjectType
)
return coroutineScope {
abilityListGson.map {
async {
requestQueue.getJSONObjectOrNull(Request.Method.GET, it.ability.url, null, VolleyError::logDebug)
}
}
}
.awaitAll()
.filterNotNull()
.map { abilityJson ->
val abilityType = object : TypeToken<AbilityGson>() {}.type
val abilityGson = Gson().fromJson<AbilityGson>(abilityJson.toString(), abilityType)
Ability(abilityGson, abilityListGson[index].is_hidden)
}
}

Kotlin \ Android - LiveData async transformation prevent previous result

So I have a LiveData that I transform to an async function that takes a while to execute (like 2 seconds sometimes, or 4 seconds).
sometimes the call takes long, and sometimes it's really fast (depends on the results) sometimes it's instant (empty result)
the problem is that if I have 2 consecutive emits in my LiveData, sometimes the first result takes a while to execute, and the second one will take an instant, than it will show the second before the first, and than overwrite the result with the earlier calculation,
what I want is mroe of a sequential effect. (kinda like RxJava concatMap)
private val _state = query.mapAsync(viewModelScope) { searchString ->
if (searchString.isEmpty()) {
NoSearch
} else {
val results = repo.search(searchString)
if (results.isNotEmpty()) {
Results(results.map { mapToMainResult(it, searchString) })
} else {
NoResults
}
}
}
#MainThread
fun <X, Y> LiveData<X>.mapAsync(
scope: CoroutineScope,
mapFunction: androidx.arch.core.util.Function<X, Y>
): LiveData<Y> {
val result = MediatorLiveData<Y>()
result.addSource(this) { x ->
scope.launch(Dispatchers.IO) { result.postValue(mapFunction.apply(x)) }
}
return result
}
how do I prevent the second result from overwriting the first result?
#MainThread
fun <X, Y> LiveData<X>.mapAsync(
scope: CoroutineScope,
mapFunction: (X) -> Y,
): LiveData<Y> = switchMap { value ->
liveData(scope.coroutineContext) {
withContext(Dispatchers.IO) {
emit(mapFunction(value))
}
}
}

Implement backoff strategy in flow

I'm trying to implement a backoff strategy just using kotlin flow.
I need to fetch data from timeA to timeB
result = dataBetween(timeA - timeB)
if the result is empty then I want to increase the end time window using exponential backoff
result = dataBetween(timeA - timeB + exponentialBackOffInDays)
I was following this article which is explaining how to approach this in rxjava2.
But got stuck at a point where flow does not have takeUntil operator yet.
You can see my implementation below.
fun main() {
runBlocking {
(0..8).asFlow()
.flatMapConcat { input ->
// To simulate a data source which fetches data based on a time-window start-date to end-date
// available with in that time frame.
flow {
println("Input: $input")
if (input < 5) {
emit(emptyList<String>())
} else { // After emitting this once the flow should complete
emit(listOf("Available"))
}
}.retryWhenThrow(DummyException(), predicate = {
it.isNotEmpty()
})
}.collect {
//println(it)
}
}
}
class DummyException : Exception("Collected size is empty")
private inline fun <T> Flow<T>.retryWhenThrow(
throwable: Throwable,
crossinline predicate: suspend (T) -> Boolean
): Flow<T> {
return flow {
collect { value ->
if (!predicate(value)) {
throw throwable // informing the upstream to keep emitting since the condition is met
}
println("Value: $value")
emit(value)
}
}.catch { e ->
if (e::class != throwable::class) throw e
}
}
It's working fine except even after the flow has a successful value the flow continue to collect till 8 from the upstream flow but ideally, it should have stopped when it reaches 5 itself.
Any help on how I should approach this would be helpful.
Maybe this does not match your exact setup but instead of calling collect, you might as well just use first{...} or firstOrNull{...}
This will automatically stop the upstream flows after an element has been found.
For example:
flowOf(0,0,3,10)
.flatMapConcat {
println("creating list with $it elements")
flow {
val listWithElementCount = MutableList(it){ "" } // just a list of n empty strings
emit(listWithElementCount)
}
}.first { it.isNotEmpty() }
On a side note, your problem sounds like a regular suspend function would be a better fit.
Something like
suspend fun getFirstNonEmptyList(initialFrom: Long, initialTo: Long): List<Any> {
var from = initialFrom
var to = initialTo
while (coroutineContext.isActive) {
val elements = getElementsInRange(from, to) // your "dataBetween"
if (elements.isNotEmpty()) return elements
val (newFrom, newTo) = nextBackoff(from, to)
from = newFrom
to = newTo
}
throw CancellationException()
}