I have to make N REST API calls and combine the results of all of them, or fail if at least one of the calls failed (returned an error or a timeout).
I want to use RxJava and I have some requirements:
Be able to configure a retry of each individual api call under some circumstances. I mean, if I have a retry = 2 and I make 3 requests each one has to be retried at most 2 times, with at most 6 requests in total.
Fail fast! If one API calls have failed N times (where the N is the configuration of the retries) it doesn't mater if the remaining requests hasn't ended, I want to return an error.
If I wish to make all the request with a single Thread, I would need an async Http Client, wouldn't?
Thanks.
You could use Zip operator to zip all request together once they ends and check there if all of them were success
private Scheduler scheduler;
private Scheduler scheduler1;
private Scheduler scheduler2;
/**
* Since every observable into the zip is created to subscribeOn a different thread, it´s means all of them will run in parallel.
* By default Rx is not async, only if you explicitly use subscribeOn.
*/
#Test
public void testAsyncZip() {
scheduler = Schedulers.newThread();
scheduler1 = Schedulers.newThread();
scheduler2 = Schedulers.newThread();
long start = System.currentTimeMillis();
Observable.zip(obAsyncString(), obAsyncString1(), obAsyncString2(), (s, s2, s3) -> s.concat(s2)
.concat(s3))
.subscribe(result -> showResult("Async in:", start, result));
}
private Observable<String> obAsyncString() {
return Observable.just("Request1")
.observeOn(scheduler)
.doOnNext(val -> {
System.out.println("Thread " + Thread.currentThread()
.getName());
})
.map(val -> "Hello");
}
private Observable<String> obAsyncString1() {
return Observable.just("Request2")
.observeOn(scheduler1)
.doOnNext(val -> {
System.out.println("Thread " + Thread.currentThread()
.getName());
})
.map(val -> " World");
}
private Observable<String> obAsyncString2() {
return Observable.just("Request3")
.observeOn(scheduler2)
.doOnNext(val -> {
System.out.println("Thread " + Thread.currentThread()
.getName());
})
.map(val -> "!");
}
In this example we just concat the results, but instead of do that, you can check the results and do your business logic there.
You can also consider merge or contact also.
you can take a look more examples here https://github.com/politrons/reactive
I would suggest to use an Observable to wrap all the calls.
Let's say you have your function to call the API:
fun restAPIcall(request: Request): Single<HttpResponse>
And you want to call this n times. I am assuming that you want to call them with a list of values:
val valuesToSend: List<Request>
Observable
.fromIterable(valuesToSend)
.flatMapSingle { valueToSend: Request ->
restAPIcall(valueToSend)
}
.toList() // This converts: Observable<Response> -> Single<List<Response>>
.map { responses: List<Response> ->
// Do something with the responses
}
So with this you can call the restAPI from the elements of your list, and have the result as a list.
The other problem is the retries. You said you wanted to retry when an individual cap is reached. This is tricky. I believe there is nothing out of the box in RxJava for this.
You can use retry(n) where you can retry n times in total, but that
is not what you wanted.
There's also a retryWhen { error -> ... } where you can do
something given an exception, but you would know what element throw
the error (unless you add the element to the exception I think).
I have not used the retries before, nevertheless it seems that it retries the whole observable, which is not ideal.
My first approach would be doing something like the following, where you save the count of each element in a dictionary or something like that and only retry if there is not a single element that exceeds your limit. This means that you have to keep a counter and search each time if any of the elements exceeded.
val counter = valuesToSend.toMap()
yourObservable
.map { value: String ->
counter[value] = counter[value]?.let { it + 1 }?: 0 // Update the counter
value // Return again the value so you can use it later for the api call
}
.map { restAPIcall(it) }
// Found a way to take yourObservable and readd the element if it doesn't exceeds
// your limit (maybe in an `onErrorResumeNext` or something). Else throw error
Related
I have been getting this problem now a few times when I'm coding and I think I just don't understand the way SwiftUI execute the order of the code.
I have a method in my context model that gets data from Firebase that I call in .onAppear. But the method doesn't execute the last line in the method after running the whole for loop.
And when I set breakpoints on different places it seems that the code first is just run through without making the for loop and then it returns to the method again and then does one run of the for loop and then it jumps to some other strange place and then back to the method again...
I guess I just don't get it?
Has it something to do with main/background thread? Can you help me?
Here is my code.
Part of my UI-view that calls the method getTeachersAndCoursesInSchool
VStack {
//Title
Text("Settings")
.font(.title)
Spacer()
NavigationView {
VStack {
NavigationLink {
ManageCourses()
.onAppear {
model.getTeachersAndCoursesInSchool()
}
} label: {
ZStack {
// ...
}
}
}
}
}
Here is the for-loop of my method:
//Get a reference to the teacher list of the school
let teachersInSchool = schoolColl.document("TeacherList")
//Get teacherlist document data
teachersInSchool.getDocument { docSnapshot, docError in
if docError == nil && docSnapshot != nil {
//Create temporary modelArr to append teachermodel to
var tempTeacherAndCoursesInSchoolArr = [TeacherModel]()
//Loop through all FB teachers collections in local array and get their teacherData
for name in teachersInSchoolArr {
//Get reference to each teachers data document and get the document data
schoolColl.document("Teachers").collection(name).document("Teacher data").getDocument {
teacherDataSnapshot, teacherDataError in
//check for error in getting snapshot
if teacherDataError == nil {
//Load teacher data from FB
//check for snapshot is not nil
if let teacherDataSnapshot = teacherDataSnapshot {
do {
//Set local variable to teacher data
let teacherData: TeacherModel = try teacherDataSnapshot.data(as: TeacherModel.self)
//Append teacher to total contentmodel array of teacherdata
tempTeacherAndCoursesInSchoolArr.append(teacherData)
} catch {
//Handle error
}
}
} else {
//TODO: Error in loading data, handle error
}
}
}
//Assign all teacher and their courses to contentmodel data
self.teacherAndCoursesInSchool = tempTeacherAndCoursesInSchoolArr
} else {
//TODO: handle error in fetching teacher Data
}
}
The method assigns data correctly to the tempTeacherAndCoursesInSchoolArr but the method doesn't assign the tempTeacherAndCoursesInSchoolArr to self.teacherAndCoursesInSchool in the last line. Why doesn't it do that?
Most of Firebase's API calls are asynchronous: when you ask Firestore to fetch a document for you, it needs to communicate with the backend, and - even on a fast connection - that will take some time.
To deal with this, you can use two approaches: callbacks and async/await. Both work fine, but you might find that async/await is easier to read. If you're interested in the details, check out my blog post Calling asynchronous Firebase APIs from Swift - Callbacks, Combine, and async/await | Peter Friese.
In your code snippet, you use a completion handler for handling the documents that getDocuments returns once the asynchronous call returns:
schoolColl.document("Teachers").collection(name).document("Teacher data").getDocument { teacherDataSnapshot, teacherDataError in
// ...
}
However, the code for assigning tempTeacherAndCoursesInSchoolArr to self.teacherAndCoursesInSchool is outside of the completion handler, so it will be called before the completion handler is even called.
You can fix this in a couple of ways:
Use Swift's async/await for fetching the data, and then use a Task group (see Paul's excellent article about how they work) to fetch all the teachers' data in parallel, and aggregate them once all the data has been received.
You might also want to consider using a collection group query - it seems like your data is structure in a way that should make this possible.
Generally, iterating over the elements of a collection and performing Firestore queries for each of the elements is considered a bad practice as is drags down the performance of your app, since it will perform N+1 network requests when instead it could just send one single network request (using a collection group query).
Why is the res variable always false even though the parameters are correct?
fun isValidUser(_studentEmail: String, _password: String): Boolean {
var res = false
userList.get().addOnSuccessListener { result ->
for (document in result) {
val studentEmail = document.data["studentEmail"] as String
val password = document.data["password"] as String
if (_studentEmail == studentEmail && _password == password) {
res = true
}
}
}
return res
}
This doesn't behave like you think it does.
The userList.get() launches an asynchronous task, which means the program will continue its execution without waiting for the task to be done.
addOnSuccessListener only registers a listener - a piece of code to run when the query successfully completes, but that piece of code will only be run in the future, not right away, because the query takes some time.
When the program reaches the return res, the query still hasn't completed and the listener hasn't been executed yet.
If you want to reason more sequentially about this stuff, you can use Kotlin coroutine extensions to await() the result instead of registering a listener. Check out kotlinx-coroutines-play-services for instance. However, if you have trouble understanding asynchronous behaviour, you should probably read up more about this before diving into coroutines.
Let's assume the following RecordInterceptor to simply return a copy of the received consumer record.
class CustomRecordInterceptor : RecordInterceptor<Any, Any> {
override fun intercept(record: ConsumerRecord<Any, Any>): ConsumerRecord<Any, Any>? {
return with(record) {
ConsumerRecord(
topic(),
partition(),
offset(),
timestamp(),
timestampType(),
checksum(),
serializedKeySize(),
serializedValueSize(),
key(),
value(),
headers(),
leaderEpoch())
}
}
}
With such an interceptor in place, we experience lost records with the following Kafka listener.
Note: record is the result returned by the interceptor.
#KafkaListener(topics = ["topic"])
fun listenToEvents(
record: ConsumerRecord<SpecificRecordBase, SpecificRecordBase?>,
ack: Acknowledgment
) {
if (shouldNegativelyAcknowledge()) {
ack.nack(2_000L)
return
}
processRecord(record)
ack.acknowledge()
}
Whenever shouldNegativelyAcknowledge() is true, we would expect that record to be reprocessed by the listener after > 2 seconds. We are using ackMode = MANUAL.
What we see however is that after a while the skipped record was not reprocessed by the listener: processRecord was never invoked for that record. After a while, the consumer group has a lag of 0.
While debugging, we found this code block in KafkaMessageListenerContainer.ListenerConsumer#handleNack:
if (next.equals(record) || list.size() > 0) {
list.add(next);
}
next is the record after the interceptor treatment (so it's the copy of the original record)
record is the record before the interceptor treatment
Note that next and record can never be equal because ConsumerRecord does not override equals.
Could this be the cause for unexpectedly skipped records, maybe a bug even?
Or is it a misuse of the record interceptor to return a different ConsumerRecord object, not equal to the original?
It's a bug and it does explain why the remaining records are not sent to the listener - please open an issue on GitHub
https://github.com/spring-projects/spring-kafka/issues
I'm trying to get all files from firebase's storage through listAll.
By the way..
storageReference.listAll().addOnSuccessListener { listResult ->
val image_task : FileDownloadTask
for (fileRef in listResult.items) {
fileRef.downloadUrl.addOnSuccessListener { Uri ->
image_list.add(Uri.toString())
println("size1 : " + image_list.size)
}
}
println("size2 : " + image_list.size)
}//addOnSuccessListener
enter image description here
Why is the execution order like this?
How do I solve it??
When you add a listener or callback to something, the code inside the listener will not be called until sometime later. Everything else in the current function will happen first.
You are adding listeners for each item using your for loop. No code in the listeners is running yet. Then your "size2" println call is made after the for loop. At some later time, all your listeners will fire.
If you want asynchronous code like this to be written sequentially, then you need to use coroutines. That's a huge topic, but your code would look something like this (but probably a little more involved than this if you want to properly handle errors). I'm using lifecycleScope from an Android Activity or Fragment for this example. If you're not on Android, you need to use some other CoroutineScope.
The calls to await() are an alternative to adding success and failure listeners. await() suspends the coroutine and then returns a result or throws an exception on failure.
lifecycleScope.launch {
val results = try {
storageReference.listAll().await()
} catch (e: Exception) {
println("Failed to get list: ${e.message}")
return#launch
}
val uris = try {
results.map { it.downloadUrl.await().toString() }
} catch (e: Exception) {
println("Failed to get at least one URI: ${e.message}")
return#launch
}
image_list.addAll(uris)
}
There is nothing wrong with the execution order here.
fileRef.downloadUrl.addOnSuccessListener { Uri ->
the downloadUrl is an asynchronous action which means it doesn't wait for the action to actually complete in order to move along with the code.
You receive the result with the success listener (at least in this case)
If you want to deal with it in a sequential way, look at coroutines.
I have this static (companion object) function to download event information from Firebase Firestore and event images from Firebase Storage:
fun downloadEventInformationAndImages() {
FirebaseFirestore.getInstance().collection("events").document(downloadedEventID)
.get().addOnSuccessListener { snap ->
//Download Event Information Here
//Do stuff
GlobalScope.launch(Dispatchers.Main) {
//Download Event Images Here
val downloadEventImage = FirebaseStorage.getInstance().reference.child("Images/Events/$eventID/eventPhoto.jpeg")
.getBytes(1024 * 1024).asDeferred()
val downloadEventFounderImage = FirebaseStorage.getInstance().reference.child("Images/Users/$founderID/profilePhoto.jpeg")
.getBytes(1024 * 1024).asDeferred()
try {
val downloadedImages = mutableListOf<ByteArray>(
downloadEventImage.await(),
downloadEventFounderImage.await())
// Update stuff on UI
} catch(e: StorageException) {
// Error handling
}
}.addOnFailureListener { exception ->
// Error handling
}
}
}
What I want to do is avoid using GlobalScope but when I tried to add runBlocking to downloadEventInformationAndImages():
fun downloadEventInformationAndImages() = runBlocking {
// Do stuff
launch(Dispatchers.Main) {
it didn't work (It didn't wait Firebase to finish downloading - then I moved runBlocking to inside of function, also didn't work). How can I avoid using GlobalScope? Thanks in advance.
The best option I see here is to pass a CoroutineScope as a parameter of downloadEventInformationAndImages. So that would be
fun downloadEventInformationAndImages(scope: CoroutineScope) {
FirebaseFirestore.getInstance().collection("events").document(downloadedEventID)
.get().addOnSuccessListener { snap ->
//Download Event Information Here
//Do stuff
scope.launch(Dispatchers.Main) { ... }
}
}
The one thing you have to be careful is that every coroutine you launch here is now launched in the scope you pass in, which means that if it fails or is cancelled, it'll also cancel any parent coroutines. To understand how to deal with this, you should check the documentation for Jobs. On the other hand, you can also build your CoroutineScope with a SupervisorJob (mentioned in the documentation link above), where child coroutines fail without affecting the parents. Finally, it's also good practice to cleanup your CoroutineScope when the object that owns it reaches the end of its lifecycle. This will avoid possible memory leaks. The cleanup can be done either with scope.cancel() or scope.coroutineContext.cancelChildren(). The first one terminates the scope's job (which gets propagated to all child jobs), and the second one just cancels any child jobs that may exist. I suggest you spend some of your time just reading articles or even documentation about coroutines, because there are a lot of nuances :)
You can write your function as CoroutineScope extension function:
fun CoroutineScope.downloadEventInformationAndImages() {
...
launch(Dispatchers.Main) {
...
}
and call it from ViewModel or some other place with scope:
uiScope.downloadEventInformationAndImages()