RxJava - Somewhat complicated API requests, together with details of progress - rx-java2

To start of, I've only been playing around with RxJava/RxAndroid for a couple of days.
In psuedocode I'm trying to accomplish something like this when loading data from the Guild Wars 2 API:
refreshDatabase {
getAllIds - single API call (50.000+ ids)
chunk ids into chunks of 100 each
for each chunk
getItems - single API call which fetches 100 items
update ui on the progress (# chunks out of # chunks done)
loop through all items when all chunks are fetched
determine if the item is something we're after
save it in our own database
update ui on progress (# items out of # items done)
return bool telling if successful or not
}
I've read through a lot of tutorials and documentation about it, but I'm not really grasping how to code this.
I've got finished and working logic for all of it if I want the sorting/saving to the database to run directly after the chunk has been fetched, but then I'm unable to update the UI with the details.
Since this is an operation that takes quite a while on the first run I'd like the progress to be pretty detailed rather than saving .5-2 seconds of loading time.
Any tips on how I get make it something like what I put in the psuedocode?

Divide an conquer.
You actually have 2 or 3 different operations, so don't try to do this in one function.
First of all, you should use the WorkManager from AndroidX Jetpack to perform the sync the local DB, and Room for reading and writing to a local SQLite database.
Starting with fetching all relevant items and storing them in your DB (not doing UI udpates here):
class MyRepository(private val api: MyApi, private val dao:MyItemDao) {
fun isItemInteresting(item: Item): Boolean {
return true
}
fun fetchAllInterestingItems(): Single<List<Item>> {
return api.getAllIds()
.flatMapIterable { it }
.buffer(100)
.flatMap { api.getItems(it) }
.flatMapIterable { it }
.filter { isItemInteresting(it) }
.toList()
}
fun updateDatabase(items: List<Item>): Completable {
return dao.storeItems(items)
}
}
class SyncWorker(
context: Context,
params: WorkerParameters,
private val repository: MyRepository
) : RxWorker(context, params) {
override fun createWork(): Single<Result> {
return repository.fetchAllInterestingItems()
.flatMapCompletable { repository.updateDatabase(it) }
.subscribeOn(Schedulers.io())
.toSingle { Result.success()}
}
}
This will fetch all items, check which are relevant, and store those in your database (MyApi would be a Retrofit interface and MyItemDao a Room Dao interface).
Reporting progress to the UI can be done through the progress reporting API in WorkManager (version 2.3.0, currently in beta).

Related

how to sync firebase and local data model in flutter

Pretty positive I'm just totally overthinking this or approaching it from an illogical angle.
I'm separating my logic from my ui where button presses call a method located in the userModel which has a change notifier (getting passed into MyApp with a provider). I'm trying to implement firebase but have never called firebase directly from the ui (always just had the requests in the ui code, never used a model).
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:tuple/tuple.dart';
//TODO: firebase has been installed now I need to figure out how to implement it
// TODO: add firebase read and write
class UserModel with ChangeNotifier {
//index = session number
List session = [];
//create an instance of firebase (this might need to go higher in the tree)
var db = FirebaseFirestore.instance;
//TODO:
//convert incoming firebase to JSON
//convert outgoing json to firebase
//track session
// takes in current session data and adds the new chunk
// already recorded (new item in list but time, breaks etc. are adding from the last one)
// IF ADDING TO EXISTING, ALL PARAMETRS MUST BE SET
addSessionChunk(
{required String intention,
int workTime = 0,
String progress = "null",
int breakTime = 0}) {
session.add({
"intention": intention,
"workTime": workTime,
"progress": progress,
"breakTime": breakTime,
});
//firebase update?
}
//TODO: when returning to a previous intention, add to the numbers that were
//TODO: currently only works for 1 call per chunk (no going back to the same intention)
//get previous data from this intention for returning to a task (do
//these update functions updadate the LAST CHUNK in the session
updateChunkWorkTime({required int workTime}) {
//this later)
session.last["workTime"] = workTime;
}
//takes in inputed progress and updates the latest chunk with it
updateChunkProgress({required String progress}) {
session.last["progress"] = progress;
}
//takes inputed breaktime and updates the lastest chunk with it
updateChunkBreakTime({required int breakTime}) {
session.last["breakTime"] = breakTime;
}
//returns tuple of the total time spent working and breaking in the current session
calculateSessionTimeTotal() {
int totalWorkTime = 0;
int totalBreakTime = 0;
for (var chunk in session) {
totalWorkTime += chunk["workTime"] as int;
totalBreakTime += chunk["breakTime"] as int;
}
return Tuple2(totalWorkTime, totalBreakTime);
}
//firebase functions
pushDataUp() {
db.collection("sessions").doc().set({
"currentSession": session,
"total": calculateSessionTimeTotal().toString()
});
}
pullDataDown() {}
}
You can see at the bottom there I started to try and come up with a way to sync the local data state with firebase but am confused. Seems weird for the user to send their data up to firebase then back down into the model which is already holding that data?
Whats the best approach to local model and cloud database interaction? Any guidance in the right direction is greatly appreciated.
What seems weird to you, is actually a quite well defined patterns known as command query responsibility segregation, and is the basic pattern behind most modern UI frameworks. By separating the command (the writing of the data here) from the query (the reading of the data here) each remains simpler, and the total app becomes much easier to reason about.
With Firestore in Flutter, this usually translates into:
The user takes some action.
Your code writes to the database.
Your onSnapshot listener gets triggered with the updated data.
Your code updates the data model/state with the new data.
Which then renders the updated UI.
All of this happens pretty instantly, as Firebase actually handles it locally before even sending the data to the server and handles any exception that may occur during the synchronization with the server.

Flutter Future timeouts not always working correctly

Hey I need some help here for How to use timeouts in flutter correctly. First of all to explain what the main goal is:
I want to recive data from my Firebase RealTime Database but need to secure this request api call with an time out of 15 sec. So after 15 sec my timeout should throw an exception that will return to the Users frontend the alert for reasons of time out.
So I used the simple way to call timeouts on future functions:
This functions should only check if on some firebase node an ID is existing or not:
Inside this class where I have declared this functions I also have an instance which called : timeoutControl this is a class which contains a duration and some reasons for the exceptions.
Future<bool> isUserCheckedIn(String oid, String maybeCheckedInUserIdentifier, String onGateId) async {
try {
databaseReference = _firebaseDatabase.ref("Boarding").child(oid).child(onGateId);
final snapshot = await databaseReference.get().timeout(Duration(seconds: timeoutControl.durationForTimeOutInSec), onTimeout: () => timeoutControl.onEppTimeoutForTask());
if(snapshot.hasChild(maybeCheckedInUserIdentifier)) {
return true;
}
else {
return false;
}
}
catch (exception) {
return false;
}
}
The TimeOutClass where the instance timeoutControl comes from:
class CustomTimeouts {
int durationForTimeOutInSec = 15; // The seconds for how long to try until we throw an timeout exception
CustomTimeouts();
// TODO: Implement the exception reasons here later ...
onEppTimeoutForUpload() {
throw Exception("Some reason ...");
}
onEppTimeoutForTask() {
throw Exception("Some reason ...");
}
onEppTimeoutForDownload() {
throw Exception("Some reason ...");
}
}
So as you can see for example I tried to use this implementation above. This works fine ... sometimes I need to fight with un explain able things -_-. Let me try to introduce what in somecases are the problem:
Inside the frontend class make this call:
bool isUserCheckedIn = await service.isUserCheckedIn(placeIdentifier, userId, gateId);
Map<String, dynamic> data = {"gateIdActive" : isUserCheckedIn};
/*
The response here is an Custom transaction handler which contains an error or an returned param
etc. so this isn't relevant for you ...
*/
_gateService.updateGate(placeIdentifier, gateId, data).then((response) {
if(response.hasError()) {
setState(() {
EppDialog.showErrorToast(response.getErrorMessage()); // Shows an error message
isSendButtonDiabled = false; /*Reset buttons state*/
});
}
else {
// Create an gate process here ...
createGateEntrys(); // <-- If the closures update was successful we also handle some
// other data inside the RTDB for other reasons here ...
}
});
IMPORTANT to know for you guys is that I am gonna use the returned "boolean" value from this function call to update some other data which will be pushed and uploaded into another RTDB other node location for other reasons. And if this was also successful the application is going on to update some entrys also inside the RTDB -->createGateEntrys()<-- This function is called as the last one and is also marked as an async function and called with its closures context and no await statement.
The Data inside my Firebase RTDB:
"GateCheckIns" / "4mrithabdaofgnL39238nH" (The place identifier) / "NFdxcfadaies45a" (The Gate Identifier)/ "nHz2mhagadzadzgadHjoeua334" : 1 (as top of the key some users id who is checked in)
So on real devices this works always without any problems... But the case of an real device or simulator could not be the reason why I'am faceing with this problem now. Sometimes inside the Simulator this Function returns always false no matter if the currentUsers Identifier is inside the this child nodes or not. Therefore I realized the timeout is always called immediately so right after 1-2 sec because the exception was always one of these I was calling from my CustomTimeouts class and the function which throws the exception inside the .timeout(duration, onTimeout: () => ...) call. I couldn't figure it out because as I said on real devices I was not faceing with this problem.
Hope I was able to explain the problem it's a little bit complicated I know but for me is important that someone could explain me for what should I pay attention to if I am useing timeouts in this style etc.
( This is my first question here on StackOverFlow :) )

Why does my Spring WebFlux controller return data on first request only?

I am working on a web application where the user's connection times out after a specific time (say 20 seconds). For long running requests I have to return a default message ("your request is under process") and then send an email to the user with the actual result.
I couldn't do this with spring web because I didn't know how to specify a timeout in the controller (with customized messages per request) and at the same time let other requests come through and be processed too. That's why I used spring web-flux which has a timeout operator for both Mono and Flux types.
To make the requested process run in a different thread, I have used Sinks. One to receive requests and one to publish the results. My problem is that the response sink can only return one result and subsequent calls to the URL returns an empty response. For example the first call to /reactive/getUser/123456789 returns the user object but subsequent calls return empty.
I'm not sure if the problem is with the Sink I have used or with how I am getting data from it. In the sample code I have used responseSink.asFlux().next() but I have also tried .single(), .toMono(), .take(1). to no avail. I get the same result.
#RequestMapping("/reactive")
#RestController
class SampleController #Autowired constructor(private val externalService: ExternalService) {
private val requestSink = Sinks.many().multicast().onBackpressureBuffer<String>()
private val responseSink = Sinks.many().multicast().onBackpressureBuffer<AppUser>()
init {
requestSink.asFlux()
.map { phoneNumber -> externalService.findByIdOrNull(phoneNumber) }
.doOnNext {
if (it != null) {
responseSink.tryEmitNext(it)
} else {
responseSink.tryEmitError(Throwable("didn't find a value for that phone number"))
}
}
.subscribe()
}
#GetMapping("/getUser/{phoneNumber}")
fun getUser(#PathVariable phoneNumber: String): Mono<String> {
requestSink.tryEmitNext(phoneNumber)
return responseSink.asFlux()
.next()
.map { it.toString() }
.timeout(Duration.ofSeconds(20), Mono.just("processing your request"))
}
}

Vertx CompositeFuture

I am working on a solution where I am using vertx 3.8.4 and vertx-mysql-client 3.9.0 for asynchronous database calls.
Here is the scenario that I have been trying to resolve, in a proper reactive manner.
I have some mastertable records which are in inactive state.
I run a query and get the list of records from the database.
This I did like this :
Future<List<Master>> locationMasters = getInactiveMasterTableRecords ();
locationMasters.onSuccess (locationMasterList -> {
if (locationMasterList.size () > 0) {
uploadTargetingDataForAllInactiveLocations(vertx, amazonS3Utility,
locationMasterList);
}
});
Now in uploadTargetingDataForAllInactiveLocations method, i have a list of items.
What I have to do is, I need to iterate over this list, for each item, I need to download a file from aws, parse the file and insert those data to db.
I understand the way to do it using CompositeFuture.
Can someone from vertx dev community help me with this or with some documentation available ?
I did not find good contents on this by googling.
I'm answering this as I was searching for something similar and I ended up spending some time before finding an answer and hopefully this might be useful to someone else in future.
I believe you want to use CompositeFuture in vertx only if you want to synchronize multiple actions. That means that you either want an action to execute in the case that either all your other actions on which your composite future is built upon succeed or at least one of the action on which your composite future is built upon succeed.
In the first case I would use CompositeFuture.all(List<Future> futures) and in the second case I would use CompositeFuture.any(List<Future> futures).
As per your question, below is a sample code where a list of item, for each item we run an asynchronous operation (namely downloadAnProcessFile()) which returns a Future and we want to execute an action doAction() in the case that all the async actions succeeded:
List<Future> futures = new ArrayList<>();
locationMasterList.forEach(elem -> {
Promise<Void> promise = Promise.promise();
futures.add(promise.future());
Future<Boolean> processStatus = downloadAndProcessFile(); // doesn't need to be boolean
processStatus.onComplete(asyncProcessStatus -> {
if (asyncProcessStatus.succeeded()){
// eventually do stuff with the result
promise.complete();
} else {
promise.fail("Error while processing file whatever");
}
});
});
CompositeFuture.all(futures).onComplete(compositeAsync -> {
if (compositeAsync.succeeded()){
doAction(); // <-- here do what you want to do when all future complete
} else {
// at least 1 future failed
}
});
This solution is probably not perfect and I suppose can be improved but this is what I found works for me. Hopefully will work for someone else.

RXJS : Idiomatic way to create an observable stream from a paged interface

I have paged interface. Given a starting point a request will produce a list of results and a continuation indicator.
I've created an observable that is built by constructing and flat mapping an observable that reads the page. The result of this observable contains both the data for the page and a value to continue with. I pluck the data and flat map it to the subscriber. Producing a stream of values.
To handle the paging I've created a subject for the next page values. It's seeded with an initial value then each time I receive a response with a valid next page I push to the pages subject and trigger another read until such time as there is no more to read.
Is there a more idiomatic way of doing this?
function records(start = 'LATEST', limit = 1000) {
let pages = new rx.Subject();
this.connect(start)
.subscribe(page => pages.onNext(page));
let records = pages
.flatMap(page => {
return this.read(page, limit)
.doOnNext(result => {
let next = result.next;
if (next === undefined) {
pages.onCompleted();
} else {
pages.onNext(next);
}
});
})
.pluck('data')
.flatMap(data => data);
return records;
}
That's a reasonable way to do it. It has a couple of potential flaws in it (that may or may not impact you depending upon your use case):
You provide no way to observe any errors that occur in this.connect(start)
Your observable is effectively hot. If the caller does not immediately subscribe to the observable (perhaps they store it and subscribe later), then they'll miss the completion of this.connect(start) and the observable will appear to never produce anything.
You provide no way to unsubscribe from the initial connect call if the caller changes its mind and unsubscribes early. Not a real big deal, but usually when one constructs an observable, one should try to chain the disposables together so it call cleans up properly if the caller unsubscribes.
Here's a modified version:
It passes errors from this.connect to the observer.
It uses Observable.create to create a cold observable that only starts is business when the caller actually subscribes so there is no chance of missing the initial page value and stalling the stream.
It combines the this.connect subscription disposable with the overall subscription disposable
Code:
function records(start = 'LATEST', limit = 1000) {
return Rx.Observable.create(observer => {
let pages = new Rx.Subject();
let connectSub = new Rx.SingleAssignmentDisposable();
let resultsSub = new Rx.SingleAssignmentDisposable();
let sub = new Rx.CompositeDisposable(connectSub, resultsSub);
// Make sure we subscribe to pages before we issue this.connect()
// just in case this.connect() finishes synchronously (possible if it caches values or something?)
let results = pages
.flatMap(page => this.read(page, limit))
.doOnNext(r => this.next !== undefined ? pages.onNext(this.next) : pages.onCompleted())
.flatMap(r => r.data);
resultsSub.setDisposable(results.subscribe(observer));
// now query the first page
connectSub.setDisposable(this.connect(start)
.subscribe(p => pages.onNext(p), e => observer.onError(e)));
return sub;
});
}
Note: I've not used the ES6 syntax before, so hopefully I didn't mess anything up here.