Is there a way to sort a collection emitted by a flow in a custom order like:
fun getList():Flow<Something>
fun main(){
launch{
getList().filter{}.map{}.sortBy{
//
}.toList()
}
}
You can toList() first and then sortBy(). Sorting a flow does not always make sense because a flow, by definition, does not know if there are going to be any more elements in the stream.
You can apply some actions like that:
getList().transform {
//it - list
// sortedList - some function to perform sorting or something else
emit(sortedList(it))
}
UPD: You can use map(similar to "transform", but more simple) and filter(it's used to emit only specific values of the flow) functions as well to perform some actions. "transform" function allows you to perform more specific actions. In that case they are same.
getList().map {
sortedList(it)
}
Related
I'm using an API that returns a text like this:
BW3511,HGP,ITP,Canceled,32.
I have to continue fetching until I get a response that is not "Canceled".
this code fetches the data:
val flightResponse = async {
println("Started fetching Flight info.")
client.get<String>(FLIGHT_ENDPOINT).also {
println("Finished fetching Flight info.")
}
}
the client.get can only be called within The coroutineScope body, also the flightResponse type is Deferred<String>.
check if it is canceled:
fun isCanceled(
flightResponse: String
) : Boolean {
val (_, _, _, status, _) = flightResponse.split(",")
return status == "Canceled"
}
how can I repeat client.get<String>(FLIGHT_ENDPOINT) until my condition is met using Functional Programming style?
I tried using takeIf but I have to get at least one result and it cannot be a nullable type.
As said in the comment by #Jorn, this looks like an overuse of functional style. It can be implemented by a simple loop and this way it will be probably more clear to the reader:
fun getNextNotCancelled() {
while (true) {
val response = client.get<String>(FLIGHT_ENDPOINT)
if (!isCanceled(response)) return response
}
}
If your real case is more complex, so you have several filters, etc. or for any other reason you really need to do this declaratively, then you need to create some kind of an infinite generator. For classic synchronous code that means sequence and for asynchronous - flow.
Example using a sequence:
generateSequence { client.get<String>(FLIGHT_ENDPOINT) }
.first { !isCanceled(it) }
Flow:
flow {
while (true) {
emit(client.get<String>(FLIGHT_ENDPOINT))
}
}.first { !isCanceled(it) }
As you said you use coroutines, I assume you would like to go for the latter. And as you can see, it is pretty similar to our initial loop-based approach, only more complicated. Of course, we can create a similar generateFlow() utility function and then it would be shorter.
I am trying to write more idiomatic Kotlin code and I am stuck with the best way to refactor this if condition. Basically when the condition if true (fragment is GenericActionsBottomSheetDialog instance in a list of Fragments) I return the funcion itself.
Here is what I had and how I refactored it. Is there better way to achieve it? After my refactoring it get worse:
Before refactor:
supportFragmentManager.fragments.iterator().forEach {
if (it is GenericActionsBottomSheetDialog)
return
After refactor:
supportFragmentManager.fragments.iterator().forEach { it ->
it.apply {
takeIf { it is GenericActionsBottomSheetDialog }?.apply { return }}}
If this forEach is the only thing in your current function (which it should IMO), you could get rid of the non-local return by using takeWhile instead:
supportFragmentManager.fragments
.takeWhile { it !is GenericActionsBottomSheetDialog }
.forEach {
// do stuff
}
/!\ be careful that this changes semantics if there is other stuff after the forEach in the same function declared with fun.
If you expect many fragments in the list, you could also use asSequence() before takeWhile so you don't create an intermediate list.
Here's one possibility, which separates the decision from the action:
if (supportFragmentManager.fragments.any{ it is GenericActionsBottomSheetDialog })
return
I think this approach makes the intent clearest. (It's also about the most efficient.)
any() simply checks each item in turn, stopping when it finds a match (or when it reaches the end of the list). Kotlin has many functions like this (inspired by functional programming languages) that use lambdas to operate on lists and other structures. They tend to be named for what they do, rather than how they do it — which makes code using them both short and easy to read. (You should be writing code for people to read, as much as for computers to execute!)
For completeness, here's another approach, which uses filterIsInstance():
if (supportFragmentManager.fragments
.filterIsInstance<GenericActionsBottomSheetDialog>)
.isNotEmpty())
return
There are bound to be many other ways. But I agree with the commenter that your ‘refactored’ approach, while using many more Kotlin functions, has little else to recommend it!
This is an opinion based question, and answers cannot be any different.
That being said: there is nothing wrong with if clauses. From what I can see from your current question, I'd leave it with an if.
Now, if you really do not want to use it, filter elements that are not of type GenericActionsBottomSheetDialog and apply whatever function you want on them (the part that is in your else clause, which we do not see).
EDIT:
In case you only want to check if the object of the GenericActionsBottomSheetDialog exists in the collection, you can perhaps do it like this:
val dialogExists = supportFragmentManager.fragments
.firstOrNull { it is GenericActionsBottomSheetDialog} != null
if (dialogExists) {
return
}
#gidds solution is IMO the most idiomatic one:
if (supportFragmentManager.fragments
.any { it is GenericActionsBottomSheetDialog }) return
I would like to add this solution eliminating the if:
supportFragmentManager.fragments
.firstOrNull { it is GenericActionsBottomSheetDialog }
?.run { return }
It's a matter of taste which one you pick, I prefer the first one.
I was wondering why you use the iterator? You could simply do:
supportFragmentManager.fragments.forEach {
I am trying to get the size of this firebase collection size of documents, and for some reason in Kotlin, I can't seem to get this to work. I have declared a variable to be zero in an int function and I put it inside a for loop where it increments to the size of the range. Then when I return the value, it is zero. Here is the code I have provided, please help me as to why it is returning zero.
This is just what is being passed to the function
var postSize = 0
That is the global variable, now for below
val db = FirebaseFirestore.getInstance()
val first = db.collection("Post").orderBy("timestamp")
getPostSize(first)
This is the function
private fun getPostSize(first: Query){
first.get().addOnSuccessListener { documents ->
for(document in documents) {
Log.d(TAG, "${document.id} => ${document.data}")
getActualPostSize(postSize++)
}
}
return postSize
}
private fun getActualPostSize(sizeOfPost: Int): Int {
// The number does push to what I am expecting right here if I called a print statement
return sizeOfPost // However here it just returns it to be zero again. Why #tenffour04? Why?
}
It is my understanding, according to the other question that this was linked to, that I was suppose to do something like this.
This question has answers that explain how to approach getting results from asynchronous APIs, like you're trying to do.
Here is a more detailed explanation using your specific example since you were having trouble adapting the answer from there.
Suppose this is your original code you were trying to make work:
// In your "calling code" (inside onCreate() or some click listener):
val db = FirebaseFirestore.getInstance()
val first = db.collection("Post").orderBy("timestamp")
val postSize = getPostSize(first)
// do something with postSize
// Elsewhere in your class:
private fun getPostSize(first: Query): Int {
var postSize = 0
first.get().addOnSuccessListener { documents ->
for(document in documents) {
Log.d(TAG, "${document.id} => ${document.data}")
postSize++
}
}
return postSize
}
The reason this doesn't work is that the code inside your addOnSuccessListener is called some time in the future, after getPostSize() has already returned.
The reason asynchronous code is called in the future is because it takes a long time to do its action, but it's bad to wait for it on the calling thread because it will freeze your UI and make the whole phone unresponsive. So the time-consuming action is done in the background on another thread, which allows the calling code to continue doing what it's doing and finish immediately so it doesn't freeze the UI. When the time-consuming action is finally finished, only then is its callback/lambda code executed.
A simple retrieval from Firebase like this likely takes less than half a second, but this is still too much time to freeze the UI, because it would make the phone seem janky. Half a second in the future is still in the future compared to the code that is called underneath and outside the lambda.
For the sake of simplifying the below examples, let's simplify your original function to avoid using the for loop, since it was unnecessary:
private fun getPostSize(first: Query): Int {
var postSize = 0
first.get().addOnSuccessListener { documents ->
postSize = documents.count()
}
return postSize
}
The following are multiple distinct approaches for working with asynchronous code. You only have to pick one. You don't have to do all of them.
1. Make your function take a callback instead of returning a value.
Change you function into a higher order function. Since the function doesn't directly return the post size, it is a good convention to put "Async" in the function name. What this function does now is call the callback to pass it the value you wanted to retrieve. It will be called in the future when the listener has been called.
private fun getPostSizeAsync(first: Query, callback: (Int) -> Unit) {
first.get().addOnSuccessListener { documents ->
val postSize = documents.count()
callback(postSize)
}
}
Then to use your function in your "calling code", you must use the retrieved value inside the callback, which can be defined using a lambda:
// In your "calling code" (inside onCreate() or some click listener):
val db = FirebaseFirestore.getInstance()
val first = db.collection("Post").orderBy("timestamp")
getPostSizeAsync(first) { postSize ->
// do something with postSize inside the lambda here
}
// Don't try to do something with postSize after the lambda here. Code under
// here is called before the code inside the lambda because the lambda is called
// some time in the future.
2. Handle the response directly in the calling code.
You might have noticed in the above solution 1, you are really just creating an intermediate callback step, because you already have to deal with the callback lambda passed to addOnSuccessListener. You could eliminate the getPostSize function completely and just deal with callbacks at once place in your code. I wouldn't normally recommend this because it violates the DRY principle and the principle of avoiding dealing with multiple levels of abstraction in a single function. However, it may be better to start this way until you better grasp the concept of asynchronous code.
It would look like this:
// In your "calling code" (inside onCreate() or some click listener):
val db = FirebaseFirestore.getInstance()
val first = db.collection("Post").orderBy("timestamp")
first.get().addOnSuccessListener { documents ->
val postSize = documents.count()
// do something with postSize inside the lambda here
}
// Don't try to do something with postSize after the lambda here. Code under
// here is called before the code inside the lambda because the lambda is called
// some time in the future.
3. Put the result in a LiveData. Observe the LiveData separately.
You can create a LiveData that will update its observers about results when it gets them. This may not be a good fit for certain situations, because it would get really complicated if you had to turn observers on and off for your particular logic flow. I think it is probably a bad solution for your code because you might have different queries you want to pass to this function, so it wouldn't really make sense to have it keep publishing its results to the same LiveData, because the observers wouldn't know which query the latest postSize is related to.
But here is how it could be done.
private val postSizeLiveData = MutableLiveData<Int>()
// Function name changed "get" to "fetch" to reflect it doesn't return
// anything but simply initiates a fetch operation:
private fun fetchPostSize(query: Query) {
first.get().addOnSuccessListener { documents ->
postSize.value = documents.count()
}
}
// In your "calling code" (inside onCreate() or some click listener):
val db = FirebaseFirestore.getInstance()
val first = db.collection("Post").orderBy("timestamp")
fetchPostSize(first)
postSizeLiveData.observer(this) { postSize ->
// Do something with postSize inside this observer that will
// be called some time in the future.
}
// Don't try to do something with postSize after the lambda here. Code under
// here is called before the code inside the lambda because the lambda is called
// some time in the future.
4. Use a suspend function and coroutine.
Coroutines allow you to write synchronous code without blocking the calling thread. After you learn to use coroutines, they lead to simpler code because there's less nesting of asynchronous callback lambdas. If you look at option 1, it will become very complicated if you need to call more than one asynchronous function in a row to get the results you want, for example if you needed to use postSize to decide what to retrieve from Firebase next. You would have to call another callback-based higher-order function inside the lambda of your first higher-order function call, nesting the future code inside other future code. (This is nicknamed "callback hell".) To write a synchronous coroutine, you launch a coroutine from lifecycleScope (or viewLifecycleOwner.lifecycleScope in a Fragment or viewModelScope in a ViewModel). You can convert your getter function into a suspend function to allow it to be used synchronously without a callback when called from a coroutine. Firebase provides an await() suspend function that can be used to wait for the result synchronously if you're in a coroutine. (Note that more properly, you should use try/catch when you call await() because it's possible Firebase fails to retrieve the documents. But I skipped that for simplicity since you weren't bothering to handle the possible failure with an error listener in your original code.)
private suspend fun getPostSize(first: Query): Int {
return first.get().await().count()
}
// In your "calling code" (inside onCreate() or some click listener):
lifecycleScope.launch {
val db = FirebaseFirestore.getInstance()
val first = db.collection("Post").orderBy("timestamp")
val postSize = getPostSize(first)
// do something with postSize
}
// Code under here will run before the coroutine finishes so
// typically, you launch coroutines and do all your work inside them.
Coroutines are the common way to do this in Kotlin, but they are a complex topic to learn for a newcomer. I recommend you start with one of the first two solutions until you are much more comfortable with Kotlin and higher order functions.
There are multiple async functions that depends one on another.
Consider functions functionA(), functionB(), functionC().
FunctionB will be called based on response callback from functionA, functionC will be called based the response callback from the functionB.
Now i want to send the result of each function as stream using flows function in Kotlin.
Any suggestions to achieve above functionality?
Thanks
Satya
This is pretty straight-forward with the flow builder. When you collect this Flow, it will call each of the functions, one after the other.
val results: Flow<ResultType> = flow {
emit(functionA())
emit(functionB())
emit(functionC())
}
I'm not sure if you're saying the results should be based on the return values of the previous function calls. If so, then you can use if statements.
val results: Flow<ResultType> = flow {
val resultA = functionA()
if (!resultA.someCriteria) {
return#flow
}
emit(resultA)
val resultB = functionB(resultA)
emit(resultB)
//...
}
With RxJava Ive become accustomed to my repositories returning Observables of data which automatically update whenever theres an underlying change. I acheive this by simply having a subject in my repository that gets notified with the relevant change info, and observables like getAll() go off of that.
As an example, take this psuedo code like snippet:
fun getAll(): Observable<List<Model> {
subject
.filter { isChangeRelevant(it) }
.startWith(initialChangeEvent)
.map { queryAll() }
}
Ive been curious about how and if the same thing can be acheived using coroutines only?
You can use Kotlin Coroutines Channels.
If you only want your values to be emitted like a stream (so you can for-each off of it) you can use produce to create them (which returns a ReceiveChannel):
fun test(): ReceiveChannel<Int>{
return produce {
send(1)
send(5)
send(100)
}
}
You can use a for-each (or consumeEach) on the values of test() to receive its values.
If you want your channel to be exactly like RxJava's PublishSubject, you can use ConflatedBroadCastChannel, and emit values to it:
val broadCastChannel = ConflatedBroadcastChannel<Int>()
You can use broadCastChannel.offer(value) to send values to the channel.
To receive values from the channel you can use a simple for-each loop:
for (i in broadCastChannel.openSubscription()) {
//your values
}