I'm using an ajax call to spring controller to start a firebase query, save the result to local database and return items saved in the local database (including the results saved from the current and previous firebase queries too). The problem is that since the firebase query runs async, the results from the local database query are returned before the firebase query finishes.
How can I make the returned local database query wait until the firebase query finished?
#PostMapping("/firebase-download")
#ResponseBody
public List<FirebaseTransactionRecord> firebaseDownload() {
firebaseService.downloadAndSaveFirebaseTransactions();
// wait for the query to end
return firebaseTransactionRecordRepository.findBySentFalse();
}
#Override
#Transactional
public void downloadAndSaveFirebaseTransactions() {
final List<FirebaseTransactionRecord> firebaseTransactions = new ArrayList<>();
firebaseDb.child(StringValueConstant.DB_CHILD_TEMPORARY_TRANSACTIONS)
.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
for (DataSnapshot userSnapshot : snapshot.getChildren()) {
userSnapshot.getChildren().forEach(transactionSnapshot -> {
FirebaseTransactionRecord record = firebaseTransactionMapper
.toFirebaseTransactionRecord(transactionSnapshot);
firebaseTransactions.add(record);
});
}
saveFirebaseTransactionRecords(firebaseTransactions);
}
#Override
public void onCancelled(DatabaseError error) {
log.error("Error reading Firebase data:", error);
}
});
}
#Override
#Transactional
public void saveFirebaseTransactionRecords(List<FirebaseTransactionRecord> firebaseTransactions) {
firebaseTransactions.forEach(firebaseTransaction -> {
if (!firebaseTransactionRecordRepository.existsByReferralNumber(firebaseTransaction.getReferralNumber())) {
firebaseTransactionRecordRepository.save(firebaseTransaction);
}
});
}
Something with a CountdownLatch should do the trick.
Set the initial latch counter to 1 (for the main listener), then set it to the number of child nodes in onDataChange. Next pass the latch to saveFirebaseTransactionRecords and decrease it when each transaction completes. Once the latch reaches 0, you can exit out of downloadAndSaveFirebaseTransactions
Related
Greeting everyone, im working a project right now and need help for firebase realtime database.
My firebase Project
As you guys can see in the above picture, inside student, I have matric number, and inside matric number have block and department.
I have a barcode scanner which scan the value of department and return to get the matric number. Any solution.
Below code is my progress.
mCodeScanner.setDecodeCallback(new DecodeCallback() {
#Override
public void onDecoded(#NonNull final Result result) {
runOnUiThread(new Runnable() {
#Override
public void run() {
r = result.getText();
Query s = ref.equalTo("JTMK", "department");
name.setText(r);
}});}});
If you don't know the matric number of the student, indeed a query is required. Assuming that result.getText() returns JTMK, please use the following lines of code:
mCodeScanner.setDecodeCallback(new DecodeCallback() {
#Override
public void onDecoded(#NonNull final Result result) {
String department = result.getText();
DatabaseReference db = FirebaseDatabase.getInstance().getReference();
DatabaseReference studentRef = db.child("Student");
Query queryByDepartment = studentRef.orderByChild("department").equalTo(department).limitToFirst(1);
queryByDepartment.get().addOnCompleteListener(new OnCompleteListener<DataSnapshot>() {
#Override
public void onComplete(#NonNull Task<DataSnapshot> task) {
if (task.isSuccessful()) {
for (DataSnapshot ds : task.getResult().getChildren()) {
String block = ds.child("block").getValue(String.class);
name.setText(block);
Log.d("TAG", block);
}
} else {
Log.d("TAG", task.getException().getMessage()); //Never ignore potential errors!
}
}
});
}
});
Things to notice:
There is no need to use runOnUiThread when reading data from the Realtime Database.
Firebase API is asynchronous. So I recommend you read the following resource:
How to read data from Firebase Realtime Database using get()?
When you run the code, you should see in the logcat BESTARI 4, which will also be set to name TextView.
public List<String> getContactsFromFirebase(){
FirebaseDatabase.getInstance().getReference().child("Users")
.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot snapshot : dataSnapshot.getChildren()) {
Users user = snapshot.getValue(Users.class);
assert user != null;
String contact_found = user.getPhone_number();
mContactsFromFirebase.add(contact_found);
Log.i("Test", mContactsFromFirebase.toString());
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
return mContactsFromFirebase;
}
I can't seem to find the error. In the code above, when I call the log, I get the values from mContactsFromFirebase, but the getContactsFromFirebase() method return an empty list. Could you help me please?
Data is loaded from Firebase asynchronously. Since it may take some time to get the data from the server, the main Android code continues and Firebase calls your onDataChange when the data is available.
This means that by the time you return mContactsFromFirebase it is still empty. The easiest way to see this is by placing a few log statements:
System.out.println("Before attaching listener");
FirebaseDatabase.getInstance().getReference().child("Users")
.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
System.out.println("In onDataChange");
}
#Override
public void onCancelled(DatabaseError databaseError) {
throw databaseError.toException(); // don't ignore errors
}
});
System.out.println("After attaching listener");
When you run this code, it will print:
Before attaching listener
After attaching listener
In onDataChange
That is probably not the order that you expected the output in. As you can see the line after the callback gets called before onDataChange. That explains why the list you return is empty, or (more correctly) it is empty when you return it and only gets filled later.
There are a few ways of dealing with this asynchronous loading.
The simplest to explain is to put all code that returns the list into the onDataChange method. That means that this code is only execute after the data has been loaded. In its simplest form:
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot snapshot : dataSnapshot.getChildren()) {
Users user = snapshot.getValue(Users.class);
assert user != null;
String contact_found = user.getPhone_number();
mContactsFromFirebase.add(contact_found);
System.out.println("Loaded "+mContactsFromFirebase.size()+" contacts");
}
}
But there are more approaches including using a custom callback (similar to Firebase's own ValueEventListener):
Java:
public interface UserListCallback {
void onCallback(List<Users> value);
}
Kotlin:
interface UserListCallback {
fun onCallback(value:List<Users>)
}
Now you can pass in an implementation of this interface to your getContactsFromFirebase method:
Java:
public void getContactsFromFirebase(final UserListCallback myCallback) {
databaseReference.child(String.format("users/%s/name", uid)).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot snapshot : dataSnapshot.getChildren()) {
Users user = snapshot.getValue(Users.class);
assert user != null;
String contact_found = user.getPhone_number();
mContactsFromFirebase.add(contact_found);
System.out.println("Loaded "+mContactsFromFirebase.size()+" contacts");
}
myCallback.onCallback(mContactsFromFirebase);
}
#Override
public void onCancelled(DatabaseError databaseError) {
throw databaseError.toException();
}
});
}
Kotlin:
fun getContactsFromFirebase(myCallback:UserListCallback) {
databaseReference.child(String.format("users/%s/name", uid)).addListenerForSingleValueEvent(object:ValueEventListener() {
fun onDataChange(dataSnapshot:DataSnapshot) {
for (snapshot in dataSnapshot.getChildren())
{
val user = snapshot.getValue(Users::class.java)
assert(user != null)
val contact_found = user.getPhone_number()
mContactsFromFirebase.add(contact_found)
System.out.println("Loaded " + mContactsFromFirebase.size() + " contacts")
}
myCallback.onCallback(mContactsFromFirebase)
}
fun onCancelled(databaseError:DatabaseError) {
throw databaseError.toException()
}
})
And then call it like this:
Java:
getContactsFromFirebase(new UserListCallback() {
#Override
public void onCallback(List<Users> users) {
System.out.println("Loaded "+users.size()+" contacts")
}
});
Kotlin:
getContactsFromFirebase(object:UserListCallback() {
fun onCallback(users:List<Users>) {
System.out.println("Loaded " + users.size() + " contacts")
}
})
It's not as simple as when data is loaded synchronously, but this has the advantage that it runs without blocking your main thread.
This topic has been discussed a lot before, so I recommend you check out some of these questions too:
this blog post from Doug
Setting Singleton property value in Firebase Listener (where I explained how in some cases you can get synchronous data loading, but usually can't)
return an object Android (the first time I used the log statements to explain what's going on)
Is it possible to synchronously load data from Firebase?
https://stackoverflow.com/a/38188683 (where Doug shows a cool-but-complex way of using the Task API with Firebase Database)
How to return DataSnapshot value as a result of a method? (from where I borrowed some of the callback syntax)
This question already has answers here:
Combining Firebase realtime data listener with RxJava
(5 answers)
Closed 2 years ago.
Does anyone knows how to connect Firebase with RxJava so when I load ALL my data from database then it runs arrayAdapter.notifyDataSetChanged() ??
I was thinking to write it in onComplete() method but it still runs before loading all data
Completable.fromCallable(new Callable<List<cards>>() {
#Override
public List<cards> call() throws Exception {
newUserDb.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
if (dataSnapshot.child(currentUID).child("sex").exists()) {
myInfo.put("sex", dataSnapshot.child(currentUID).child("sex").getValue().toString());
}
if (dataSnapshot.child(currentUID).child("dateOfBirth").exists()) {
int myAge = stringDateToAge(dataSnapshot.child(currentUID).child("dateOfBirth").getValue().toString());
myInfo.put("age", String.valueOf(myAge));
}
if (dataSnapshot.child(currentUID).child("connections").child("yes").exists()) {
for (DataSnapshot ds : dataSnapshot.child(currentUID).child("connections").child("yes").getChildren()) {
if (!dataSnapshot.child(currentUID).child("connections").child("matches").hasChild(ds.getKey())) {
Log.d("rxJava", "onDataChange: " + ds.getKey());
first.add(ds.getKey());
getTagsPreferencesUsers(dataSnapshot.child(ds.getKey()), true);
}
}
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
return rowItems;
}
}).subscribeOn(Schedulers.computation())
.subscribe(new CompletableObserver() {
#Override
public void onSubscribe(Disposable d) {
Log.d("rxJava", "Test RxJAVA, onSubscribe");
}
#Override
public void onComplete() {
Log.d("rxJava", "Test RxJAVA, onComplete");
}
#Override
public void onError(Throwable error) {
Log.d("rxJava", "Test RxJAVA, onError");
}
});
and the output is
2020-06-04 23:36:28.797 29515-29515/com.example.tinderapp D/rxJava: Test RxJAVA, onSubscribe
2020-06-04 23:36:28.800 29515-29612/com.example.tinderapp D/rxJava: Test RxJAVA, onComplete
2020-06-04 23:36:29.018 29515-29515/com.example.tinderapp D/rxJava: onDataChange: a4hqGgAJBRTVJOlPp3blNDt5v7q1
2020-06-04 23:36:29.022 29515-29515/com.example.tinderapp D/rxJava: onDataChange: aA9HAOtaB7ao6vzKqqBNp0iaBev2
I would say, this is expected behavior, which is described in the following:
Completable.fromCallable
fromCallable takes a Lambda, which returns a List on subscription. In your case, a database-connection is opened as-well, which is basically fall through, because the callback is registered via callback non-blocking.
subscribeOn
this makes sure, that the subscribeAcutal from fromCallable is called from given scheduler. Therefore the subscribing thread and and emitting thread are decoupled.
You get onComplete first, because the fromCallable will return rowItems immediately and the database connection will stay open, because you did not remove the listener. After a while you get data-base callback logs, because the database connection is still open and the listener is still registered.
You want to actually do something like this:
Single.create<List<Card>> { emitter ->
// register onChange callback to database
// callback will be called, when a value is available
// the Single will stay open, until emitter#onSuccess is called with a collected list.
newUserDb.addListenerForSingleValueEvent {
// do some stuff
emitter.onSuccess(listOf()) // return collected data from database here...
}
emitter.setCancellable {
// unregister addListenerForSingleValueEvent from newUserDb here
}
}.subscribeOn(Schedulers.computation())
.subscribe(
// stuff
)
If you want to have a constant stream of updates, exchange Single with Observable/ Flowable
In Firebase Cloud Functions (Javascript), we use database trigger like below,
functions.database.ref('/DoctorApp/DrTimeSlot/{doctorUID}/Patients/{patientID}')
.onCreate((snapshot, context) => {
// codes here
};
Here, {doctorUID} is not known so all function is working on all UIDs.
Is this possible to use the same method in Android Studio (Java)? below this code is not working for me, can you provide me the alternate of the code.
Query query = rootData.child("DrTimeSlot/{drUID}/Patients/").orderByChild("patientUID").equalTo(auth.getUid());
query.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
//codes here
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
While not possible to use the same syntax as Cloud Functions, your query to find all doctors with a given patient ID can be achieved using:
Query query = rootData.child("DrTimeSlot").orderByChild("Patients/" + auth.getUid()).startAt(""); // startAt("") will exclude non-existent values
query.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot queryDataSnapshot) {
if (queryDataSnapshot.hasChildren()) {
// handle no results
System.out.println("No results returned");
return;
}
for (DataSnapshot doctorDataSnapshot: dataSnapshot.getChildren()) {
// TODO: handle the data
System.out.println(doctorDataSnapshot.getKey() + " => " + doctorDataSnapshot.getValue());
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
// Getting doctors failed, log a message
Log.w(TAG, "findDoctorsForUser:onCancelled", databaseError.toException());
}
});
However, to permit such an operation, the client must have read access to every single doctor's data, even if the user performing the search is not a patient of that doctor. It will also be entirely processed on the client device which means downloading all of the data under "DrTimeSlot" which is not recommended and is also a massive security & privacy risk.
Instead, in the user data of a particular user, store a list of their doctors. When you need to find them, download /users/userId1/doctors and then get the needed patient data from each doctor restricting read/write access to that doctor and that user only.
"users": {
"userId1": {
"doctors": {
"doctorId1": true, // boolean value for simplicity
"doctorId2": "paediatrician", // could also use value for type
"doctorId3": 10, // or number of visits
"doctorId4": false, // or if they are an active patient of this doctor
},
...
},
...
}
In android you cannot do this query child("DrTimeSlot/{drUID}/Patients/"), all 3 nodes need to be known.
If you don't know the drUID, then when you stored it first to the database, you can store it to a variable and pass the variable around, example:
String drUID = ref.push().getKey();
Then you can pass the above variable using intent to different activities.
Not solely a Firebase question, but I am using Firebase to make posts to a backend from Android and run it 10 times, once every second.
Firebase ref = new Firebase(URL);
ref.child("Time").setValue(Currentime());
However this is an Asynchronous Call and when I put a while loop:
while (time_now < time_start + 10 seconds) {
Firebase ref = new Firebase(URL);
ref.child("Time").setValue(Currentime());
}
It seems to run the while loop first and then ~10 Firebase calls at the end. Is there a way to add a timeout so that it forces the Asynchronous (Firebase) calls to run for a second before calling the next Async call?
If you look at the Java example on the Firebase web site, you'll see that it has a doTransactions method and an onComplete method:
Firebase countRef = new Firebase(URL);
Transaction.Handler handler = new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData currentData) {
// set the new time
...
}
#Override
public void onComplete(FirebaseError error, boolean committed, DataSnapshot currentData) {
if (error != null) {
...
} else {
if (!committed) {
...
} else {
// transaction committed, do next iteration
...
}
...
}
}
});
countRef.runTransaction(handler);
So you'd set the new time in the doTransaction method:
// set the new time
currentData.setValue(time_now);
return Transaction.success(currentData);
And then start a next iteration in the onComplete method.
// transaction committed, do next iteration
if (time_now < time_start + 10 seconds) {
countRef.runTransaction(handler);
}