This question already has answers here:
How to return a list from Firestore database as a result of a function in Kotlin?
(3 answers)
Closed 3 years ago.
I am fairly new to Android development and the Firestore DB, I would greatly appreciate it if someone can point me in the right direction.
I am creating an app that would allow a user to sign in, and I am using their email as the primary identifier for a document in the collection of "users".
I want to be able to check if the users already exists using their email and if they do then return that an account is already registered with that email.
So far I managed to input user data into DB and query the DB to check if the user exists.
However my code overwrites the user with the supplied email rather than ignoring the write request and alerting the user that email already exists.
I already tried creating a private helper boolean function which I will call "checkIfUserExists()" that contains an emailFlag to query the db and change the flag to true if it exists and return the status of the flag, in which I would handle the call to write to the DB based on the result of checkIfUserExists()
//Set on click listener to call Write To DB function
//This is where my writetoDb and checkIfUserExist come together inside my onCreate Method
submitButton.setOnClickListener {
//Check to make sure there are no users registered with that email
if (!checkIfUserExists())
writeUserToDb()
else
Toast.makeText(
this,
"Account already registered with supplied email, choose another.",
Toast.LENGTH_LONG
).show()
}
//This should be called if and only if checkIfUserExists returns false
#SuppressLint("NewApi")
private fun writeUserToDb() {
//User Privilege is initially set to 0
val user = hashMapOf(
"firstName" to firstName.text.toString(),
"lastName" to lastName.text.toString(),
"email" to email.text.toString(),
"password" to password.text.toString(),
"birthDate" to SimpleDateFormat("dd-MM-yyyy", Locale.US).parse(date.text.toString()),
"userPrivilege" to 0,
"userComments" to listOf("")
)
//Create a new document for the User with the ID as Email of user
//useful to query db and check if user already exists
try {
db.collection("users").document(email.text.toString()).set(user).addOnSuccessListener {
Log.d(
TAG,
"DocumentSnapshot added with ID as Email: $email"
)
}.addOnFailureListener { e -> Log.w(TAG, "Error adding document", e) }
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun checkIfUserExists(): Boolean {
var emailFlagExist = false
val userExistQuery = db.collection("users")
userExistQuery.document(email.text.toString()).get()
.addOnSuccessListener { document ->
if (document != null)
Log.w(
TAG,
"Account already exists with supplied email : ${email.text}"
)
emailFlagExist = true
}
return emailFlagExist
//TODO can create a toast to alert user if account is already registered with this email
}
Right now it alerts me that it detected a user with the given email in the DB, however it also overwrites the current user with the recently supplied information after I click on the submit button in the registration page.
How can I prevent this from happening and if you can also point me in the the right direction of best practices for FireStore/Android development I would greatly appreciate it!
the addOnSuccessListener registers a async callback. And checkIfUserExists always returns false, because it finishes before receiving the response from the firebase (the callback execution).
One way to solve this issue, is to put your logic in the callback (call writeUserToDb in your callback method)
Related
After I create the user using auth.createUserWithEmailAndPassword, I then need to create the relevant documents in Cloud Firestore.
Option 1: if the auth.createUserWithEmailAndPassword task is successful, call a Cloud Function from the app and pass it the username, UID, and email to create the relevant documents.
if (user.isNotEmpty() && email.isNotEmpty() && password.isNotEmpty()) {
fs.authCreateUser(email, password)
.addOnCompleteListener() { task ->
if (task.isSuccessful) {
Log.e(tag, "createUserWithEmailAndPassword task was successful")
fs.CFcreateUser(user, email)
.addOnCompleteListener() { task ->
if (task.isSuccessful) {
Log.e(tag, "CFcreateUser task was successful")
val result = task.result!!["result"]
val message = task.result!!["message"]
//If result = 1, go to Groups Activity
//Else, delete the Firebase user so that the list of authenticated users matches the list of users in Firestore
if (result == "1") {
val intent = Intent(this, ActivityGroups::class.java)
startActivity(intent)
finish()
} else {
}
} else {
Log.e(tag, "CFcreateUser task failed")
Log.e(tag, "CFcreateUser task: ${task.exception.toString()}")
}
}
} else {
Log.e(tag, "createUserWithEmailAndPassword task failed")
Log.e(tag, "createUserWithEmailAndPassword exception: ${task.exception.toString()}")
}
}
}
Option 2: if the auth.createUserWithEmailAndPassword task is successful, update the user's displayName with user!!.updateProfile (from the docs) and then somehow set up a background trigger to create the relevant documents using the displayname as the username.
//After the auth.createUserWithEmailAndPassword task runs, run the code below
val user = Firebase.auth.currentUser
val profileUpdates = userProfileChangeRequest {
displayName = "Jane Q. User"
photoUri = Uri.parse("https://example.com/jane-q-user/profile.jpg")
}
user!!.updateProfile(profileUpdates)
.addOnCompleteListener { task ->
if (task.isSuccessful) {
Log.d(TAG, "User profile updated.")
}
}
Not sure which option is more secure, I'm new to Android dev.
Which is the more secure method between these two?
All requests that are made to Firebase Products like Cloud Firestore, Firebase Realtime Database, or Cloud Functions for Firebase and are coming from a backend SDK will bypass the security rules entirely. To also mention, that this also includes the Firebase Admin SDK. Security rules only apply to web and mobile clients. On the other hand, when using client-side code, you can secure your app using either, Cloud Firestore Security Rules or Firebase Realtime Database Security Rules.
That being said, in my opinion, that's not really about security as it is about efficiency. It's true that you can make the client app do the work, but there is a disadvantage because you can instead do it more efficiently using Cloud Functions. If the client app will do the work, then you'll don't have to pay the cost against the data plan, in terms of data usage and speed. In the case of Cloud Functions, everything is done internally.
Because you are using Firebase Authentication, you can create a Cloud Function to simply solve this problem. So you have to create a function that will be triggered every time something new happens. Here I'm talking about the trigger of a function on user creation.
That's also the same when we are talking about Cloud Storage triggers.
I want to store doctors and users two different collections on the firestore I have created different collections one for users and one for doctors in registration activity. Now the problem is when someone wants to log in I don't know he is a user or doctor I need to call the collection in the main activity I want to display all the details of user or doctor how can i identify which collection i need to call
for example, when doctors want to log in I only know his email id and I want to show all the details of him
in main activity i dont know which collection i need to call because i don't know he is a doctor or a user
I have created two collections because I wanna show all the doctors list to the user
and users list to doctors
my app details
My first activity is login activity, if someone is new, they can register
now let's start with Register activity
this is my Register activity
FirebaseAuth.getInstance().createUserWithEmailAndPassword( binding.etEmail.text.toString().trim { it <= ' ' }, binding.etPassword.text.toString().trim { it <= ' ' })
.addOnCompleteListener { task ->
if (task.isSuccessful) {
val firebaseUser: FirebaseUser = task.result!!.user!!
val user = User(
firebaseUser.uid,
binding.etFirstName.text.toString().trim{it<=' '},
binding.etLastName.text.toString().trim{it<=' '},
binding.etEmail.text.toString().trim { it <= ' ' },
)
if (binding.rbUser.isChecked){
// registerUser method create a user collection on firebase
FirestoreClass().registerUser(this,user)
}else{
// // registerDoctors method create a doctor collection on firebase
FirestoreClass().registerDoctors(this, user)
}
} else {
showErrorSnackBar(task.exception!!.message.toString(), true)
}
}
I think i don't need to put FirestoreClass().registerUser(this,user) and FirestoreClass().registerDoctors(this, user) methods code all work fine now i have two collection
This is my firestore collections sreenshot
now someone wants to login I don't know he/she is a doctor or user
This is my login activity layout
// this is my login activity
val email = binding.etEmail.text.toString().trim{it <= ' '}
val password = binding.etPassword.text.toString().trim{it <= ' '}
FirebaseAuth.getInstance().signInWithEmailAndPassword(email,password)
.addOnCompleteListener {
task ->
if(task.isSuccessful){
// Now how can I identify This is A doctor or A user what should I put in if blocks please tell
if ( )
FirestoreClass().getDoctorDtails(this#LoginActivity)
else{
FirestoreClass().getUserDetails(this#LoginActivity)
}
}else{
// hideProgressDialog()
showErrorSnackBar(task.exception!!.message.toString(), true)
}
if your have any solution for this please tell me how can i know he/she is a doctor and i user
You can use Firebase Custom Claims. They are like roles assigned to the users. You need to use the Firebase Admin SDK for assigned custom claims.
Now it entirely depends on your sign up flow that how and when are you going to assign the custom claims.
Coming to the part when you need to know if the current user is a Doctor or normal user, you can do so with the follow code in Android (Kotlin):
user.getIdToken(false).addOnSuccessListener(new OnSuccessListener<GetTokenResult>() {
#Override
public void onSuccess(GetTokenResult result) {
boolean isDoctor = result.getClaims().get("doctor");
if (isDoctor) {
// Show doctor UI.
} else {
// Show regular user UI.
}
}
});
Then you can check if the logged in user is a doctor or no and then conditionally switch to relevant activities. Please let me know if you need help setting up Custom Claims in your sign up flow.
I need to detect and differentiate two users using Firebase phone authentication. This should be done before granting a privilege to enter into the home activity of the app. When I did as suggested here (Stackoverflow), it does well by detecting the user using timeStamp() method. The answer does its job but the fancy thing is I need some data input from the new user before the verification code is sent.
In order for a verification code to be sent, a user provides a number which is directly authenticated in the firebase. Hence I cannot check if it's a new user (phone number) or current user (phone number).
Here is the code using TimeStamp() method.
private void signInWithPhoneAuthCredential(PhoneAuthCredential credential)
{
_firebaseAuth.signInWithCredential(credential).addOnCompleteListener(Objects.requireNonNull(getActivity()), task ->
{
if(task.isSuccessful())
{
//Sign in success, update UI with the signed-in user's information.
FirebaseUser _user = Objects.requireNonNull(task.getResult()).getUser();
long creationTimestamp = Objects.requireNonNull(Objects.requireNonNull(_user).getMetadata()).getCreationTimestamp();
long lastLoginTimestamp = Objects.requireNonNull(Objects.requireNonNull(_user).getMetadata()).getLastSignInTimestamp();
if(creationTimestamp == lastLoginTimestamp)
{
//Create a new user with account
setUserDataToDatabase(_user, _username, _university, _course, _year);
sendUserToWelcome();
}
else
{
//User exists, just login
sendUserToHome();
}
}
else
{
FancyToast.makeText(getContext(), "Enter sent code", FancyToast.LENGTH_SHORT, FancyToast.INFO, false).show();
}
});
}
After several research with no success. I decided to walk around, I'm using firestore database. I decided to track every user's number in a new collection with auto-generated document id. I called the collection USERS whereas each document has a unique random id.
I get the user's number and check it if any of the registered user has that number with the USERS's collection using a whereEqualTo() method with the phone_number field. If the number is exists I login the user else display a registration screen.
_firestore.collection(USERS).whereEqualTo("phone_number", _phoneCheck).get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>()
{
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task)
{
if(task.isSuccessful())
{
//If task is greater than 0 means there is a presence of a phone number.
if(Objects.requireNonNull(task.getResult()).size() > 0)
{
//Here I allow user to login as usual.
PhoneAuthOptions options = PhoneAuthOptions.newBuilder(_firebaseAuth).setPhoneNumber(_phone).setTimeout(60L, TimeUnit.SECONDS).setActivity(Objects.requireNonNull(getActivity())).setCallbacks(_callbacks).build();
PhoneAuthProvider.verifyPhoneNumber(options);
}
}
else
{
//Else the task is empty means there is no a presence of a phone number.
//Check if there is a presence of registration data to bind with new user.
if(_registrationData != null)
{
//I login user with the new data and save the information into the firestore plus the phone number.
PhoneAuthOptions options = PhoneAuthOptions.newBuilder(_firebaseAuth).setPhoneNumber(_phone).setTimeout(60L, TimeUnit.SECONDS).setActivity(Objects.requireNonNull(getActivity())).setCallbacks(_callbacks).build();
PhoneAuthProvider.verifyPhoneNumber(options);
userInputs();
}
else
{
//Display a welcome a screen to register an account.
FancyToast.makeText(getContext(), "Welcome! Open an account", FancyToast.LENGTH_SHORT, FancyToast.INFO, false).show();
}
}
}
}
});
Allowing unauthenticated user to have a privilege into the database is very risk. Hence, I implemented a rule to allow unauthenticated user to read only.
match /USERS/{document=**}
{
allow read: if true;
}
Though this still is risky, any rule suggestions I will be grad and appreciable.
I am doing a simple Registration page and I have a problem. I can't put users' data in the firestore database. I did it with Realtime Database but with firestore there is a problem and I don't know what it is. Here is the code :
CollectionReference UsersRef = FirebaseFirestore.getInstance().collection("Users");
if (UsersRef != null) {
HashMap<String, Object> userDataMap = new HashMap<>();
userDataMap.put("phone",phoneNumber);
userDataMap.put("password",password);
userDataMap.put("name",name);
UsersRef.document(phoneNumber).update(userDataMap).addOnSuccessListener(aVoid -> {
// First TOAST
Toast.makeText(RegisterActivity.this,"Congratulations, your account has been created.",Toast.LENGTH_SHORT).show();
loadingBar.dismiss();
startLoginActivity();
}).addOnFailureListener(e -> {
loadingBar.dismiss();
//SECOND TOAST
Toast.makeText(RegisterActivity.this,"Network error, please try again later",Toast.LENGTH_LONG).show();
});
} else {
Toast.makeText(RegisterActivity.this,"Votre reference USERS n'a pas été créée",Toast.LENGTH_SHORT).show();
}
I'm getting the user's inputs with variables name, phone number, and password.
If everything is fine, the first toast appears.
if the operation didn't work, the second toast appears.
Just for the sake of giving this post an answer:
As Tom Bailey has correctly commented, the code above is using the update() operation. On this official documentation you can find very useful information about the set() and update() operations.
TL:DR
Set(): creates or overwrites a single document. If the document does not exist, it will be created. If the document does exist, its contents will be overwritten with the newly provided data.
Update(): updates some fields of a document without overwriting the entire document.
I am using Firebase on both Android and iOS and after I register a user, I pass the username I took from the user, to my server to update the displayname. This works, but it's not visible on the app until the user closes the app or logs out.
I have tried using the reload method which states
Manually refreshes the data of the current user (for example, attached providers, display name, and so on).
But this doesn't do anything.
Is there another way of refreshing/reloading the data, or maybe clearing the cache?
It's possible that I could add the displayName in the app itself, but as I limit the displayname based on availability of names in my server, it makes sense to do it there.
UPDATE: (have added some code examples)
I update the displayname in a java app using the admin sdk like this
private void updateUsernameInFirebase(String uniqueId, String username){
UserRecord.UpdateRequest request = new UserRecord.UpdateRequest(uniqueId).setDisplayName(username);
try {
UserRecord userRecord = FirebaseAuth.getInstance().updateUser(request);
} catch (FirebaseAuthException e) {
e.printStackTrace();
}
}
and then try to refresh the data in my Android client app like this
FirebaseUser firebaseUser = FirebaseAuth.getInstance().getCurrentUser();
if(firebaseUser != null) {
Task<Void> reload = firebaseUser.reload();
}