How to get data from listener? - java

I am cross-platforming my app using Android and iOS and Firebase. In the iOS app, I have a option for the driver to use bids for the requested ride, whereas in the android app, it is just a calculated price.
If the Android rider requests a ride with an iOS driver, the iOS driver can offer a discounted bid rather than the calculated ridePrice.
In Firebase, in the RideRequests node, it adds ridePrice as the price to be paid but if its a bid price, it also adds a bidPrice. I am trying to set it up that if 'rideBidPrice' exists, then rideAmt = rideBidPrice but if not, then rideAmt = ridePrice.
Unfortunately, what I am doing isn't getting to the PayPalPayment method.
How can I do this?
firebase db:
"RideRequests" : {
"buIpWNEFgmZJWN3jIB9IwS8r74d2" : { // rider Id
"archiveTimestamp" : "2019-03-17 22:00",
"currentAddress" : "2 Peel Plaza",
"destAddress" : "Courtenay Bay Causeway",
"destLat" : 47.277879500000004,
"destLong" : -63.04432369999999,
"driver" : "Dean Winchester",
"driverID" : "nD1v8oxHv3ObdQAKeKjuTt6f5TL2",
"rideBidPrice" : 5.74,
"ridePrice" : 8.38,
"riderPaid" : "false",
"status" : "driverBid",
"userId" : "buIpWNEFgmZJWN3jIB9IwS8r74d2",
"username" : "riderANDROID"
}
Code:
private void checkForBids() {
Log.e(TAG, "checkForBids");
DatabaseReference bids = FirebaseDatabase.getInstance().getReference(Common.request_tbl)
.child(riderId).child("rideBidPrice");
bids.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
bidPrice = dataSnapshot.getValue(Double.class);
if (bidPrice != null) {
rideAmt = bidPrice;
} else {
rideAmt = ridePrice;
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
private void payPalPayment() {
Log.e(TAG, "payPalPayment");
PayPalPayment payment = new PayPalPayment(new BigDecimal(rideAmt),
"CAD", "Ryyde Payment ", PayPalPayment.PAYMENT_INTENT_SALE);
// PaymentActivity is created by PayPal API
Intent intent = new Intent(this, PaymentActivity.class);
intent.putExtra(PayPalService.EXTRA_PAYPAL_CONFIGURATION, config);
intent.putExtra(PaymentActivity.EXTRA_PAYMENT, payment);
startActivityForResult(intent, PAYPAL_REQUEST_CODE);
}
I also tried putting the payPalPayment method inside the block but won't work because of the intent.
EDIT #1
This works:
As per #AlexMamo post to below post:
How to return DataSnapshot value as a result of a method?
I have added an interface:
public interface MyCallback {
void onCallback(Double value);
}
The method that is actually getting the data from the database:
public void readData(final MyCallback myCallback) {
Log.e(TAG, "readData");
DatabaseReference bids = FirebaseDatabase.getInstance().getReference(Common.request_tbl)
.child(riderId).child("rideBidPrice");
bids.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Double value = dataSnapshot.getValue(Double.class);
myCallback.onCallback(value);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
Then I call the method inside the PayPalPayment method like this:
private void payPalPayment() {
readData(new MyCallback() {
#Override
public void onCallback(Double value) {
PayPalPayment payment = new PayPalPayment(new BigDecimal(value),
"CAD", "Ryyde Payment ", PayPalPayment.PAYMENT_INTENT_SALE);
// PaymentActivity is created by PayPal API
Intent intent = new Intent(RiderHome.this, PaymentActivity.class);
intent.putExtra(PayPalService.EXTRA_PAYPAL_CONFIGURATION, config);
intent.putExtra(PaymentActivity.EXTRA_PAYMENT, payment);
startActivityForResult(intent, PAYPAL_REQUEST_CODE);
}
});
}

You cannot create your rideAmt as a global variable and simply use it inside your payPalPayment() method because the Firebase API is asynchronous, meaning that onDataChange() method returns immediately after it's invoked, and the callback from the Task it returns, will be called some time later. There are no guarantees about how long it will take. So it may take from a few hundred milliseconds to a few seconds before that data is available. Because that method returns immediately, the value of your rideAmt variable you're trying to use it outside the onDataChange() method, will not have been populated from the callback yet.
Basically, you're trying to use the value of rideAmt synchronously from an API that's asynchronous. That's not a good idea. You should handle the API asynchronously as intended.
A quick solve for this problem would be to move all the logic that exist in your payPalPayment() method right inside the onDataChange() method. In this way, the data that you are trying to get will be available at that time.
If you need to use it outside the callback, I recommend you see the last part of my anwser from this post in which I have explained how it can be done using a custom callback. You can also take a look at this video for a better understanding.

Related

Firestore asynchronous API getting null values outside readData

this is my logcat output
I am trying to fetch data from Firestore and work on them tried this but not working
getting null value for "emergencyNumber" outside "readData()"
I have tried this solution Text but still getting null
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_emergency_contact);
Log.d("TAG","Initial");
firebaseAuth = FirebaseAuth.getInstance();
fStore = FirebaseFirestore.getInstance();
userID = firebaseAuth.getCurrentUser().getUid();
readData(new FirebaseCallBack() {
#Override
public void onCallback(String str) {
emergencyNumber = str;
//Toast.makeText(EmergencyContact.this, emergencyNumber, Toast.LENGTH_SHORT).show();
phone = (TextView) dialog.findViewById(R.id.EmergencyContactNumber) ;
//emergencyNumber = phone.getText().toString() + "Hello";
phone.setText(emergencyNumber);
Log.d("TAG",emergencyNumber+"inner read me");
}
});
Log.d("TAG",emergencyNumber+"middle");
Log.d("TAG",emergencyNumber+"end");
}
private void readData(FirebaseCallBack firebaseCallBack) {
documentReference.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()){
DocumentSnapshot document = task.getResult();
long l = document.getLong("EmergencyContact");
emergencyNumber = "0" + Long.toString(l);
firebaseCallBack.onCallback(emergencyNumber);
//Toast.makeText(EmergencyContact.this, emergencyNumber, Toast.LENGTH_SHORT).show();
Log.d("TAG",emergencyNumber+"exit read me");
}
}
});
}
private interface FirebaseCallBack {
void onCallback(String str);
}
}
Firestore asynchronous API getting null values outside readData.
Any code that needs data from Cloud Firestore needs to be inside the "onComplete()" method, or be called from there. It doesn't really matter if you create another callback, the same rules apply. This means that you cannot use the value of "emergencyNumber" outside the "onCallback()" method. Please note that this method fires, only when "onComplete()" method fires, hence that behavior. When the following Log statement is triggered:
Log.d("TAG",emergencyNumber+"middle");
The data isn't finished loading yet, that's why you have that order of execution in your logcat.
If you are not comfortable with callbacks, then I recommend you using the modern way of dealing with asynchronous programming when getting data from Firestore, which is using LiveData and ViewModel. Here you can find an example from one of my repositories where I have used the MVVM architecture pattern with LiveData and ViewModel to authenticate users in Firebase.
If you consider at some point in time to try using Kotlin, please check below an example:
https://github.com/alexmamo/FirestoreJetpackCompose
Where I have used Kotlin Coroutine for getting data from Firestore.

Performing Callbacks one after another

I am new to doing asynchronous programming in Android Java. I am wondering if there is a way to run another Callback after an initial Callback function has completed. Right now, I think they are running in parallel even though the second relies on the first.
First Callback:
// GETTING USER
private interface FirestoreUserCallback {
void onCallback (User myUser);
}
private void getUser(final FirestoreUserCallback firestoreCallback) {
Task<DocumentSnapshot> task = fStore.collection("users").document(fAuth.getCurrentUser().getUid()).get();
task.addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
user = documentSnapshot.toObject(User.class);
firestoreCallback.onCallback(user);
Log.d(TAG, "user created");
}
});
task.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.d(TAG, "user creation failed");
}
});
}
Second Callback:
// GETTING ALL DOCUMENTS
private interface FirestoreDocumentCallback {
void onCallback (List<TableEntries> myEntries);
}
private void getDocuments (final FirestoreDocumentCallback firestoreDocumentCallback) {
fStore.collection("result")
.document(Integer.toString(user.getCompanyNumber())) // need to use User object returned from the first Callback
.collection("SAM").get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
List<TableEntries> results = new ArrayList<>();
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
// add objects to results ArrayList ...
Log.d(TAG, document.getId() + " => " + document.getData());
}
firestoreDocumentCallback.onCallback(results);
} else {
Log.d(TAG, "Error getting documents: ", task.getException());
}
}
});
}
onCreate:
getUser(new FirestoreUserCallback () {
#Override
public void onCallback(User myUser) {
user = myUser;
}
});
getDocuments(new FirestoreDocumentCallback() {
#Override
public void onCallback(List<TableEntries> myEntries) {
entries = myEntries;
}
});
getDocuments() relies on the user variable being given its value from the first Callback. I'm receiving this error:
java.lang.NullPointerException: Attempt to invoke virtual method 'double java.lang.Double.doubleValue()' on a null object reference
Callbacks are looking fine. You just need to check if your value is null or not before accessing it. Just add a null check
if(doubleValue!=null)
Using RxJava. First, we fetch the user and then fetch the documents. Rx-Java has an operator flatmap. flatmap is used to execute the sequential tasks, where the second task is dependent on the data from the first task.
final CompositeDisposable disposable = new CompositeDisposable();
//function to fetch user data
Single<User> getUser(){
return API.getUserData(...);
}
//function to fetch ducuments
Sinlge<UserDetail> getDocuments(int userId){
return API.getUserDetail(userId, ...);
}
//Subscribe
disposable.add(getUser()
.flatmap(user-> return getDocuments(...))
.subscribeOn(Scheduler.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new DisposableSingleObservable(){
#Override
public void onSuccess(UserDetail userDetail){
Log.v("Api result", "Successful";
//Do some work
}
#Override
public void onError(Throwable e)
Log.v("Api result", "Error Returned");
}
}));
If either of the API call fails, onError() is called. If first API fails, second API call is not executed and onError() is called.
The simplest solution for your use-case is to pass both queries to Tasks.whenAllSuccess() method, as explained in my answer from the following post:
Firestore - Merging two queries locally
So once the task is complete, you can use the elements from both queries. Another solution might be to use Android Jetpack with LiveData along with ViewModel, as the Android team recommends.

When user unlikes comment all notifications of that user get deleted from database instead of only that specific notification

Have written a new method to delete notifications that a user receives if some other user likes their post, or comments, or likes their comment, etc. The problem is the post notification is being added to the database, but when I for example "unlike" a comment (hitting an ImageView) and then unlike it the notification with it's specific ID aren't deleted from the database and I am not sure why that is. All of the notifications for that user are deleted when I for instance "unlike" a post when only that notification which was sent because I had liked the post should be removed, and the others untouched.
Can someone tell me why this is happening and how to fix it?
PostAdapter
holder.like.setOnClickListener(v -> {
if (holder.like.getTag().equals("like")) {
FirebaseDatabase.getInstance().getReference().child("Likes").child(post.getPostid()).child(mFirebaseUser.getUid()).setValue(true);
addNotification(post.getPublisher(), post.getPostid());
} else {
FirebaseDatabase.getInstance().getReference().child("Likes").child(post.getPostid()).child(mFirebaseUser.getUid()).removeValue();
deleteNotification(post.getPublisher());
}
});
private void deleteNotification(String userId) {
DatabaseReference reference = FirebaseDatabase.getInstance().getReference("Notifications");
reference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
for (DataSnapshot snapshot : dataSnapshot.getChildren()) {
Notification notification = snapshot.getValue(Notification.class);
if (notification != null) {
reference.child(userId).child(notification.getNotificationId()).removeValue();
}
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
As i can see when you use function addLikeNotification(), then to notifid is assigned last push() key. When you try to remove any notification, there is still last value used in notifid .
What you can do is to create in your layout new field (per example TextView), set notifid to it and its visibility to GONE.
When you try to remove value, firstly assign text from that field to notifid and then remove your notification

How can I query in firebase android like database trigger in cloud function

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.

Read Data from Cloud FireStore Android

Is this script wrong, because the data I receive is null while I've added data on the Cloud Firestore. I do not use RecyclerView because I only need one data only.
This is the script:
private void getCustomer(){
firestoreDB.collection("customer")
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
customers = new ArrayList<>();
for (DocumentSnapshot doc : task.getResult()) {
Customer customer = doc.toObject(Customer.class);
customer.setId_customer(doc.getId());
customers.add(customer);
}
} else {
// Log.d(TAG, "Error getting documents: ", task.getException());
}
}
});
firestoreListener = firestoreDB.collection("customer")
.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(QuerySnapshot documentSnapshots, FirebaseFirestoreException e) {
if (e != null) {
// Log.e(TAG, "Listen failed!", e);
return;
}
customers = new ArrayList<>();
for (DocumentSnapshot doc : documentSnapshots) {
Customer customer = doc.toObject(Customer.class);
customer.setId_customer(doc.getId());
customers.add(customer);
}
}
});
id_customer = customers.get(0).getId_customer();
}
and this is my firestore:
You cannot use something now that hasn't been loaded yet. With other words, you cannot simply use the following line of code:
id_customer = customers.get(0).getId_customer();
Outside the onSuccess() method because it will always be null due the asynchronous behaviour of this method. This means that by the time you are trying to use the id_customer variable outside that method, the data hasn't finished loading yet from the database and that's why is not accessible.
A quick solve for this problem would be to use that result only inside the onSuccess() method, or if you want to use it outside, I recommend you see the last part of my anwser from this post in which I have exaplined how it can be done using a custom callback. You can also take a look at this video for a better understanding.

Categories

Resources