I had been trying to use RxJava with Room. The logic is simple, if there is no row for current date, I will create an instance and insert the row into the databse. The problem is that the row is being inserted but its acting like a loop. When I debug, the code goes to run insertTestType, it completes and then comes to the subscriber part of getTestModel and then the counter is 1 and then stops.
private void getTestModel() {
String date = new SimpleDateFormat("dd-MM-yyyy", Locale.getDefault()).format(new Date());
mCompositeDisposable.add(questionDatabase.questionDao().getTestByDate(date, testType)
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(testModels -> {
if (testModels.size() > 0) {
testModel = testModels.get(0);
} else {
testModel = new TestModel(testType, date);
insertTestType();
}
}, throwable -> Log.e("ErrorRx", "exception getModels")));
}
private void insertTestType() {
Completable.fromAction(() -> questionDatabase.questionDao().insert(testModel))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new DisposableCompletableObserver() {
#Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
#Override
public void onError(Throwable e) {
Log.d(TAG, "onError");
}
});
}
The database is,
#Insert
void insert(TestModel testModel);
#Query("SELECT * FROM " + TestModel.TABLE_NAME+ " WHERE " + TestModel.DATE + " = :date" + " AND testType " + "=:testType")
Flowable<List<TestModel>> getTestByDate(String date, TestType testType);
why its coming back to previous subscriber? Thanks in advance.
The code is running just as it should be :)
When you use Flowable, every time the data is updated the Flowable object will emit automatically, notifying all it's subscribers and that's why it invokes code from subscribe in your first method.
If you don't want such behaviour consider using Maybe - more informations here
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 need to insert lesson object to firebase, so I put here the onData change section of code.
First of all I get data snapshot and insert the lessons that I have in firebase, after that I scan the List of Lessons and check:
if the date and time exist in the firebase in any Lesson so I do something else I insert the lesson object to firebase .
The main problem is :
when I insert the details of the lesson and press add, the lesson enter to the firebase twice minimum, and if I try another insertion the program enter to infinite loop .
will be happy for any help !
ArrayList<Lesson> existLesson=new ArrayList<>();
List<String> keys = new ArrayList<>();
int counter=0;
public void getLessons(DataSnapshot dataSnapshot){
//insert the lessons to "existLesson" arrayList
for (DataSnapshot keyNode : dataSnapshot.getChildren()) {
keys.add(keyNode.getKey());
Lesson lesson = keyNode.getValue(Lesson.class);
existLesson.add(lesson);
Log.i(tag, "data : " + lesson.getSubject());
}//for
}
int flag=1;
#Override
public void addLesson(final String subject, final String topic, final String date, final String time) {
mDatabase.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
getLessons(dataSnapshot);
//Check if date and time is busy
for (Lesson lessonToCheck : existLesson) {
if (lessonToCheck.getDate().equals(date) && lessonToCheck.getTime().equals(time)) {
flag = 0;
} else {
flag = 1;
}
}//for
if (flag == 0) {
Toast.makeText(LessonDetails.this, "date exist", Toast.LENGTH_SHORT).show();
// Check empty lessons
nearestLessons(existLesson, date, time);
} else {
if (flag == 1) {
String id = mDatabase.push().getKey();
Lesson lesson = new Lesson(subject, topic, date, time, id); //create lesson
Toast.makeText(LessonDetails.this,
subject + " - " + topic + " - " + date + " - " + time, Toast.LENGTH_SHORT).show();
mDatabase.child(id).setValue(lesson);
} //add lesson to DB
} //else
Log.i(tag,"end");
} //onDataChange
When you call you're adding a listener to the data at. This listener will immediately read the data and call your onDataChange, and then continues to listen for updates to the data.
For each update to the data, it calls your onDataChange again. And since you're updating the data inside onDataChange, this ends in an endless loop of setValue->onDataChange->setValue->onDataChange->...
To fix this, you'd typically use addListenerForSingleValueEvent instead, as this only gets the value once and doesn't continue listening for changes.
So something like:
mDatabase.addForListenerValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
getLessons(dataSnapshot);
//Check if date and time is busy
for (Lesson lessonToCheck : existLesson) {
if (lessonToCheck.getDate().equals(date) && lessonToCheck.getTime().equals(time)) {
flag = 0;
} else {
flag = 1;
}
}//for
if (flag == 0) {
Toast.makeText(LessonDetails.this, "date exist", Toast.LENGTH_SHORT).show();
// Check empty lessons
nearestLessons(existLesson, date, time);
} else {
if (flag == 1) {
String id = mDatabase.push().getKey();
Lesson lesson = new Lesson(subject, topic, date, time, id); //create lesson
Toast.makeText(LessonDetails.this,
subject + " - " + topic + " - " + date + " - " + time, Toast.LENGTH_SHORT).show();
mDatabase.child(id).setValue(lesson);
} //add lesson to DB
} //else
Log.i(tag,"end");
} //onDataChange
})
Note that, since you're updating the data based on its current value, there's a chance that another user may be doing the same operation at almost the same time. If this can lead to conflicting updates in your use-case, consider using a transaction which combines the read and write from your code into a single (repeatable) operation.
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 want to fill an ArrayList with the results I get back from a Parse query. When I get the results I add them to the ArrayList and print the ArrayList size to the console to make sure the results are added, which is succesful, but when I return the ArrayList it's empty. Can anyone explain to me why this happens?
public ArrayList<ParseObject>findAllGroupByUserId(ParseUser userId){
//TODO hier uit db halen alle groupen van user
final ArrayList<ParseObject> groups = new ArrayList<>();
ParseQuery<Group_user> query = ParseQuery.getQuery("Group_user");
query.whereEqualTo("user_id", userId);
query.findInBackground(new FindCallback<Group_user>() {
#Override
public void done(List<Group_user> objects, ParseException e) {
if (e == null) {
for (Group_user group : objects) {
Log.e("SUCCESS", group.getObjectId() + " , " + group.getGroup_id().getObjectId());
ParseObject g = new Group();
groups.add(g);
}
System.out.println(groups.size() + " :Done method"); //THIS RETURNS 2
} else {
Log.e("ERROR", "message: " + e);
}
Log.e("SUCCESS", "we have " + groups.size() + " results");
}
});
System.out.println(groups.size() + " :return"); // THIS RETURNS 0
return groups;
}
Because findInBackground() runs asynchronously on a different thread. You need to execute your remaining logic from the done() call back to get the populated array.
Think of it like this:
Thread 1 -> invokes findInBackground() -> thread one is running -----------> group is empty until Thread 2 finishes
Thread 2 spawned -> reaches out to server and gets query results -> invokes done call back on Thread 1 (now you have the data ready)
So I'm assuming that Group_user is a subclass of ParseObject that you've already defined. Since the findInBackground is async, you should change logic of the calling of the function to async too. Instead of returning list of objects like you were before, do all the logic in the done function of the query, no need to return.
public void findAllGroupByUserId(ParseUser userId) {
ParseQuery<Group_user> query = ParseQuery.getQuery("Group_user");
query.whereEqualTo("user_id", userId);
query.findInBackground(new FindCallback<Group_user>() {
#Override
public void done(List<Group_user> groups, ParseException e) {
if (e == null && groups != null) {
for (Group_user group : groups) {
// perform all logic here
}
} else {
Log.e("Find Callback", "Oh no! Query failed!");
}
}
});
}
I'm using Parse and I'm doing a query to fetch a table .
As you can see in the code below, the list LOCALparseQuestionList is populated correctly during the for loop inside the findInBackground. Once it's done, the LOCALparseQuestionList is empty (the log prints 0 size and I see the same when using the debugger).
How should I fetch correctly the data and populate my LOCALparseQuestionList?
public List<QuestionStruct> getParseAllQuestions() {
final List<QuestionStruct> LOCALparseQuestionList = new ArrayList<QuestionStruct>();
// Select All Query
ParseQuery<ParseObject> questionQuery = ParseQuery.getQuery("triviaQuestions");
questionQuery.findInBackground(new FindCallback<ParseObject>() {
public void done(List<ParseObject> allQuestions, ParseException e) {
if (e == null) {
parseQuestionList = allQuestions;
Log.d(TAG, "Retrieved " + allQuestions.size() + " All questions");
for (ParseObject qu : allQuestions) {
QuestionStruct currentQuestion = new QuestionStruct();
currentQuestion.setID(qu.getInt("id"));
currentQuestion.setQuestion(qu.getString("question"));
currentQuestion.setCorrectAnswer(qu.getString("correct"));
currentQuestion.setPossibleAnswer(qu.getString("wrong_1"));
currentQuestion.setPossibleAnswer(qu.getString("wrong_2"));
currentQuestion.setPossibleAnswer(qu.getString("wrong_3"));
currentQuestion.setPossibleAnswer(qu.getString("correct"));
LOCALparseQuestionList.add(currentQuestion);
Log.d(TAG, "Retrieved " + LOCALparseQuestionList.size() + " LOCALparseQuestionList ");
}
} else {
Log.d(TAG, "Error: " + e.getMessage());
}
}
});
Log.d(TAG, "questionList size: " + LOCALparseQuestionList.size());
return LOCALparseQuestionList;
}
Its a the number one misunderstanding about asynchronous functions: the code underneath the find function does not run after the find function. It runs before it.
The last log statement in the function logs, and the return statement returns an empty list, because that list is populated later, after the find is done and the results are returned. Anything you do that depend on LOCALparseQuestionList being populated must be done within the find's callback.