Say I have an API like so:
interface Foo {
val barFlow: Flow<Bar>
}
And I consume it like so:
class FooConsumer(private val foo: Foo) {
init {
CoroutineScope(Dispatchers.IO).launch {
val bar = foo.barFlow.single()
println("Collected bar: $bar)
}
}
}
According to the docs for single a NoSuchElementException can be thrown if the flow is empty. However, this confuses me quite a lot, as a terminal operation on a flow will "await" elements of the flow to be emitted. So how will the call to single know that there were no elements in the flow? Maybe an element just hasn't been emitted yet?
I mean under the hood, the call to single is collecting the source flow before it does the check. Therefore at least 1 item must have been emitted before the check for null is carried out, so that null check should never succeed and a NoSuchElementException should never be thrown (for the case where the flow is of a non nullable type).
So will NoSuchElementException only be a possibility for flows of nullable types?
Here is the source code for single:
/**
* The terminal operator, that awaits for one and only one value to be published.
* Throws [NoSuchElementException] for empty flow and [IllegalStateException] for flow
* that contains more than one element.
*/
public suspend fun <T> Flow<T>.single(): T {
var result: Any? = NULL
collect { value ->
if (result !== NULL) error("Expected only one element")
result = value
}
if (result === NULL) throw NoSuchElementException("Expected at least one element")
#Suppress("UNCHECKED_CAST")
return result as T
}
NoSuchElementException is thrown when the Flow finishes its emission without emitting a single element. One case I can think of right now is when you need to turn a collection into a Flow source. If that collection is empty and you call single on that Flow you will get a NoSuchElementException.
This example may seem absurd but you get the point:
val emptyListFlow = emptyList<Int>().asFlow()
launch {
val data = emptyListFlow.single()
}
In my case, I made a list.first(), where the list was empty
Related
You know that Array and List only store the same data struction.
I run the Code A and get the Result A.
It seems that the Flow can emit both Int value and String value, why?
Code A
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
suspend fun performRequest(request: Int): Int {
delay(1000) // imitate long-running asynchronous work
return request
}
fun main() = runBlocking<Unit> {
(1..3).asFlow() // a flow of requests
.transform { request ->
emit("Making request $request")
if (request >1) {
emit(performRequest(request))
}
}
.collect { response -> println(response) }
}
Result A
Making request 1
Making request 2
2
Making request 3
3
This is not a question of Flow but Java/Kotling generics and type safety.
The type this flow returns is Comperable<*>
val flow: Flow<Comparable<*>> = (1..3).asFlow() // a flow of requests
.transform { request ->
emit("Making request $request")
if (request > 1) {
emit(performRequest(request))
}
If you explicitly specify which value you want to return Flow you can restrict the types.
About generics you can refer here or check any document about generics in java/kotlin, type safety you can refer this question
Also when you are in doubt what your specified type is use alt + enter with Android Studio to see avaialble options and select Specify type explicitly.
Disregarding the nature of this request, you can have the functionality you want by making your flow emit instances of some algebraic data type that is basically a "sum" (from the type-theoretic POV) of your constituent types:
sealed interface Record
data class IntData(val get: Int) : Record
data class Metadata(val get: String) : Record
// somewhere later (flow is of type Flow<Record>)
fun main() = runBlocking<Unit> {
(1..3).asFlow() // a flow of requests
.transform { request ->
emit(Metadata("Making request $request"))
if (request > 1) {
emit(IntData(performRequest(request)))
}
// probably want to handle the `else` case too
}
.collect { response -> println(response) }
}
This would be a good solution since it's extendable (i.e. you can add the other cases later on if you need to).
In your specific case though, since you just want to debug the flow, you might not want to actually emit the "metadata" and just go for the tests of your code directly.
I am learning coroutines and need some help to understand a basic use case.
Implement a non-blocking method that:
Fetches a single item from a (reactive) DB
Determines a range (i.e. the month that the item lives in) based on that item's timestamp
Fetches all items in that month
Returns the items as Flow
Approach
Because it must return a Flow I will not use suspend (like I would when returning a single item). Returning Flow and suspend (which kind of returns a Mono) are most commonly mutually exclusive, right?
So I came up with this signature:
override fun getHistory(beforeUtcMillisExclusive: Long): Flow<Item>
Trying an implementation:
val itemInNextPeriod = itemRepository.findOneByTimestampLessThan(beforeUtcMillisExclusive)
if (itemInNextPeriod == null) {
return emptyFlow()
} else {
val range = calcRange(itemInNextPeriod.timestamp)
return itemRepository.findByTimestampGreaterThanEqualAndTimestampLessThan(range.start, range.end)
}
This gives me on the very first line:
Suspend function 'findOneByTimestampLessThan' should be called only
from a coroutine or another suspend function
I understand the problem that we are not allowed to call a suspend function here and the proposed solution by IntelliJ "adding suspend" does not make sense, when already returning a flow.
So, from this question I got the idea of using a return flow {...}:
return flow {
val itemInNextPeriod = itemRepository.findOneByTimestampLessThan(beforeUtcMillisExclusive)
if (itemInNextPeriod == null) {
return#flow
} else {
val range = calcRange(itemInNextPeriod.timestamp)
return#flow itemRepository.findByTimestampGreaterThanEqualAndTimestampLessThan(range.start,
range.end)
}
}
The second repository call findByTimestampGreaterThanEqualAndTimestampLessThan returns Flow<Item> and I do not understand why I cannot return it.
This function must return a value of type Unit
Type mismatch.
Required:
Unit
Found:
Flow
return#flow returns from the lambda, not from enclosing function.
You need to reemit items from Flow returned by findByTimestampGreaterThanEqualAndTimestampLessThan call into Flow you're building with flow function:
return flow {
val itemInNextPeriod = itemRepository.findOneByTimestampLessThan(beforeUtcMillisExclusive)
if (itemInNextPeriod != null) {
val range = calcRange(itemInNextPeriod.timestamp)
emitAll(itemRepository.findByTimestampGreaterThanEqualAndTimestampLessThan(range.start, range.end))
}
}
I understand that in Kotlin there is no such thing as "Non-local variables" or "Global Variables" I am looking for a way to modify variables in another "Scope" in Kotlin by using the function below:
class Listres(){
var listsize = 0
fun gatherlistresult(){
var listallinfo = FirebaseStorage.getInstance()
.getReference()
.child("MainTimeline/")
.listAll()
listallinfo.addOnSuccessListener {
listResult -> listsize += listResult.items.size
}
}
}
the value of listsize is always 0 (logging the result from inside of the .addOnSuccessListener scope returns 8) so clearly the listsize variable isn't being modified. I have seen many different posts about this topic on other sites , but none fit my usecase.
I simply want to modify listsize inside of the .addOnSuccessListener callback
This method will always be returned 0 as the addOnSuccessListener() listener will be invoked after the method execution completed. The addOnSuccessListener() is a callback method for asynchronous operation and you will get the value if it gives success only.
You can get the value by changing the code as below:
class Demo {
fun registerListResult() {
var listallinfo = FirebaseStorage.getInstance()
.getReference()
.child("MainTimeline/")
.listAll()
listallinfo.addOnSuccessListener {
listResult -> listsize += listResult.items.size
processResult(listsize)
}
listallinfo.addOnFailureListener {
// Uh-oh, an error occurred!
}
}
fun processResult(listsize: Int) {
print(listResult+"") // you will get the 8 here as you said
}
}
What you're looking for is a way to bridge some asynchronous processing into a synchronous context. If possible it's usually better (in my opinion) to stick to one model (sync or async) throughout your code base.
That being said, sometimes these circumstances are out of our control. One approach I've used in similar situations involves introducing a BlockingQueue as a data pipe to transfer data from the async context to the sync context. In your case, that might look something like this:
class Demo {
var listSize = 0
fun registerListResult() {
val listAll = FirebaseStorage.getInstance()
.getReference()
.child("MainTimeline/")
.listAll()
val dataQueue = ArrayBlockingQueue<Int>(1)
listAll.addOnSuccessListener { dataQueue.put(it.items.size) }
listSize = dataQueue.take()
}
}
The key points are:
there is a blocking variant of the Queue interface that will be used to pipe data from the async context (listener) into the sync context (calling code)
data is put() on the queue within the OnSuccessListener
the calling code invokes the queue's take() method, which will cause that thread to block until a value is available
If that doesn't work for you, hopefully it will at least inspire some new thoughts!
I want to handle validation in functional approach. I have user validation logic as below
if user object is null throw an exception
if the user is not active throw an exception
if the user type is super do nothing. If user type is admin do some operation.
In my code snippet I am throwing an exception if generated random number is even, otherwise complete the flow.
* You can edit, run, and share this code.
* play.kotlinlang.org
*/
import java.time.Instant
import java.util.UUID
import java.lang.IllegalStateException
import java.util.Random
fun main() {
val user : User? = User(UUID.randomUUID(),Instant.now(),UserStatus.ACTIVE,UserType.SUPER)
//val user : User? = User(UUID.randomUUID(),Instant.now(),UserStatus.ACTIVE,UserType.NORMAL) // works fine
user?.let{ existing -> existing.takeIf{it.status == UserStatus.ACTIVE}?.let{ activeUser ->
activeUser.takeUnless{ user -> user.userType == UserType.SUPER}?.let{
val number = Random().nextInt(5);
println(number)
if(number %2 == 0) throw IllegalStateException("invalid random number")
}
}?: throw IllegalStateException("User is not active right now")
} ?:throw IllegalStateException("user not created at all")
}
data class User(val uuid:UUID, val created:Instant, val status:UserStatus, val userType:UserType)
enum class UserStatus {
ACTIVE,INACTIVE
}
enum class UserType{
SUPER,NORMAL
}
when generated random number is odd its throwing an error message user is not active right now which is not correct. it should silently complete the function call.Any one help me what's wrong with the code?
In the inner block
existing.takeIf { it.status == UserStatus.ACTIVE }?.let { activeUser ->
activeUser.takeUnless { user -> user.userType == UserType.SUPER }?.let {
val number = Random().nextInt(5);
println(number)
if (number % 2 == 0) throw IllegalStateException("invalid random number")
}
} ?: throw IllegalStateException("User is not active right now")
You have a not null value.
Since you're not chaining the ?. calls. The better idea is to do early return/throw.
val existing = user ?: throw IllegalStateException("user not created at all")
So the nested block can be flattened.
The second exception "User is not active right now" happens when one of the two checks is not met.
The programming concept to describe this kind of business logic is not nullable variables. A regular old if check is much cleaner.
if (existing.status != UserStatus.ACTIVE || existing.userType == UserType.SUPER) {
throw IllegalStateException("User is not active right now")
}
BTW your error message seems wrong.
By "functional approach", I think you mean having the code expression-based. But nullable type is not powerful enough for your use case.
You should be looking for Either or Try. Both of them are available in the Arrow library. They are both monads. Their chaining (flatMap), like the ?.let calls, also require the callback hell.
In some languages, there is syntactic sugar to flatten the callback hell of flatMaps. You can take a look at the Monad Comprehensions, and see if you like that.
If not, no problem. In Kotlin, there is no shame associated with early return/throw.
Be pragmatic.
I am trying to make a class that would take incoming user events, process them and then pass the result to whoever subscribed to it:
class EventProcessor
{
val flux: Flux<Result>
fun onUserEvent1(e : Event)
{
val result = process(e)
// Notify flux that I have a new result
}
fun onUserEvent2(e : Event)
{
val result = process(e)
// Notify flux that I have a new result
}
fun process(e : Event): Result
{
...
}
}
Then the client code can subscribe to EventProcessor::flux and get notified each time a user event has been successfully processed.
However, I do not know how to do this. I tried to construct the flux with the Flux::generate function like this:
class EventProcessor
{
private var sink: SynchronousSink<Result>? = null
val flux: Flux<Result> = Flux.generate{ sink = it }
fun onUserEvent1(e : Event)
{
val result = process(e)
sink?.next(result)
}
fun onUserEvent2(e : Event)
{
val result = process(e)
sink?.next(result)
}
....
}
But this does not work, since I am supposed to immediately call next on the SynchronousSink<Result> passed to me in Flux::generate. I cannot store the sink as in the example:
reactor.core.Exceptions$ErrorCallbackNotImplemented:
java.lang.IllegalStateException: The generator didn't call any of the
SynchronousSink method
I was also thinking about the Flux::merge and Flux::concat methods, but these are static and they create a new Flux. I just want to push things into the existing flux, such that whoever holds it, gets notified.
Based on my limited understanding of the reactive types, this is supposed to be a common use case. Yet I find it very difficult to actually implement it. This brings me to a suspicion that I am missing something crucial or that I am using the library in an odd way, in which it was not intended to be used. If this is the case, any advice is warmly welcome.