I've looked for multiple solutions here but couldn't find anything specific to my situation and therefore am posting a question here while I still continue looking for a solution. I'm fairly new to Firestore still and their guide/docs are still unclear.
My phone application has a system to get a user to enter in a name. This name is to be used to traverse the Firestore database and if the name exists as a field for one of the users, then the method must return a boolean of true.
This query is to be triggered by a "continue button" which is in my main activity as shown below:
//Authenticate user and proceed to next activity
continueBtn = (Button) findViewById(R.id.continue_btn);
continueBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//On click create a db reference and perform a query on it to find current user
//and authenticate it.
CollectionReference myRef = database.collection("users");
Query findNameQ = myRef.whereEqualTo("name", mUserName);
authenticateUser(findNameQ, mUserName);//I need to pass to this method a variable 'findNameQ' which can be used to validate the existence of a user.
//mUserName is the name it's looking for.
}
});
Once the query is run then it runs the authenticateUser method which basically validates the existence of the user and creates a new one if the user doesn't exist. Here's the method:
private void authenticateUser(Query findNameQ, String mUserName)
{
//Read from database and check if user exists
//if current users name matches to one in database then set userExists to true.
if (findNameQ != null)
{
userExists = true;
Toast.makeText(this, "User exists!", Toast.LENGTH_SHORT).show();
}
Toast.makeText(this, "User doesn't exist!", Toast.LENGTH_SHORT).show();
}
I'd like to use if (findNameQ != false) instead of null, how do I make it so my findNameQ variable is a boolean and not a query object?
In order to know if a user name exists in Firestore database, you need to use a get() call. Just creating a Query object will not provide you much. Beside that, if you are checking findNameQ != null it will always evaluate to true because findNameQ object is created and will never be null. So to solve this, please use the following lines of code:
productsRef.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
if (document.exists()) {
authenticateUser(findNameQ, mUserName);
}
}
}
}
});
Please also note, that using a addSnapshotListener will not help you because it will attach a listener to get data in real time but this is not what you need. You need to get the data only once.
You can use a boolean variable as
boolean nameFound = false;
Now, attach a snapshot listener to your query to check whether the name exists or not:
findNameQ.addSnapshotListener(new EventListener<QuerySnapshot>(){
#Override
public void onEvent(QuerySnapshot queryDocumentSnapshots, FirebaseFirestoreException e) {
for (DocumentSnapshot ds: queryDocumentSnapshots){
if (ds!=null && ds.exists()){
Toast.makeText(RegisterActivity.this, "Username Exists!", Toast.LENGTH_SHORT).show();
nameFound = true;
}
}
}
});
else the default value of nameFound that is false will be used. Now, use can use if else to call your authentication method based on the value of nameFound.
Related
How should I fetch the document fields from one collection and combine them to add a new document to another collection? I have attached picture of the database how does it looks, I want to fetch the fields from the collection show and want to update it to the new collection along with some other data:
private void savePost(String mPostTitle, String mPostContent, String mlistSpinnerC) {
final DocumentReference docRef = FirebaseFirestore.getInstance().collection("users").document(mauth.getCurrentUser().getUid());
docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if (document != null) {
String username = (String)
document.get("username");
String email= (String) document.get(email);
} else {
Log.d(TAG, "No such document");
}
} else {
Log.d(TAG, "get failed with ", task.getException());
}
}
});
postMap.put(Constants.POSTTTITLE, mPostTitle);
postMap.put(Constants.POSTCATEGORY, mlistSpinnerC);
postMap.put(Constants.POSTCONTENT, mPostContent);
postMap.put(Constants.TIMESTAMP, (System.currentTimeMillis()/1000));
postMap.put(Constants.USER_ID,mauth.getCurrentUser().getUid());
postMap.put("username", username);
PostsRef.document().set(postMap).addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if(task.isSuccessful()){
Intent toHomeActivity = new Intent(AddPostActivity.this, MainActivity.class);
startActivity(toHomeActivity);
}
}
});
I am just not able to map the fields from one collection to another collection, please guide me the correct method to that.
By the time you are trying to add the username to your postMap using the following line of code:
postMap.put("username", username);
The data has not finished loading yet from the database and this is because the listener you have added to your get() call is being invoked some unknown amount of time later after your query finishes. You don't know how long it's going to take, it may take from a few hundred milliseconds to a few seconds before that data is available. The onComplete() method has an asynchronous behavior, that's why you cannot get that username in such a way.
A quick solve for this problem would be to move all that block of code related to adding data to the postMap, inside the onComplete() method. In this you are waiting for the callback and username your will be available. Otherwise 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.
Image of DB structure
I have currently created a method for updating a book using Firestore. I am attempting to call this method in onOptionsItemSelected.
I am relatively new to Android and was always under the impression when calling this method I would call it as updateBook(Book book).
Below is my updateBook method
#Override
public void updateBook(Book book) {
String chapterName = editTextChapterName.getText().toString().trim();
String chapterInfo = editTextChapterInfo.getText().toString().trim();
int chapterNumber = numberPickerChapterNumber.getValue();
if (chapterName.trim().isEmpty() || chapterInfo.trim().isEmpty()) { //ensure that user has not left boxes empty
Toast.makeText(this, "Please add a chapter name and the chapter information", Toast.LENGTH_SHORT).show();
}
FirebaseFirestore db = FirebaseFirestore.getInstance();
DocumentReference bookRef = db.collection("Book")
.document();
bookRef.update("chapterName", book.getChapterName(),
"chapterInfo", book.getChapterInfo(),"chapterNumber", book.getChapterNumber())
.addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if (task.isSuccessful()){
Toast.makeText(AdminUpdateActivity.this, "Success", Toast.LENGTH_SHORT).show();
}
}
});
Below is where I am trying to call my method
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.save_icon:
updateBook();
return true;
default:
return super.onOptionsItemSelected(item);
}
Within the updateBook parenthesis I am getting an error "Cannot be applied to ()".
I have read through other questions on here to do with this topic but have not found a solution. Can someone explain how to fix this issue and why?
Thanks
To solve this, you should change the following line of code:
DocumentReference bookRef = db.collection("Book")
.document();
to
DocumentReference bookRef = db.collection("Book")
.document(bookId);
Without passing the id of the book to the document() method, the Firebase SDK doesn't really know which book object you want to update. Beside that, calling document() method on a DocumentReference it only generates a new random id each time.
There is also another change is also needed. Please also remove the argument from your updateBook() method. Should be:
public void updateBook() {}
Without any argument.
I am checking if the user has made an appointment in the database if not add the user(add a new document which has the user details). The problem with my app is that it runs both AddUser() and AlertUser() functions:
DocumentReference docRef = firebaseFirestore.collection(group).document(userIdentity);
docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()){
DocumentSnapshot documentSnapshot = task.getResult();
if (documentSnapshot != null) {
if (!documentSnapshot.exists()){
//User does not exist create the new user
addUser();
} else {
//User has already made an appointment show dialog
AlertUser();
}
}
}else {
Log.i("Key","Task not successfull");
}
}
});
What does this code do is checking whether a document with the userIdentity id actually exists. If it doesn't exist, the addUser() method is called, otherwise, the AlertUser() is called. There is no way in which both parts of the if statement are evaluated. So it's one or the other. You can have both method calls only if you access all those lines of code twice. Meaning that the first time the user is created and the second time is alerted. To solve this, remove from your code the part where you are calling the above code twice.
This is a Cloud Firestore question and not a Firebase Realtime database one, so I've changed the tag accordingly.
In the below example, I call a method signinUser(username, password) This then runs through Firebase to determine if the user has successfully or unsuccessfully been able to sign in. However, it takes Firebase a short amount of time to do this, which by that time the method has already returned with the original value, before being updated by the successful-ness / unsuccessful-ness of the sign in process.
How would I go about returning the method once the Firebase authentication has done it's thing. I am aware I could put a timer on when the return statement is called, however, I'm not sure how that'd work as slow internet connects could cause it to take longer than the given amount set by a timer.
My code is as follows:
public AuthSuccess signinUser(String username, String password) {
mAuth.signInWithEmailAndPassword(username + "#debugrestaurant.com", password)
.addOnCompleteListener(new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
if (task.isSuccessful()) {
Toast.makeText(context, "Successful!", Toast.LENGTH_SHORT).show();
authSuccess = new AuthSuccess(true, null);
} else {
Toast.makeText(context, "Unsuccessful!", Toast.LENGTH_SHORT).show();
authSuccess = new AuthSuccess(false, task.getException());
Toast.makeText(context, "Exception: " + authSuccess.getException().getLocalizedMessage(), Toast.LENGTH_SHORT).show();
}
}
});
return authSuccess;
}
Please note that the class AuthSuccess is simply an object I've created to collect whether or not the sign in was successful, and if not, to collect the exception.
You cannot return something now that hasn't been loaded yet. With other words, you cannot simply use the authSuccess object outside the onComplete() method because it will always be null due the asynchronous behaviour of this method. This means that by the time you are trying to return that result 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 the value of authSuccess only inside the onComplete() method, otherwise 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.
Your old code is probably something like this
public void onSignInClick(){
AuthSuccess result = signinUser(username, password);
if(result.success){
startMainActivity();
}else{
showErrorPopUp();
}
}
Change this to:
public void onSignInClick(){
signinUser(username, password);
}
protected void handleLoginResult(AuthSuccess result){
if(result.success){
startMainActivity();
}else{
showErrorPopUp();
}
}
And at the very end of your onComplete method do:
handleLoginResult(authSuccess);
The issue I am ultimately trying to solve, before I pose my question, is the synchronicity of querying my Firebase database and writing code based on the result. A simple example to illustrate:
Boolean userFound = false;
DatabaseReference userName = FirebaseDatabase.getInstance().getReference().child("Profiles").child("Name");
userName.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
String name = dataSnapshot.getValue().toString();
userFound = true;
Toast.makeText(getBaseContext(), "Welcome back, " + name + ".", Toast.LENGTH_SHORT).show();
}
#Override
public void onCancelled(DatabaseError databaseError) {
//Never used this section
}
});
If (!userFound) {
Toast.makeText(getBaseContext(), "User not found.", Toast.LENGTH_SHORT).show();
}
In the example above, the listener looks for a name in the database. If the name is found it gives a welcome message and sets "userFound" to true. If a name is not found, "userFound" will remain as false and you can generate a user not found message.
The problem with this is that everything runs at the same instant and so you will always get the "User not found" message instantly, and then a few seconds later the listener might actually find the user and say "Welcome back".
I have been looking into how I can possible resolve this, and I have found Java Promises. Am I looking in the right direction? Here are two promise examples:
CompletableFuture.supplyAsync(this::failingMsg)
.exceptionally(ex -> new Result(Status.FAILED))
.thenAccept(this::notify);
This code looks great, and the article here is very detailed in its usage: http://www.deadcoderising.com/java8-writing-asynchronous-code-with-completablefuture/
Except for the fact that is will ONLY work in API 24 and above. Which means your app will not work on 90% of devices. So this is essentially worthless.
The other way of doing this is as follows:
try {
Promise { client.newCall(request).execute() }
.then { ... }
.thenAsync { ... }
.then { ... }
} catch (e: Exception) {
...
}
As explained here: https://medium.com/#sampsonjoliver/promises-in-android-and-java-d6b1c418ea6c
Except that when I try to use this code there is no such thing as Promise. It just says it cannot resolve the symbol. So this guy has written an article on something that doesn't even exist.
Am I looking at the right stuff here? The end game is to make my app wait for the result of any database lookup before continuing to process code. If I cannot do this, then the database becomes completely useless.
Thanks guys. Please help!
The solution with a problem using asynchronous APIs is pretty much always the same: move the code that needs access to the data into the method that is called when the data is available.
So in your case that means moving the check and toast into onDataChange:
DatabaseReference userName = FirebaseDatabase.getInstance().getReference().child("Profiles").child("Name");
userName.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Boolean userFound = false;
String name = dataSnapshot.getValue().toString();
userFound = true;
Toast.makeText(getBaseContext(), "Welcome back, " + name + ".", Toast.LENGTH_SHORT).show();
if (!userFound) {
Toast.makeText(getBaseContext(), "User not found.", Toast.LENGTH_SHORT).show();
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
throw databaseError.toException();
}
});
For more on this, see:
Setting Singleton property value in Firebase Listener
Doug's excellent blog post
Querying data from firebase
Wait Firebase async retrive data in android
Handle data returned by an Async task (Firebase)