Insert data to firebase -Android - and check specific properties - android

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.

Related

Firestore query doesn't return results sometimes when in a loop

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.");
});

Get Realtime-Update of an Array of DocumentReferences

I'm writing a Game, where the users can join a Lobby to play together. Therefore I update the Lobby and the Users, stored in Firestore. The Lobbys in the Lobby-Collection contain an Id, creator, creationDate and an array of all Members (DocumentReferences to User Objects in a Users Collection). The Users contain an Id, name, mail and an active Lobby. Now, when I update the Entries in the Firestore, in there (Firestore) they seem to be correct.. but when I receive a realtimeUpdate (by adding a SnapshotListener) the array of Members seem to be empty.. but I just inserted the Users to the Array and they ARE saved to Firestore..
Maybe nice to know: I convert the Datasets i get from Firestore to local Java-Objects for better handling with UI things
I also have a local HashMap to more or less cache the Objects so i dont have to always load the from Firestore (I know there is already a cache in the Firestore lib.. but i think i need my own)
At the moment I write the changes directly to Firestore and wait for them to come back via RealtimeUpdate to then update my local Objects. I also tried to update my local Objects and then write them to Firestore.. but then I only append my Users to the Members-Array and the array contains mulitple References of the same user..
/**
* Get a Lobby Object from a DocumentSnapshot from Firestrore
* If the Object already exists it will be loaded from the "CacheMap"
*
* #param documentSnapshot DocumentSnapshot with the data from the Firestore
* #param feedback a method to call when the Lobby was retrieved
*/
public static void getLobbyByDocSnap(DocumentSnapshot documentSnapshot, IFeedback feedback) {
final String METHOD = TAG + " #getLobbyByDocSnap";
String lobby_id = documentSnapshot.getString(FIRESTORE_DOCUMENT_ID);
if (allLoadedLobbys.containsKey(lobby_id)) {
Log.d(METHOD, "found object in map for id: " + lobby_id);
feedback.trigger(allLoadedLobbys.get(lobby_id));
return;
}
Log.d(METHOD, "Could not find in Map.. generate through data");
Lobby lobby = new Lobby();
lobby.setId(lobby_id);
lobby.setPrivateLobby(documentSnapshot.getBoolean(FIRESTORE_DOCUMENT_PRIVATELOBBY));
lobby.setCreationDate(documentSnapshot.getDate(FIRESTORE_DOCUMENT_CREATIONDATE));
allLoadedLobbys.put(lobby.getId(), lobby);
//create all Members of the Lobby as User Objects
final List<User> members = new ArrayList<>();
List<DocumentReference> docmems = (List<DocumentReference>) documentSnapshot.get(FIRESTORE_DOCUMENT_MEMBER);
Log.d(METHOD, "get all members of lobby: " + lobby_id);
for (final DocumentReference docmem : docmems) {
/*docmem.collection(FIRESTORE_DOCUMENT_MEMBER).get()
.addOnSuccessListener(queryDocumentSnapshots -> {
Log.d(METHOD, "Found Members for: "+lobby_id+": "+Arrays.toString(queryDocumentSnapshots.getDocuments().toArray()));
//Convert DocumentReference to User-Object
for (DocumentSnapshot document : queryDocumentSnapshots.getDocuments()) {
Log.d(METHOD, "Get User Object from "+UserManager.class.getCanonicalName());
UserManager.getUserByDocSnap(document, o -> members.add((User) o));
}
});*/
UserManager.getUserByRef(docmem, o -> members.add((User) o));
}
lobby.setMember(members);
Log.d(METHOD, "Start getting the Creator of this Lobby: " + lobby_id);
//create an User-Object for the Creator
UserManager.getUserByRef((DocumentReference) documentSnapshot.get(FIRESTORE_DOCUMENT_CREATOR), o -> {
User creator = (User) o;
lobby.setCreator(creator);
Log.d(METHOD, "Got the Creator, now get the artist for: " + lobby_id);
UserManager.getUserByRef((DocumentReference) documentSnapshot.get(FIRESTORE_DOCUMENT_ARTIST), a -> {
User artist = (User) a;
Log.d(METHOD, "Got the Artist. All Infos collected for: " + lobby_id);
//Create The Lobby-Object
lobby.setArtist(artist);
Log.d(METHOD, "Save the Lobby to the CacheMap: " + lobby.toString());
//add it to the given list and trigger the feedback
feedback.trigger(lobby);
});
});
documentSnapshot.getReference().addSnapshotListener((snapshot, e) -> {
if (e != null) {
Log.w(METHOD+"+new", "Listen failed.", e);
return;
}
if (snapshot != null && snapshot.exists()) {
Log.d(METHOD + "*new", "Current data: " + snapshot.getData());
String update_lobby_id = snapshot.getString(FIRESTORE_DOCUMENT_ID);
Lobby update_lobby = allLoadedLobbys.get(update_lobby_id);
update_lobby.setCreationDate(snapshot.getDate(FIRESTORE_DOCUMENT_CREATIONDATE));
update_lobby.setPrivateLobby(snapshot.getBoolean(FIRESTORE_DOCUMENT_PRIVATELOBBY));
UserManager.getUserByRef(snapshot.getDocumentReference(FIRESTORE_DOCUMENT_ARTIST), o -> update_lobby.setArtist((User) o));
UserManager.getUserByRef(snapshot.getDocumentReference(FIRESTORE_DOCUMENT_CREATOR), o -> update_lobby.setCreator((User) o));
List<User> update_member = update_lobby.getMember();
update_member.clear();
List<DocumentReference> update_docmems = (List<DocumentReference>) documentSnapshot.get(FIRESTORE_DOCUMENT_MEMBER);
//update_lobby.setMember(update_member);
Log.d(METHOD+"*new", "get all updated members of lobby: " + update_lobby_id);
Log.d(METHOD+"*new", "members DocRef List: " + update_docmems);
/*for (final DocumentReference update_docmem : update_docmems) {
Log.d(METHOD+"*new", update_docmem.getId());
UserManager.getUserByRef(update_docmem, o -> {
Log.d(METHOD+"*new",((User) o).toString());
update_lobby.addMember((User) o);
});
}*/
getMemberList(update_docmems, new ArrayList<>(), o -> {
List<User> mems = (List<User>) o;
update_lobby.getMember().clear();
update_lobby.getMember().addAll(mems);
});
} else {
Log.d(METHOD+"*new", "Current data: null");
}
});
}
private static void getMemberList(List<DocumentReference> update_docmems, List<User> member, IFeedback feedback){
final String METHOD = TAG + " #getMemberList";
/*if(null == member){
member = new ArrayList<>();
}*/
if(update_docmems.isEmpty()){
feedback.trigger(member);
return;
}
DocumentReference docref = update_docmems.get(0);
UserManager.getUserByRef(docref, o -> {
member.add((User) o);
Log.d(METHOD, o.toString());
update_docmems.remove(0);
getMemberList(update_docmems, member, feedback);
});
}
The Realtime only provides the "normal" Data but not the array of references. When I initialy load the Data from Firestore i get the actual Data of the Firestore (not empty). But I want to get the whole Document, inluding the "normal" Data (id, creationDate, ...) and the whole array of members.
I already burned 1.5 days to solve this and I cant figure out, whats wrong..
Never mind, i got my error.... really stupid one ^^
in the part where i updated my objects, when the firestore changes.. i used the wrong/old DocumentSnapshot. So I used the inital Members-Array not my new updated one :D
should be:
List<DocumentReference> update_docmems = (List<DocumentReference>) snapshot.get(FIRESTORE_DOCUMENT_MEMBER);
instead of:
List<DocumentReference> update_docmems = (List<DocumentReference>) documentSnapshot.get(FIRESTORE_DOCUMENT_MEMBER);
Now i get my updates correctly :D

Reading data from firebase database fails

I am reading the data from the firebase database.Following is snapshot of the data stored in database.
In the snap string starting with "8SS..." is the uid of the user. Following is the code for retrieving the data from firebase database.
//To check if uid of current user and database user matches.
Query q = FirebaseDatabase.getInstance().getReference().child("Location").child(user.getUid()).equalTo(FirebaseAuth.getInstance().getCurrentUser().getUid());
q.addListenerForSingleValueEvent(
new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for(DataSnapshot singleSnapshot : dataSnapshot.getChildren()){
Log.d(TAG, "Yay!!");
User us = singleSnapshot.getValue(User.class);
String string = "Name: "+ us.getName()+"\nAddress: "+ us.getlat()+ us.getlon()+ "\n\n";
n.setText(string);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
// read query is cancelled.
Log.d(TAG, "loadPost:onCancelled", databaseError.toException());
}
});
User class contains getters and setters.
The error is that only empty Text View appears concluding reading from database fails.
How to evaluate if query is true or false?
What is the error while reading from ValueEventListener()?
I tried using this:
DatabaseReference ref = FirebaseDatabase.getInstance().getReference().child("Location").child("8SS0uk4FmiPUtXP208Tx8Cqxt2z2");
And then calling on ref.addListenerForSingleValueEvent() but still nothing gets displayed.
I tried using this:
DatabaseReference ref = FirebaseDatabase.getInstance().getReference().child("Location").child(user.getUid());
This gives dataSnapShot : "DataSnapshot={key='-Kn...', value="latitude:.., longitude:..., Name:..."}. But this is not how I expected it to be.
The database structure should have been Location --> Uid --> Name : "Jane", .. .
This is my code for inserting data in the database.
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser() ;
refDatabase = FirebaseDatabase.getInstance().getReference().child("Location").child(user.getUid());
DatabaseReference newPost = refDatabase.push();
//the push() command is already creating unique key
Map<String, String> mapname = new HashMap<String, String>();
mapname.put("Name", n.getText().toString());
mapname.put("latitude", Double.toString(lat));
mapname.put("longitude", Double.toString(longt));
mapname.put("user id", user.getUid());
newPost.setValue(mapname);
I solved this question by introducing multiple for loops.
So, the snapshot of my first child was dataSnapShot : "DataSnapshot={key='-Kn...', value="latitude:.., longitude:..., Name:..."}.
Below is the code to extract all the values and keys :
mRef.addValueEventListener(
new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Log.d(TAG, "Children" + dataSnapshot.getKey());
for(DataSnapshot singleSnapshot : dataSnapshot.getChildren()){
String st = singleSnapshot.getKey();
Log.d(TAG, "Yay!!" + singleSnapshot.child(st));
st = "";
int count=0;
for(DataSnapshot singleSnap : singleSnapshot.getChildren()) {
Log.d(TAG, "String" + singleSnap.getValue());
//n.setText(us.getName());
if(count==0) {
st = "Name: " + singleSnap.getValue() + '\n';
}
else if(count==1) {
st = st + "Latitude: " + singleSnap.getValue() + '\n';
}
else if(count==2) {
st = st + "Longitude: " + singleSnap.getValue() + '\n';
}
count++;
}
final TextView rowTextView = new TextView(Menu5.this.getActivity());
rowTextView.setText((CharSequence) st);
ll.addView(rowTextView);
}
}
This gives single key and value pair for every unique id of created by push.So, I had to hard code the concatenation and display as the structure will remain same throughout the app.
Why are you using equal to and then getting the current user. .child(user.getUid()) should already be your current user which gives you the value of the child you are trying to listen to.
I think the uuid's are the children of "8SSOuk.......".
So it should look something like this:
FirebaseDatabase.getInstance().getReference().child("Location").child("8SSOuk.......").child(user.getUid());

Parse.com Android public void done does not return my ArrayList properly

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!");
}
}
});
}

Unable to use the result of a ParseQuery

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.

Categories

Resources