I apply (Callback method to fix the asynchronous problem)the https://www.youtube.com/watch?v=0ofkvm97i0s video. But I have problems.
Hear are readData and interface.
private void readData(FirestoreCallback firestorecallback){
String currentUserId = firebaseAuth.getCurrentUser().getUid();
DocumentReference docRef = firestore.collection("Users").document(currentUserId);
docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if(task.isSuccessful()){
DocumentSnapshot document = task.getResult();
if(document.exists()){
a = document.getString('gender');
}
firestorecallback.onCallback(a);
}
}
});
}
private interface FirestoreCallback{
void onCallback(String b);
}
And in onCreate method
readData(new FirestoreCallback() {
#Override
public void onCallback(String b) {
String c;
if(b=='남자'){
c ='여자';
}
if(b=='여자'){
c = '남자';
}
EventChangeListener(b);
Log.d('',b);
Log.d('',c);
}
});
EventChangeListener function's code
private void EventChangeListener(String a) {
//firestore.collection('Users').orderBy('time',Query.Direction.DESCENDING).limit(50)
//
firestore.collection('Users')
//.whereEqualTo('mbti','ENTP')
//.whereEqualTo('region','')
.whereEqualTo('gender',a)
.orderBy('time',Query.Direction.DESCENDING).limit(50)
.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot value, #Nullable FirebaseFirestoreException error) {
if(error != null){
if(progressDialog.isShowing()){
progressDialog.dismiss();
}
Log.e("Firestore error",error.getMessage());
return;
}
for (DocumentChange dc : value.getDocumentChanges()){
if (dc.getType() == DocumentChange.Type.ADDED){
userArrayList.add(dc.getDocument().toObject(User.class));
}
myAdapter.notifyDataSetChanged();
if(progressDialog.isShowing()){
progressDialog.dismiss();
}
}
}
});
}
As you can see, I can get the variable 'b' but I want to when users' 'b' is male c-> male, 'b' is female c-> male.
So I define the variable 'c' but android said variable c is not defined
public void onCallback(String b) {
String c;
if(b=='female'){
c ='male';
}
if(b=='male'){
c = 'female';
}
EventChangeListener(b);
Log.d('',b);
Log.d('',c);
}
Related
When I get data from Cloud Firestore in android. It returns an empty variable. It should return the value of the flag true or false.
public boolean checkIfUserAlreadyExists(final String email) {
final boolean[] flag = {false};
db.collection(context.getString(R.string.db_collection_users))
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
if (document.getData().get(context.getString(R.string.db_field_email)).toString() != null && document.getData().get(context.getString(R.string.db_field_email)).toString().equals(email)) {
Toast.makeText(context, "User Already exits", Toast.LENGTH_SHORT);
Log.e(TAG, "User already exists" + document.getData().get("email").toString());
flag[0] = true;
} else {
Log.e(TAG, "User do not exists");
}
}
} else {
Log.e(TAG, "Error getting documents.", task.getException());
}
}
});
return flag[0];
}
flag[0] should return true but it returns false when called in another activity.
addOnCompleteListener works asynchronously.
That is you are returning flag[0] immeadiately after the OnCompleteListener is set. By that time the onComplete() would not have called and hence the flag[0] still have the initial value. This compiler expected this type of error that's why it's warned you that you can not change non final variables inside the callback method. But you bypassed it in a strange way by making it an array 😀
You have can solve this in multiple ways
Solution 1
Instead of returning the value, access the value from the callback
public void checkIfUserAlreadyExists(final String email) {
db.collection(context.getString(R.string.db_collection_users))
.get()
.addOnCompleteListener(new OnCompleteListener < QuerySnapshot > () {
#Override
public void onComplete(#NonNull Task < QuerySnapshot > task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document: task.getResult()) {
if (document.getData().get(context.getString(R.string.db_field_email)).toString() != null && document.getData().get(context.getString(R.string.db_field_email)).toString().equals(email)) {
Toast.makeText(context, "User Already exits", Toast.LENGTH_SHORT);
Log.e(TAG, "User already exists" + document.getData().get("email").toString());
// User exists.
userTheResult(true);
return;
} else {
Log.e(TAG, "User do not exists");
}
}
// No user exists.
userTheResult(false);
} else {
Log.e(TAG, "Error getting documents.", task.getException());
}
}
});
}
Solution 2
You can use this class to make a thread waiting until the result is obtained. This way you don't have to change your code much. What you have to do is simply add the class ThreadLockedTask<T> as it is to your project and use it as in the example given.
import java.util.concurrent.atomic.AtomicReference;
/**
* #author Ahamad Anees P.A
* #version 1.0
* #param <T> type
*/
public class ThreadLockedTask<T> {
private AtomicReference<ResultWrapper<T>> mReference;
public ThreadLockedTask() {
mReference = new AtomicReference<>(new ResultWrapper<T>());
}
public T execute(Runnable runnable) {
runnable.run();
if (!mReference.get().mIsSet)
lockUntilSet();
return mReference.get().mResult;
}
private void lockUntilSet() {
synchronized (this) {
while (!mReference.get().isSet()) {
try {
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public void setResult(T result) {
synchronized (this) {
ResultWrapper<T> wrapper = mReference.get();
wrapper.setResult(result);
wrapper.setIsSet(true);
notify();
}
}
public static class ResultWrapper<T> {
private boolean mIsSet;
private T mResult;
public boolean isSet() {
return mIsSet;
}
public T getResult() {
return mResult;
}
void setIsSet(boolean isCompleted) {
this.mIsSet = isCompleted;
}
void setResult(T result) {
this.mResult = result;
}
}
}
Usage
public boolean checkIfUserAlreadyExists(final String email) {
ThreadLockedTask < Boolean > task = new ThreadLockedTask < > ();
boolean flag = task.execute(new Runnable() {
#Override
public void run() {
db.collection(context.getString(R.string.db_collection_users))
.get()
.addOnCompleteListener(new OnCompleteListener < QuerySnapshot > () {
#Override
public void onComplete(#NonNull Task < QuerySnapshot > task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document: task.getResult()) {
if (document.getData().get(context.getString(R.string.db_field_email)).toString() != null && document.getData().get(context.getString(R.string.db_field_email)).toString().equals(email)) {
Toast.makeText(context, "User Already exits", Toast.LENGTH_SHORT);
Log.e(TAG, "User already exists" + document.getData().get("email").toString());
task.setResult(true);
return;
} else {
Log.e(TAG, "User do not exists");
}
}
} else {
Log.e(TAG, "Error getting documents.", task.getException());
}
task.setResult(false);
}
});
}
});
return flag;
}
I have a problem with my return..since the data required a few second to get downloaded, when the method return the ArrayList, it's still empty. In fact if I put that lines of code Log.v("array", String.valueOf(partecipantsArrayList));
first it print 0 and then it print the array filled. So I have to wait before the return statement..is there any way to achieve this?
public ArrayList<String> getPartecipantsList(){
String email = getEmail();
String groupTitle = getTitleBar();
DocumentReference docRef = db.collection("users").document(email).collection("Group").document(groupTitle);
docRef.get()
.addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
DocumentSnapshot document = task.getResult();
//Extracting participants ArrayList from the document
for(Object item : task.getResult().getData().values()) {
String[] values = String.valueOf(item).replace("[", "").replace("]", "").split(",");
for (String value : values){
partecipantsArrayList.add(value);
}
}
partecipantsArrayList.remove(String.valueOf("["));
partecipantsArrayList.remove(partecipantsArrayList.size() - 1);
Log.v("array", String.valueOf(partecipantsArrayList));
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
}
});
return partecipantsArrayList;
}
It is async task you can adjust this code like below.
change the signature of that method to return void
public void getPartecipantsList();
do next action after the on complete
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
...............
partecipantsArrayList.remove(String.valueOf("["));
partecipantsArrayList.remove(partecipantsArrayList.size() - 1);
Log.v("array", String.valueOf(partecipantsArrayList));
// do your action by calling next method from here
//eg: setResultFromArray(partecipantsArrayList);
}
do fail action or call method when failures in side the onFailure.
#Override
public void onFailure(#NonNull Exception e) {
//......
}
I built with Firebase two options for registration, one can register as an employer and another option to register as an worker. I gave an ID to them, and each employer received the letter 'e' at the beginning of the ID and the worker received the letter 'w' at the beginning of the ID so that I could identify them.
I also created the possibility of connecting with Firebase and I want if I connect as a worker I will switch to a certain screen and if I connect as an employer I will switch to another screen.
I store the information inside the Firestore. Each document in the collection receives the UID of that user within the document. The ID is found with the letters.
I try this.
private char s = 'e';
mAuth.signInWithEmailAndPassword(email, password)
.addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
if(task.isSuccessful())
{
mDatabase.collection("employer").document(mUser.getUid()).get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful())
{
DocumentSnapshot documentSnapshot = task.getResult();
index = documentSnapshot.getString("ID");
ID = index.charAt(0);
}
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
mDatabase.collection("worker").document(mUser.getUid()).get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
DocumentSnapshot documentSnapshot = task.getResult();
index = documentSnapshot.getString("ID");
ID = index.charAt(0);
}
});
}
});
if(mUser.isEmailVerified())
{
if(ID == s)
{
Intent k = new Intent(login_page.this,home_screen_employer.class);
startActivity(k);
}
else {
Intent k = new Intent(login_page.this,home_screen_worker.class);
startActivity(k);
}
}
Data is loaded from Firestore asynchronously. By the time your if(ID == s) runs, the data hasn't been loaded yet.
You'll need call the code that redirects (builds the intent and starts the activity) inside the onComplete methods.
So first define a function:
public void redirect(User user, char id) {
if(user.isEmailVerified()) {
if (id == 'e') {
Intent k = new Intent(login_page.this,home_screen_employer.class);
startActivity(k);
}
else {
Intent k = new Intent(login_page.this,home_screen_worker.class);
startActivity(k);
}
}
}
And then call it from your two onComplete methods:
mAuth.signInWithEmailAndPassword(email, password)
.addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
if(task.isSuccessful()) {
mDatabase.collection("employer").document(mUser.getUid()).get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful())
{
DocumentSnapshot documentSnapshot = task.getResult();
index = documentSnapshot.getString("ID");
redirect(mUser, index.charAt(0));
}
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
mDatabase.collection("worker").document(mUser.getUid()).get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
DocumentSnapshot documentSnapshot = task.getResult();
index = documentSnapshot.getString("ID");
redirect(mUser, index.charAt(0));
}
});
}
});
Refactoring this a bit to use success listeners, leads to:
mAuth.signInWithEmailAndPassword(email, password)
.addOnSuccessListener(this, new OnSuccessListener<AuthResult>() {
#Override
public void onSuccess(AuthResult authResult) {
mDatabase.collection("employer").document(mUser.getUid()).get().addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot doc) {
index = doc.getString("ID");
redirect(mUser, index.charAt(0));
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
mDatabase.collection("worker").document(mUser.getUid()).get().addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot doc) {
index = doc.getString("ID");
redirect(mUser, index.charAt(0));
}
});
}
});
And then refactoring to get rid of the duplicated document-parsing:
mAuth.signInWithEmailAndPassword(email, password)
.addOnSuccessListener(this, new OnSuccessListener<AuthResult>() {
#Override
public void onSuccess(AuthResult authResult) {
mDatabase.collection("employer").document(mUser.getUid()).get().addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot doc) {
redirect(mUser, doc);
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
mDatabase.collection("worker").document(mUser.getUid()).get().addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot doc) {
redirect(mUser, doc);
}
});
}
});
With this updated definition of the redirect function:
public void redirect(User user, Document doc) {
if(user.isEmailVerified()) {
char id = doc.getString("ID").charAt(0);
if (id == 'e') {
Intent k = new Intent(login_page.this,home_screen_employer.class);
startActivity(k);
}
else {
Intent k = new Intent(login_page.this,home_screen_worker.class);
startActivity(k);
}
}
}
I want to read the a value from firestore document. But it doesn't work for me and I don't know where is the problem.should i use get() or realtime updates.
I tried both but doesn't work for me
this is my code.
I confirmed my positions by using toast and the are ok.
How to solve this problem?
public class SingleAdActivity extends AppCompatActivity {
String x;
String a;
String z;
String coname;
TextView Co_Name;
private ListenerRegistration listenerRegistration;
DocumentReference firebaseFirestore;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_single_ad);
Co_Name = findViewById(R.id.SingleAd_CoName);
ad_discription = findViewById(R.id.disrcibe_ad);
phone_number = findViewById(R.id.phone);
x = getIntent().getStringExtra("single_ad_position");
a = getIntent().getStringExtra("single_ad_categoryP");
z = getIntent().getStringExtra("documentId");
firebaseFirestore = FirebaseFirestore.getInstance().collection("MainCategories")
.document(String.valueOf(a))
.collection("Ads")
.document(z);
#Override
protected void onStart () {
super.onStart();
listenerRegistration = firebaseFirestore
.addSnapshotListener(this, new EventListener<DocumentSnapshot>() {
#Override
public void
onEvent(#Nullable DocumentSnapshot documentSnapshot, #Nullable FirebaseFirestoreException e) {
if (e != null) {
Toast.makeText(SingleAdActivity.this, "error", Toast.LENGTH_SHORT).show();
return;
}
assert documentSnapshot != null;
if (documentSnapshot.exists()) {
coname = documentSnapshot.getString("CompanyName");
Co_Name.setText(coname);
} else {
// Co_Name.setText(a);
}
}
});
}
#Override
protected void onStop () {
super.onStop();
listenerRegistration.remove();
}
}
}
The way I do it is like this:
FirebaseFirestore firestoreDb = FirebaseFirestore.getInstance();
firestoreDb..collection(yourCollection)
.document(yourDocument)
.get()
.addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if(task.isSuccessful()) {
Object yourObject = task.getResult().toObject(YourClassName.class);
} else {
if(task.getException() != null)
{
task.getException().printStackTrace();
}
}
}
});
So here's my scenario:
I have three separate calls to firestore in their own classes. All three of the calls are nested within each other's callback and in the last callback I do all my work. When I trigger this stack, my Admin object will have already existed while my users list and books list would be created as this stack listens for an update. If data doesn't exist in the database and we create an entry the callback doesn't report a book (I do get Admin and User objects however). However, if we re-open the app and data already exists in the database then the callback reports something. After this, anytime I add a user/book then the list updates properly.
Note: these are realTime listeners so as soon as a user/book is added then we should see the update.
Here are my classes:
public class FirebaseGetThisAdmin {
//firebase objects
private FirebaseAuth mAuth;
private FirebaseFirestore mDbase;
private Activity activity;
private Admin admin;
private String adminID;
//default constructor
public FirebaseGetThisAdmin() {
}
public FirebaseGetThisAdmin(Activity activity) {
this.activity = activity;
}
public interface FirestoreCallback {
void onCallback(Admin admin);
}
public void readDataRTUpdate(final FirestoreCallback firestoreCallback) {
//firebase new instances
mAuth = FirebaseAuth.getInstance();
mDbase = FirebaseFirestore.getInstance();
//get admin email address and user name from Auth and set textInput fields to them
if (mAuth.getCurrentUser() != null) {
adminID = mAuth.getUid();
}
final DocumentReference docRef = mDbase.collection("admins").document(adminID);
docRef.addSnapshotListener(activity, new EventListener<DocumentSnapshot>() {
#Override
public void onEvent(#Nullable DocumentSnapshot snapshot,
#Nullable FirebaseFirestoreException e) {
if (e != null) {
Log.w(TAG, "Listen failed.", e);
return;
}
String source = snapshot != null && snapshot.getMetadata().hasPendingWrites()
? "Local" : "Server";
if (snapshot != null && snapshot.exists()) {
admin = new Admin();
admin = snapshot.toObject(Admin.class);
//pass variables to the callback
firestoreCallback.onCallback(admin);
Log.d(TAG, source + " data: " + snapshot.getData());
} else {
Log.d(TAG, source + " data: null");
}
}
});
}
}
public class FirebaseGetUsers {
//firebase objects
private FirebaseAuth mAuth;
private FirebaseFirestore mDbase;
private Activity activity;
private String adminID;
//default constructor
public FirebaseGetUsers() {
}
public FirebaseGetUsers(Activity activity) {
this.activity = activity;
//firebase new instances
mAuth = FirebaseAuth.getInstance();
mDbase = FirebaseFirestore.getInstance();
if (mAuth.getCurrentUser() != null) {
adminID = mAuth.getUid();
}
}
public interface FirestoreCallback {
void onCallback(List<User> users);
}
public void readDataRTUpdate(final FirestoreCallback firestoreCallback) {
mDbase.collection("admins").document(adminID).collection("users")
.addSnapshotListener(activity, new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot value,
#Nullable FirebaseFirestoreException e) {
if (e != null) {
Log.w(TAG, "Listen failed.", e);
return;
}
if (value != null) {
int i = 0;
List<User> users = new ArrayList<>();
for (QueryDocumentSnapshot document : value) {
users.add(document.toObject(User.class));
Log.d(TAG, "User: " + users.get(i).toString());
i++;
}
firestoreCallback.onCallback(users);
Log.d(TAG, "Document updated.");
}
else {
Log.d(TAG, "No such document");
}
}
});
}
}
Next:
public class FirebaseGetUsers {
//firebase objects
private FirebaseAuth mAuth;
private FirebaseFirestore mDbase;
private Activity activity;
private String adminID;
//default constructor
public FirebaseGetUsers() {
}
public FirebaseGetUsers(Activity activity) {
this.activity = activity;
//firebase new instances
mAuth = FirebaseAuth.getInstance();
mDbase = FirebaseFirestore.getInstance();
if (mAuth.getCurrentUser() != null) {
adminID = mAuth.getUid();
}
}
public interface FirestoreCallback {
void onCallback(List<User> users);
}
public void readDataRTUpdate(final FirestoreCallback firestoreCallback) {
mDbase.collection("admins").document(adminID).collection("users")
.addSnapshotListener(activity, new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot value,
#Nullable FirebaseFirestoreException e) {
if (e != null) {
Log.w(TAG, "Listen failed.", e);
return;
}
if (value != null) {
int i = 0;
List<User> users = new ArrayList<>();
for (QueryDocumentSnapshot document : value) {
users.add(document.toObject(User.class));
Log.d(TAG, "User: " + users.get(i).toString());
i++;
}
firestoreCallback.onCallback(users);
Log.d(TAG, "Document updated.");
}
else {
Log.d(TAG, "No such document");
}
}
});
}
}
Next:
public class FirebaseGetBooks {
//firebase objects
private FirebaseFirestore mDbase;
private Activity activity;
private String groupID;
//default constructor
public FirebaseGetBooks() {
}
public FirebaseGetBooks(Activity activity) {
this.activity = activity;
//firebase new instances
mDbase = FirebaseFirestore.getInstance();
FirebaseGetGroupID firebaseGetGroupID = new FirebaseGetGroupID(activity);
groupID = firebaseGetGroupID.getGroupID();
}
public interface FirestoreCallback {
void onCallback(List<Book> books);
}
public void readDataRTUpdate(final FirestoreCallback firestoreCallback) {
mDbase.collection("books").whereEqualTo("groupID", groupID)
.addSnapshotListener(activity, new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot value,
#Nullable FirebaseFirestoreException e) {
if (e != null) {
Log.w(TAG, "Listen failed.", e);
return;
}
if (value != null) {
int i = 0;
List<Book> books = new ArrayList<>();
for (QueryDocumentSnapshot document : value) {
books.add(document.toObject(Book.class));
Log.d(TAG, "Book: " + books.get(i).toString());
i++;
}
firestoreCallback.onCallback(books);
Log.d(TAG, "Document updated.");
}
else {
Log.d(TAG, "No such document");
}
}
});
}
}
And finally my call in MainActivity:
firebaseGetUsers.readDataRTUpdate(new FirebaseGetUsers.FirestoreCallback() {
#Override
public void onCallback(final List<User> users) {
firebaseGetBooks.readDataRTUpdate(new FirebaseGetBooks.FirestoreCallback() {
#Override
public void onCallback(final List<Book> books) {
firebaseGetThisAdmin.readDataRTUpdate(new FirebaseGetThisAdmin.FirestoreCallback() {
#Override
public void onCallback(Admin admin) {
//processing all code here.
//books are empty!!
System.out.println("books: " + books.toString());
}
}
});
}
});
}
});
I can't figure out what I'm doing wrong...any ideas would be greatly appreciated.
Thanks!
Okay, so I was able to finally solve this issue. It had nothing to do with the Firestore nested callbacks. If you notice in the getBooks class, I'm referencing a groupID value. This value represents this Admin's account (holds reference to both users and other admins). Since this value never changes throughout the life of the account, I'm storing it locally in shared preferences so I can reduce database get() commands. However, it appears that on first login, the variable groupID isn't getting set quickly enough before we try to use it to access books from the database.
So, the net result is that books isn't able to find any documents since the variable isn't set yet.
My fix for this is two-folded. When the admin creates their account, the groupID is created when we create our first document reference. At this point we store it immediately to sharedPrefs. No need to try to get it from the database at this point, since it created and accessible locally. Second, I nested the Firestore get() groupID callback inside of our Firestore sign in method. So anything that gets executed when the user logs in happens AFTER we retrieve this value (for proper cleanup purposes the groupID is cleared from sharedPrefs on every logout).