I want to cancel kotlin flow if certain condition occurred in code.
Suppose i have a method as following
fun test(): Flow<String> = flow {
val lst = listOf("A", "B", "C")
while(true) {
lst.forEach { emit(it) }
//If some condition occurs, need to return from here, else continue
//How to stop flow here
}
}
and calling it like
test().collect { println(it)}
question is, how to stop flow to produce anything on certain conditions (from flow builder or outside of it)?
fun test(): Flow<String> = flow {
val lst = listOf("A", "B", "C")
while(true) {
lst.forEach { emit(it) }
if (someCondition) {
return#flow
}
}
}
return#flow instantly returns from flow lambda, so the flow will be ended. As another option, you can just break your while(true) loop when your condition occurs.
Related
Earlier, I have used this function:
override fun getAuthResponse() = callbackFlow {
val listener = AuthStateListener {
trySend(it.currentUser == null)
}
auth.addAuthStateListener(listener)
awaitClose {
auth.removeAuthStateListener(listener)
}
}
But due to some constraints that are explained here I had to use:
override fun getAuthResponse(): StateFlow<Boolean> {
val flow = MutableStateFlow(auth.currentUser == null)
val listener = AuthStateListener {
flow.value = it.currentUser == null
}
auth.addAuthStateListener(listener)
return flow
}
Which is what I need, but I cannot find awaitClose {...}, so I can remove the listener. How to remove the listener when using StateFlow?
You can use your callback flow with stateIn to turn it into a StateFlow that only uses a listener when it has collector(s). When it loses all collectors, WhileSubscribed causes the upstream callback flow to terminate, so it will remove the listener. When it gets a new first collector, it restarts the upstream flow from scratch.
To use stateIn, you must have a coroutine scope available to run the state flow conversion in. I'm just using GlobalScope for this example. If this is in a ViewModel you might want to use viewModelScope instead. Or you could make the coroutine scope an input parameter of the function. I think GlobalScope is probably OK due to the use of WhileSubscribed().
override fun getAuthResponse(): StateFlow<Boolean> = callbackFlow {
send(it.currentUser == null)
val listener = AuthStateListener {
trySend(it.currentUser == null)
}
auth.addAuthStateListener(listener)
awaitClose {
auth.removeAuthStateListener(listener)
}
}.stateIn(GlobalScope, SharingStarted.WhileSubscribed(), auth.currentUser == null)
thank you for taking your time to read my problem.
Im currently using Firebase Firestore to retrieve a list of objects that I which to display to the UI, im trying to use a suspend function to fold the accumulative values of a sequence of calls from the Firestore server, but at the moment im unable to pass the result value outside the scope of the coroutine.
This is my fold function:
suspend fun getFormattedList(): FirestoreState {
return foldFunctions(FirestoreModel(""), ::getMatchesFromBackend, ...., ....)
}
This is my custom fold function:
suspend fun foldFunctions(model: FirestoreModel,
vararg functions: suspend (FirestoreModel, SuccessData) -> FirestoreState): FirestoreState {
val successData: SuccessData = functions.fold(SuccessData()) { updatedSuccessData, function ->
val status = function(model, updatedSuccessData)
if (status !is FirestoreState.Continue) {
return status
}
updatedSuccessData <--- I managed to retrieve the list of values correctly here
}
val successModel = SuccessData()
successData.matchList?.let { successModel.matchList = it }
successData.usermatchList?.let { successModel.usermatchList = it }
successData.formattedList?.let { successModel.formattedList = it }
return FirestoreState.Success(successModel) <--- I cant event get to this line with debugger on
}
This is my first function (which is working fine)
suspend fun getMatchesFromBackend(model: FirestoreModel, successData: SuccessData): FirestoreState {
return try {
val querySnapshot: QuerySnapshot? = db.collection("matches").get().await()
querySnapshot?.toObjects(Match::class.java).let { list ->
val matchList = mutableListOf<Match>()
list?.let {
for (document in it) {
matchList.add(Match(document.away_score,
document.away_team,
document.date,
document.home_score,
document.home_team,
document.match_id,
document.matchpoints,
document.played,
document.round,
document.tournament))
}
successData.matchList = matchList <--- where list gets stored
}
}
FirestoreState.Continue
} catch (e : Exception){
when (e) {
is RuntimeException -> FirestoreState.MatchesFailure
is ConnectException -> FirestoreState.MatchesFailure
is CancellationException -> FirestoreState.MatchesFailure
else -> FirestoreState.MatchesFailure
}
}
}
My hypothesis is that the suspen fun get cancelled and the continuation of the scope gets blocked, I have tried to use runBlocking { } without vail. If someone has an idea of how to circumvent this issue I'd be very gratefull.
Let's say I have list of repos. I want to iterate through all of them. As each repo returns with result, I wanted to pass it on.
val repos = listOf(repo1, repo2, repo3)
val deferredItems = mutableListOf<Deferred<List<result>>>()
repos.forEach { repo ->
deferredItems.add(async { getResult(repo) })
}
val results = mutableListOf<Any>()
deferredItems.forEach { deferredItem ->
results.add(deferredItem.await())
}
println("results :: $results")
In the above case, It waits for each repo to return result. It fills the results in sequence, result of repo1 followed by result of repo2. If repo1 takes more time than repo2 to return result, we will be waiting for repo1's result even though we have result for repo2.
Is there any way to pass the result of repo2 as soon as we have the result?
The Flow API supports this almost directly:
repos.asFlow()
.flatMapMerge { flow { emit(getResult(it)) } }
.collect { println(it) }
flatMapMerge first collects all the Flows that come out of the lambda you pass to it and then concurrently collects those and sends them into the downstream as soon as any of them completes.
That's what channels are for:
val repos = listOf("repo1", "repo2", "repo3")
val results = Channel<Result>()
repos.forEach { repo ->
launch {
val res = getResult(repo)
results.send(res)
}
}
for (r in results) {
println(r)
}
This example is incomplete, as I don't close the channel, so the resulting code will be forever suspended. Make sure that in your real code you close the channel once all results are received:
val count = AtomicInteger()
for (r in results) {
println(r)
if (count.incrementAndGet() == repos.size) {
results.close()
}
}
you should use Channels.
suspend fun loadReposConcurrent() = coroutineScope {
val repos = listOf(repo1, repo2, repo3)
val channel = Channel<List<YourResultType>>()
for (repo in repos) {
launch {
val result = getResult(repo)
channel.send(result)
}
}
var allResults = emptyList<YourResultType>()
repeat(repos.size) {
val result = channel.receive()
allResults = allResults + result
println("results :: $result")
//updateUi(allResults)
}
}
in the code above in for (repo in repos) {...} loop all the requests calculated in seprate coroutines with launch and as soon as their result is ready will send to channel.
in repeat(repos.size) {...} the channel.receive() waits for new values from all coroutines and consumes them.
I want to change the function inserted at the timing before the instruction with the same address.
What should I do?
For,example.
int count=10;
void insert_check_code(INS ins){
if(INS_Address(ins) == tmpaddr)
if(count > 5){
INS_InsertCall(ins,IPOINT_BEFORE,count--func)
}else {
INS_InsertCall(ins,IPOINT_BEFORE,count_printfunc)
}
}
In the above example,The count value returns to its original I want to change the function inserted at the timing before the instruction with the same address.
What should I do?
For,example.
int count=10;
void insert_check_code(INS ins){
if(INS_Address(ins) == tmpaddr)
if(count > 5){
INS_InsertCall(ins,IPOINT_BEFORE,count--func)
}else {
INS_InsertCall(ins,IPOINT_BEFORE,count_printfunc)
}
}
In the above example,The count value returns to its original value.
The target program is a simple server program, so we are using the fork () function.
Is it necessary to write a special description in Pintool for a program using the fork () function?value.
Move the if-else code into its own function and insert that function instead:
void conditional_func() {
if(count > 5){
count_decrement_func()
} else {
count_printfunc()
}
}
void insert_check_code(INS ins) {
INS_InsertCall(ins,IPOINT_BEFORE,conditional_func)
}
Regarding fork(), it depends on what behavior you want when fork()ing.
Keep in mind that if the application is multi-threaded, count access must be synchronized.
Let's use the code on the wikipedia page as the example.
semaphore mutex = 1;
semaphore fillCount = 0;
semaphore emptyCount = BUFFER_SIZE;
procedure producer() {
while (true) {
item = produceItem();
down(emptyCount);
down(mutex);
putItemIntoBuffer(item);
up(mutex);
up(fillCount);
}
}
procedure consumer() {
while (true) {
down(fillCount);
down(mutex);
item = removeItemFromBuffer();
up(mutex);
up(emptyCount);
consumeItem(item);
}
}
Why does swapping the position of up(mutex) and up(fillCount) in the producer function guarantee deadlock? I'm trying to come up with an instance, but can't seem to find any.