While debugging rxJava network call in an app i came across to a situation, that if we dispose or clear disposal object returned by subscription of a chain of observables then only first observable gets disposed not other chained observables by flatMap.
Have a look at following demo code snippet:
CompositeDisposable testCompositeDisposal = new CompositeDisposable();
private void testLoadData() {
Disposable disposable = Observable.create(sbr -> {
for (int i = 0; i < 5; i++) {
Thread.sleep(3000);
Log.w("Debug: ", "First: " + i);
sbr.onNext(true);
}
sbr.onComplete();
}).subscribeOn(Schedulers.io()).flatMap(value -> Observable.create(sbr -> {
for (int i = 0; i < 5; i++) {
Thread.sleep(3000);
Log.w("Debug: ", "Second: " + i);
sbr.onNext(true);
}
sbr.onComplete();
})).doOnNext(value -> {
Log.w("Debug: ", "doONNext");
}).doOnDispose(()-> {
Log.w("Debug: ", "doOnDispose: observable has been disposed");
}).subscribe();
testCompositeDisposal.add(disposable);
}
#Override
public void onStop() {
super.onStop();
testCompositeDisposal.clear();
}
output:
W/Debug:: First: 0
W/Debug:: doOnDispose: observable has been disposed // I dispose Observable chain here.
W/Debug:: First: 1
W/Debug:: First: 2
W/Debug:: First: 3
W/Debug:: First: 4
As you can just see in above log output that when i dispose given rxJava observable chain only first observable stops emitting items.
I want to stop all observable those are chained.
What is the idiomatic way to solve this issue?
Two things:
flatMap may pre-consume items from upstream (up to 16 on android);
Second and more applicable to your use-case, before you call onNext you should check whether the observer is disposed (via .isDisposed()) and abort when that happens.
Also, the second flatMap gets terminated (actually it never gets called). The first one continues.
EDIT
private void testLoadData() {
Disposable disposable = Observable.create(sbr -> {
for (int i = 0; i < 5; i++) {
if(sbr.isDisposed()) return; // this will cause subscription to terminate.
Thread.sleep(3000);
Log.w("Debug: ", "First: " + i);
sbr.onNext(true);
}
sbr.onComplete();
}).subscribeOn(Schedulers.io()).flatMap(value -> Observable.create(sbr -> {
for (int i = 0; i < 5; i++) {
Thread.sleep(3000);
Log.w("Debug: ", "Second: " + i);
sbr.onNext(true);
}
sbr.onComplete();
})).doOnNext(value -> {
Log.w("Debug: ", "doONNext");
}).doOnDispose(()-> {
Log.w("Debug: ", "doOnDispose: observable has been disposed");
}).subscribe();
testCompositeDisposal.add(disposable);
}
Related
I am facing an issue here in my code. I am trying to get a collection in Firestore but sometimes the query returns a result and sometimes it doesn't.
Solutions I have applied
Turned off the persistence
Querying data through .get(Source. SERVER)
for (int i = 1; i <= 31; i++) {
String date = String.format("%02d", i) + "-" + String.format("%02d", currentMonth) + "-" + 2022;
int finalI = i;
monthRef.document(date)
.collection("AttendanceSections")
.document(student.getCurrentSection())
.collection("AttendanceStudents")
.whereEqualTo("StudentID", student.getStudentId())
.get(Source.SERVER)
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
if (task.getResult().size() > 0) {
for (QueryDocumentSnapshot document : task.getResult()) {
StudentAttendance studentAttendance = document.toObject(StudentAttendance.class);
studentAttendance.setDate(date);
attendanceList.add(studentAttendance);
}
}
} else {
Log.d(TAG, "onComplete: " + task.getException().getMessage());
}
if (finalI == 31) {
Log.d(TAG, "onComplete: " + attendanceList.size());
progressBar.setVisibility(View.INVISIBLE);
if (attendanceList.size() > 0) {
drawDecorators();
}
}
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
}
});
}
Point to note here is the date that I passing in the loop.
String date = String.format("%02d", i) + "-" + String.format("%02d",
currentMonth) + "-" + 2022;
On 20-09-2022 a collection exists but sometimes it returns results in the task and sometimes it doesn't. I am unable to figure out the solution.
Firestore Structure
Any help would be appreciated.
With your current code, the order that the queries complete in is not guaranteed. If your query for day 31 doesn't have any results (like it would for certain months of the year), it is likely to finish before any of the other days causing your list to be rendered out before it's properly filled.
You need to correct this by correctly waiting for them all to complete first and then hiding the progress bar.
By using some java.util.Stream APIs, you gain the flexibility to arbitrarily apply filters to your collection query, and then fetch and collate all matches into a single list of StudentAttendance instances, while maintaining the correct order.
// create a list to hold all the fetch tasks
List<Task<Stream<StudentAttendance>>> fetchStudentsTasks = new ArrayList<Task<Stream<StudentAttendance>>>(31);
for (int i = 0; i <= 31; i++) {
// for each computed date, fetch the students who attended on those dates
String date = String.format("%02d", i) + "-" + String.format("%02d", currentMonth) + "-" + 2022;
fetchStudentsTasks.add(
monthRef.document(date)
.collection("AttendanceSections")
.document(student.getCurrentSection())
.collection("AttendanceStudents")
.whereEqualTo("StudentID", student.getStudentId()) // omitting this line would get all students in attendance
.get(Source.SERVER)
.continueWith(t -> { // continueWith manipulates completed tasks, returning a new task with the given result
// The t.getResult() below will throw an error if the get
// documents task failed, effectively rethrowing it, which
// is OK here. If you want to ignore any failed queries,
// return Stream.empty() when t.isSuccessful() is false.
return StreamSupport
.stream(t.getResult().spliterator(), false) // streams the documents in the snapshot so we can iterate them
.map(document -> { // take each document and convert it to a StudentAttendance instance, with the right date
StudentAttendance studentAttendance = document.toObject(StudentAttendance.class);
studentAttendance.setDate(date);
return studentAttendance;
}) // this returned Stream is 'open', waiting for further processing
})
);
}
Tasks.whenAll(fetchStudentsTasks)
.addOnSuccessListener(() -> {
// if here, all queries returned successfully, update the list
attendanceList = fetchStudentsTasks
.stream()
.flatMap(t -> t.getResult()) // pull out the streams of StudentAttendance instances and merge them into one stream (closing each child stream when finished)
.collect(Collectors.toList()) // join all the StudentAttendance instances into one large ordered list
Log.d(TAG, "onComplete: " + attendanceList.size());
progressBar.setVisibility(View.INVISIBLE);
if (attendanceList.size() > 0) {
drawDecorators();
}
})
.addOnFailureListener(() -> {
// if here, one or more queries failed
// todo: iterate over fetchStudentsTasks to pull out the exceptions
Log.d(TAG, "One or more fetch students tasks failed.");
});
I have a problem with downloading data from server, to be more specific I cannot implement a "waiting/observing incoming data" behavior. I want to fill up my RecyclerView and Spinner with lists come from server, but right know, the methods return null/empty list. I understand, the problem is that the method finish before the data arrives.
I've checked these two questions which are similar, but none of the answers worked for me:
Waiting for API Calls to complete with RxJava and Retrofit
RXJava retrofit waiting having the UI wait for data to be fetched from API
My code:
API:
#GET("/api/categories")
Observable<Response<JsonObject>> getCategories();
netWorker:
public List<String> getCat() {
List<String> incomingDataSet = new ArrayList<>();
compositeDisposable.add(
apiConnector.getCategories()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(response -> {
if (response.code() >= 200 && response.code() < 300) {
JsonArray incomingArray = response.body().getAsJsonArray("category");
for (int i = 0; i < incomingArray.size(); i++) {
incomingDataSet.add(incomingArray.get(i).getAsJsonObject().get("categoryname").toString().replaceAll("\"", ""));
Log.i("LOG", "downloadCategories response: " + incomingDataSet.toString());
}
} else {
Log.i("LOG", "Response error: " + response.code());
}
})
);
Log.i("LOG", "getCat: " + incomingDataSet.toString());
return incomingDataSet;
}
MainActivity:
List<String> categorylist = new ArrayList<>();
categorylist = netWorker.getCat();
Log.i("LOG", "list:" + categorylist );
So, what I see in log:
First:
getCat: []
list: []
and then:
downloadCategories response: [cat1]
downloadCategories response: [cat1, cat2]
downloadCategories response: [cat1, cat2, cat3]
Show spinner in doOnSubscribe() and hide in doFinally(). Put code that fills RecyclerView inside subscribe() to fill it when data arrives. Otherwise you just get empty new list on Main thread and do not update RecyclerView further.
for (int i = 1; i < 3; i++) {
list.add(new Integer(i));
}
Collections.shuffle(list);
for (int i = 0; i < 2; i++) {
int r = list.get(i);
numeroRandom = Integer.toString(r);
Log.d("DEBUG", "teste random :" + list.get(i));
Log.d("DEBUG", "numeroRandomGerado = " + numeroRandom);
final DatabaseReference questaoRef = fireBaseRef.child("questoes");
Log.d("DEBUG", "Loop Stop here");
questaoRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Log.d("DEBUG", "teste2");
if (dataSnapshot != null) {
Questoes questoes = dataSnapshot.child("id").child(numeroRandom).getValue(Questoes.class);
Log.d("DEBUG", "teste3");
Questoes objetoQuestao = new Questoes();
objetoQuestao.setQuestao(questoes.getQuestao());
objetoQuestao.setOpcaoA(questoes.getOpcaoA());
objetoQuestao.setOpcaoB(questoes.getOpcaoB());
objetoQuestao.setOpcaoC(questoes.getOpcaoC());
objetoQuestao.setOpcaoD(questoes.getOpcaoD());
objetoQuestao.setResultado(questoes.getResultado());
listaQuestoes.add(objetoQuestao);
Log.d("DEBUG", "tamanho = " + listaQuestoes.size());
}
callBack.onCallback(listaQuestoes);
}
#Override
public void onCancelled(DatabaseError databaseError) {
throw databaseError.toException();
}
});
For some reason my loop, stops on Log.d("DEBUG", "Loop Stop here") and back to the start and execute 2 times the rest of code.
this is the Log i get from the code, for some reason stops on the 3° debug and restarts and execute everything all over
teste random :1
numeroRandomGerado = 1
Loop stop here
teste random :2
numeroRandomGerado = 2
Loop stop here - teste 1
teste2
tamanho = 1
callback
teste2
teste3
When the correct would be like this:
teste random :1
numeroRandomGerado = 1
Loop stop here - teste 1
teste2
teste3
tamanho = 1
callBack
teste random :2
numeroRandomGerado = 2
Loop stop here
teste2
teste3
tamanho = 2
callBack
this is because the onDataChange(DataSnapshot dataSnapshot) listener is where the execution becomes asynchronous - while you expect synchronous execution. possibly even because it might have another listener registered for .child("questoes"), while the previously registered listener has not yet returned. try setting breakpoints and step into it, to see in which order the execution actually happens. adding Thread.sleep(2000); after registering the listener, most likely should make it work, as you'd expect it to work.
Hi I have been quite struggling with this for a while. Any help is appreciated.
I have a requirement to run one observable after completion of another observable. So e.g. The following code creates an observable from input value to value + 10.
Observable<ColoredIntegerModel> getSequenceObservable(int value, int delay, int color) {
return Observable.range(value,10)
.map(i -> {
Log.d(TAG, "Value " + i
+ " evaluating on " + Thread.currentThread().getName()
+ " emitting item at " + System.currentTimeMillis());
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
}
return new ColoredIntegerModel(i, color);
});
}
The ColorIntegerModel is as follows
public class ColoredIntegerModel {
private Integer mValue;
private int mColor;
public ColoredIntegerModel(Integer value, int color) {
mValue = value;
mColor = color;
}
public Integer getValue() {
return mValue;
}
public int getColor() {
return mColor;
}
}
I create the two observables as follows and concat them like so .
Observable<ColoredIntegerModel> observable1 = getSequenceObservable(1, 1000, Color.BLUE);
Observable<ColoredIntegerModel> observable11 = getSequenceObservable(11, 1000, Color.RED);
mDisposable =
observable1.concatWith(observable11)
.doOnDispose(() -> {Log.d(TAG, "observable disposed");})
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.repeat(2)
.subscribe((m) -> {
Utils.appendColoredText(mResultTextView, "Adding item "
+ m.getValue().toString() + "\n", m.getColor());
});
The above code prints 1..10 (in blue each item delayed by 1s) and 11..20 (in red).
So far so good.
But my requirement is to create the second observable only after the first is complete. Infact it could be array of observables, where the n+1 observable is only created after the nth is done. Each observable can emit multiple items. Is there any operator to achieve this?
I don't know if I understood it right but if you want to create the Observable when you subscribe to it you need the defer operator
You can start next observable from doOnCompleted() of previous one
Observable<MyData> observable1 = ...;
Observable<MyData> observable2 = ...;
Observable<MyData> observable3 = ...;
Observable
.concat(observable1.doOnCompleted(this::onCompleteObservable1),
observable2.doOnCompleted(this::onCompleteObservable2),
observable3.doOnCompleted(this::onCompleteObservable3))
...
...
Hope this help.
I'm running a JobService every x hours to cleanup duplicates (objects created within 5 seconds).
First I load every MyContainer (sorted or not makes no difference for triggering insertions) and subsequently every object with matching fields and a timestamp within 5secs. Found objects are deleted
//Simplified for this question
public static void doCleanUp(Context context) {
Realm realm = context.mRealm;
realm.executeTransaction(realm1 -> {
RealmResults<MyContainer> allContainer = realm1.where(MyContainer.class)
.findAllSorted("timestamp", Sort.ASCENDING);
// implicit snapshot
for (MyContainer item : allContainer) {
if (item.isValid()) {
RealmResults<MyContainer> toDelete = realm1.where(MyContainer.class)
.notEqualTo("rowId", item.getRowId())
.equalTo("name", item.getName())
.equalTo("path", item.getPath())
.lessThan("timestamp", item.getTimestamp() + 5000)
.findAll();
if (toDelete.size() > 0) {
Log.i(TAG, "RealmCleanupJobService: deleteing objects " + toDelete.size());
toDelete.deleteAllFromRealm();
}
}
}
});
}
In another service a changeListener is listening, however it is only reacting on insertions:
private void setupRealmCollectionListener() {
mThreadPool = Executors.newCachedThreadPool();
mAllContainer = getRealm().where(MyContainer.class).findAllSorted("timestamp", Sort.DESCENDING);
mAllContainer.addChangeListener((myContainers, changeSet) -> {
if (changeSet == null) return;
OrderedCollectionChangeSet.Range[] insertions = changeSet.getInsertionRanges();
for (OrderedCollectionChangeSet.Range range : insertions) {
for (int i = 0; i < range.length; i++) {
MyContainer ac = getRealm().copyFromRealm(myContainers.get(range.startIndex + i));
mThreadPool.execute(() -> {
// ...
});
}
}
});
}
Deletion works as expected. However, some insertions appear:
How do i combat this and why insertions are appearing? I thought about some workarounds, like removing and readding the listener.
Tested with Realm 3.7.2 and 3.5.0
Android Studio 3.0 Beta 5
CompileSdk 26, Target 25