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)
Related
ive created a simple login screen that collects users email. then collects password and encrypts it. then sends that data to firebase database and signs the user in. it changes the textviews and edit texts to say the user is logged in and hides the login button. all works great. that is until i added a new button to log the user back out. for some reason it logs them out but instantly logs the user back in lol. can somebody just take a quick look at the code n see what ive done wrong. thanks.
this is my onClick method
#Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_login:
geteditTexts();
if (SIGNUP) {
logUserIn();
} else {
sendUserDataToFirebase();
}
break;
case R.id.btn_logout:
setStatusLoggedOut();
break;
this method Checks the user exists and that the encrypted password matches the encrypted key stored with in that database then logs the user in
private void logUserIn() {
reference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
if (snapshot.exists()) {
userdata = snapshot.child("email").getValue(String.class);
userpassword = snapshot.child("password").getValue(String.class);
if (userpassword.matches(encryptedMsg) & userdata.matches(memail)) {
setStatusLoggedIn(snapshot);
} else {
Toast.makeText(getBaseContext(), "Wrong email or password please try again", Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(getBaseContext(), "No account registered", Toast.LENGTH_SHORT).show();
}
}
this method simply sends the data to firebase
private void sendUserDataToFirebase() {
reference.child("email").setValue(memail);
reference.child("password").setValue(encryptedMsg);
}
i call this method at onStart() to check if the user is logged in or not
private void checkUserLogin() {
reference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
signedin = snapshot.child("SIGNEDIN").getValue(Boolean.class);
if (signedin) {
setStatusLoggedIn(snapshot);
}else {
setStatusLoggedOut();
}
}
then finally these 2 methods set the user as logged in or out
private void setStatusLoggedIn(DataSnapshot snapshot){
reference.child("SIGNEDIN").setValue(true);
userdata = snapshot.child("email").getValue(String.class);
et_email.setText(userdata);
userpassword = snapshot.child("password").getValue(String.class);
DecryptPassword();
btn_login.setText("Already Signed in");
btn_login.setOnClickListener(null);
forgot_password.setVisibility(View.GONE);
signup.setVisibility(View.GONE);
tv_login_desc.setText("You Are Logged In ");
btn_signout.setVisibility(View.VISIBLE);
}
private void setStatusLoggedOut(){
reference.child("SIGNEDIN").setValue(false);
et_email.setText("");
et_password.setText("");
btn_login.setText("Login");
btn_login.setOnClickListener(this);
forgot_password.setVisibility(View.VISIBLE);
signup.setVisibility(View.GONE);
tv_login_desc.setText("Login");
btn_signout.setVisibility(View.GONE);
}
everything works ok when logging user in its
If the reference variable in all the code snippets you shared points to the same database path, then the loop can be explained.
First up, you are adding a permanent listener on reference here:
reference.addValueEventListener(new ValueEventListener() {
...
Then when that listener is trigger, you call setStatusLoggedIn which then writes to reference:
reference.child("SIGNEDIN").setValue(true);
This will then trigger the value event listener from the first snippet again, which will then once again write to the database, which triggers the listener again, etc...
If you only want to read from the database once, use addListenerForSingleValueEvent or getData as shown in the documentation on reading data once.
I'm trying to update parts of a WebView in my Android app with data I'm getting from a peer connected via Firebase. For that, it could be helpful to execute blocking operations that will return the needed data. For example, an implementation of the Chat example that will wait until another chat participant writes something before the push.setValue() to return.
Is such a behavior possible with Firebase?
import com.google.android.gms.tasks.Tasks;
Tasks.await(taskFromFirebase);
On a regular JVM, you'd do this with regular Java synchronization primitives.
For example:
// create a java.util.concurrent.Semaphore with 0 initial permits
final Semaphore semaphore = new Semaphore(0);
// attach a value listener to a Firebase reference
ref.addValueEventListener(new ValueEventListener() {
// onDataChange will execute when the current value loaded and whenever it changes
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// TODO: do whatever you need to do with the dataSnapshot
// tell the caller that we're done
semaphore.release();
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});
// wait until the onDataChange callback has released the semaphore
semaphore.acquire();
// send our response message
ref.push().setValue("Oh really? Here is what I think of that");
But this won't work on Android. And that's a Good Thing, because it is a bad idea to use this type of blocking approach in anything that affects the user interface. The only reason I had this code lying around is because I needed in a unit test.
In real user-facing code, you should go for an event driven approach. So instead of "wait for the data to come and and then send my message", I would "when the data comes in, send my message":
// attach a value listener to a Firebase reference
ref.addValueEventListener(new ValueEventListener() {
// onDataChange will execute when the current value loaded and whenever it changes
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// TODO: do whatever you need to do with the dataSnapshot
// send our response message
ref.push().setValue("Oh really? Here is what I think of that!");
}
#Override
public void onCancelled(FirebaseError firebaseError) {
throw firebaseError.toException();
}
});
The net result is exactly the same, but this code doesn't required synchronization and doesn't block on Android.
I came up with another way of fetching data synchronously.
Prerequisite is to be not on the UI Thread.
final TaskCompletionSource<List<Objects>> tcs = new TaskCompletionSource<>();
firebaseDatabase.getReference().child("objects").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Mapper<DataSnapshot, List<Object>> mapper = new SnapshotToObjects();
tcs.setResult(mapper.map(dataSnapshot));
}
#Override
public void onCancelled(DatabaseError databaseError) {
tcs.setException(databaseError.toException());
}
});
Task<List<Object>> t = tcs.getTask();
try {
Tasks.await(t);
} catch (ExecutionException | InterruptedException e) {
t = Tasks.forException(e);
}
if(t.isSuccessful()) {
List<Object> result = t.getResult();
}
I tested my solution and it is working fine, but please prove me wrong!
Here's a longer example based on Alex's compact answer:
import com.google.android.gms.tasks.Tasks;
import com.google.firebase.firestore.CollectionReference;
import com.google.firebase.firestore.DocumentSnapshot;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.Query;
import com.google.firebase.firestore.QuerySnapshot;
final FirebaseFirestore firestore = FirebaseFirestore.getInstance();
final CollectionReference chatMessageReference = firestore.collection("chatmessages");
final Query johnMessagesQuery = chatMessageReference.whereEqualTo("name", "john");
final QuerySnapshot querySnapshot = Tasks.await(johnMessagesQuery.get());
final List<DocumentSnapshot> johnMessagesDocs = querySnapshot.getDocuments();
final ChatMessage firstChatMessage = johnMessagesDocs.get(0).toObject(ChatMessage.class);
Note that this is not good practice as it blocks the UI thread, one should use a callback instead in general. But in this particular case this helps.
If anyone is also thinking about how to use Kotlin's coroutine you can use kotlinx-coroutines-play-services.
Add to your app build.gradle file:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.1.1"
Then simply:
suspend fun signIn(email: String, password: String) {
try {
val auth: FirebaseAuth = FirebaseAuth.getInstance()
auth.signInWithEmailAndPassword(email, password).await()
} catch (e: FirebaseAuthException) {
println("${e.errorCode}: ${e.message}")
}
}
I made a simple class to call tasks synchronously in Android.
Note that this is similar to Javascript's async await function.
Check my gist.
Here's a sample code to use it.
TasksManager.call(() -> {
Tasks.await(AuthManager.signInAnonymously());
// You can use multiple Tasks.await method here.
// Tasks.await(getUserTask());
// Tasks.await(getProfileTask());
// Tasks.await(moreAwesomeTask());
// ...
startMainActivity();
return null;
}).addOnFailureListener(e -> {
Log.w(TAG, "signInAnonymously:ERROR", e);
});
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.
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);
I'm trying to make a error checking log in activity, where it will trigger an intent if it detects the following issues :
1) if user hasn't signed up (the email he used isn't authenticated with firebase) which is working out well for me
2) if user has signed up but didn't give me any information into the firebase database
My issue is that, for some reason the code i use to check for information in database, works for users even though they have information in the database attached to their UID.
meaning that the intent to tell them to give information will trigger when they already have given information.
if(task.isSuccessful()){
// Checks if user has submitted information in the Essential Information activity
//Takes the Unique ID(if it is present if not it will tell him to sign up or invalid email) asks the firebase database if he has given information to the database
reference.child("Users").child(UserUID).addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// User exists
if (dataSnapshot.exists()) {
//Displays Toast telling user that their information is saved in the database
Toast.makeText(LogInActivity.this, "You have data in our database ", Toast.LENGTH_SHORT).show();
}
//User doesn't have information in the database
else {
// Displays Toast telling user he/she needs to sign in into the firebase database
// User goes to UserInformationActivity to give his/her information
Toast.makeText(LogInActivity.this, "You need to give Essential Information", Toast.LENGTH_SHORT).show();
// 3 second delay
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
// Goes to UserInformationActivity
Intent GoToUserInformation = new Intent(LogInActivity.this, UserInformationActivity.class);
LogInActivity.this.startActivity(GoToUserInformation);
}
}, 3000);
}
}
// if the checking got cancelled, likability of that happening is small
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
Don't you want to check if the user is signed in?
Directly from firebase...
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
if (user != null) {
// User is signed in
// check a snapshot to see if there is information....if not, error out.
} else {
// No user is signed in
}