strange loop when logging a user in to my app - java

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.

Related

How can I get username from real time database and show it in navigation header?

Register Button in Register Acvtivity
public void registerBtnClicked(View view){
String email = binding.userEmailEditText.getText().toString();
String password = binding.userPasswordEditText.getText().toString();
String userNameData = binding.usernameEditText.getText().toString();
user = new Users(userNameData,email,password);
db = FirebaseDatabase.getInstance();
databaseReference = db.getReference(Users.class.getSimpleName());
databaseReference.push().setValue(user);
if(email.equals("") || password.equals("")){
Toast.makeText(this, "Enter email and password", Toast.LENGTH_LONG).show();
}else{
auth.createUserWithEmailAndPassword(email,password).addOnSuccessListener(new OnSuccessListener<AuthResult>() {
#Override
public void onSuccess(AuthResult authResult) {
Intent intent = new Intent(RegisterPage.this, MainActivity.class);
startActivity(intent);
finish();
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Toast.makeText(RegisterPage.this, e.getLocalizedMessage(),Toast.LENGTH_LONG).show();
}
});
}
}
I created a real time database.But I couldn't figure out how to show username in navigation header section. Can you help me?
If I understand correctly, the firebaseUser is null when you're trying to read the display name from it. This is actually a common scenario, as the user's sign-in session is managed by Firebase in the background, and the current user may change at any time.
The simple fix is to check whether there is a current user before accessing their display name, which you can do with:
firebaseUser = FirebaseAuth.getInstance().getCurrentUser();
if (firebaseUser != null) {
navUserEmail.setText(firebaseUser.getEmail());
navUserName.setText(firebaseUser.getDisplayName());
}
Note though that the display name is an optional property of the user profile, so it can indeed be null. If you want to display nothing in that case, you can do:
String displayName = firebaseUser.getDisplayName();
navUserName.setText(displayName != null ? displayName : "");
Even if you've set the display name of a user, it may take up to an hour until that is updated for all connected clients, as they all cache the user profile. And since such updates happen in the background... 👇
To correctly handle all auth state changes, you'll want to use an auth state listener, as shown in this article: https://stackoverflow.com/collectives/google-cloud/articles/68104924/listen-for-authentication-state-in-android

Querying database to make sure new users don't register with an already existing username

What I am trying to achieve is when a new user registers I would like to run a query to make sure that the username which they have selected doesn't already belong to some other user. To do this I have written the register(); method, but it's not giving me back anything, not the Toast message indicating that the username has already been taken, nor does it take me to the next page indicating that the email has been sent and the user has to now log in.
Can someone explain to me what I am doing wrong?
RegisterActivity
private void register(final String username, final String fullname, String email, String password) {
DatabaseReference reference = FirebaseDatabase.getInstance().getReference("Users");
reference.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
boolean ifUsernameExists = false;
for (DataSnapshot snapshot : dataSnapshot.getChildren()) {
User user = snapshot.getValue(User.class);
if (user != null) {
if (username.equals(user.getUsername())) {
Toast.makeText(RegisterActivity.this, "That username has already been taken. Try another", Toast.LENGTH_SHORT).show();
ifUsernameExists = true;
}
}
}
if (!ifUsernameExists) {
mAuth.createUserWithEmailAndPassword(email, password).addOnCompleteListener(RegisterActivity.this, task -> {
if (task.isSuccessful()) {
sendVerificationEmail();
FirebaseUser firebaseUser = mAuth.getCurrentUser();
String userid = firebaseUser.getUid();
mReference = FirebaseDatabase.getInstance().getReference().child("Users").child(userid);
HashMap<String, Object> hashMap = new HashMap<>();
hashMap.put("id", userid);
hashMap.put("email", email);
hashMap.put("username", username.toLowerCase());
hashMap.put("fullname", fullname);
hashMap.put("bio", "");
hashMap.put("imageurl", "https://firebasestorage.googleapis.com/v0/b/events-50eda.appspot.com/o/Placeholder.png?alt=media&token=0e651fa8-32e9-4f42-be9a-a5365f44b0f4");
mReference.setValue(hashMap).addOnCompleteListener(task1 -> {
if (task1.isSuccessful()) {
FirebaseAuth.getInstance().signOut();
Intent intent = new Intent(RegisterActivity.this, LoginActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
});
} else {
Toast.makeText(RegisterActivity.this, "You can't register with that email", Toast.LENGTH_LONG).show();
mProgressDialog.dismiss();
}
});
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
The value that you are looking for in the user variable is getting lost. I would print it out ar run a debug to check where I am losing it.
The first part of the problem was solved according to OP's comment:
I figured out that actually my entire problem was Firebase rules.
Using the information in the answer from the following post:
Checking if a particular value exists in the Firebase database
What if I just wanted to use a for loop and look through all the usernames?
You cannot do that unless you are authenticated.
Like I said the method I wrote works, I just need to make it so an unauthenticated user can read to see if the username could possibly exist and then write to the database, meaning save their data to Firebase.
The only way in which unauthenticated users can read the data is to write the rules like this:
{
"rules": {
".read": true,
".write": true
}
}
But is not recommended because these security rules are defined as public, and anyone can steal, modify, or delete data in your database.
The simplest solution I can think of is to authenticate the user with email and password and provide the option the set the user name right after that. Once the user is already authenticated, you can use the query that you said it's working along with the existing security rules.

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

Android Cloud Firestore: Query to find a users name

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.

if statement checking for database information, works when it's not supposed to

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
}

Categories

Resources