0

I have a form that takes email and password. So after entering the data first I checked the email is already available in document (in Firebase firestore database) or not. If not available then I will insert those data. But my code always inserts first before executing the check for what already exists. Is any way I can solve it without using my insert code inside the checking code.

For example, my database already has "test@gmail.com" document. When I input the same email and submit then it will show Account available. Can not sign up! But my code inserts again then check for the email availability in DB.

Thank you for sharing your valuable time.

XML Code:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical"
    android:paddingTop="30dp">

    <View
        android:layout_width="400dp"
        android:layout_height="1dp"
        android:background="#678049"
        android:layout_gravity="center"
        android:layout_marginTop="10dp"
        android:layout_marginBottom="30dp"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="center"
        android:layout_marginTop="5dp">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Email"
            android:textSize="20dp"
            android:textColor="@color/black"
            android:layout_marginLeft="5dp"
            android:layout_gravity="center|top"/>

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="center"
        android:layout_marginTop="5dp">

        <EditText
            android:id="@+id/UserEmail"
            android:layout_width="345dp"
            android:layout_height="40dp"
            android:textSize="20dp"
            android:background="#5676"
            android:hint="Enter  Email Adress.."
            android:inputType="text"
            android:layout_marginLeft="3dp"/>

    </LinearLayout>
    <Space
        android:layout_width="match_parent"
        android:layout_height="5dp" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="center"
        android:layout_marginTop="5dp">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Password"
            android:textColor="@color/black"
            android:textSize="20dp"
            android:layout_marginLeft="5dp" />

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="center"
        android:layout_marginTop="5dp">

        <EditText
            android:id="@+id/UserPassword"
            android:layout_width="345dp"
            android:layout_height="40dp"
            android:textSize="20dp"
            android:background="#5676"
            android:hint="Enter Password.."
            android:layout_marginVertical="13dp"
            android:inputType="textPassword" />

    </LinearLayout>

    <Button
        android:id="@+id/btnSubmit"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="15dp"
        android:padding="10dp"
        android:layout_marginHorizontal="80dp"
        android:backgroundTint="#4C4B4B"
        android:text="Sign up"
        android:textColor="#F5FBF6" />

</LinearLayout>

Java Code

public class MainActivity extends AppCompatActivity {
    public static final String COLLECTION_USER = "collection_user";
    EditText userEmail, userPassword;
    Button btnSignUp;
    boolean accountAlreadyAvailable = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        userEmail = findViewById(R.id.UserEmail);
        userPassword = findViewById(R.id.UserPassword);
        btnSignUp = findViewById(R.id.btnSubmit);

        btnSignUp.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String email = userEmail.getText().toString();
                String password = userPassword.getText().toString();

                Map<String, Object> data = new HashMap<>();
                data.put("email", email);
                data.put("password", password);

                FirebaseFirestore db = FirebaseFirestore.getInstance();
                db.collection(COLLECTION_USER).document(email)
                        .get()
                        .addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
                            @Override
                            public void onComplete(@NonNull Task<DocumentSnapshot> task) {
                                if(task.getResult().exists()) {
                                    accountAlreadyAvailable = true;
                                    Log.d(TAG, "Account available changed to: " + accountAlreadyAvailable);
                                }
                            }
                        });

                //As no email document found,
                //new data will insert to firebase firestore database
                Log.d(TAG, "Account available: " + accountAlreadyAvailable);
                if(accountAlreadyAvailable == false) {
                    db.collection(COLLECTION_USER).document(email).set(data).addOnCompleteListener(new OnCompleteListener<Void>() {
                        @Override
                        public void onComplete(@NonNull Task<Void> task) {
                            if(task.isSuccessful()) {
                                Log.d(TAG, "Data inserted as no account found");
                            }
                        }
                    });
                } else {
                    Log.d(TAG, "Account available. Can not sign up!");
                }
            }
        });
    }
}
  • I have understood this [link](https://stackoverflow.com/a/50435519/15541176) (i have also read this [link](https://stackoverflow.com/a/57330767/15541176) ) but can't figure out in which section of my code I have to add to implement the callback. Can you give the solution using a custom callback? @Tyler V – Arafat_19AK Jan 01 '23 at 19:03
  • A callback is just one of the solutions listed there - you could also call some sort of processing method inside `onComplete` to handle the data. You need to understand that the code you put in `onComplete` is not run immediately, it is run sometime in the future when it gets data. In your case, you could just break your work up into several functions, and call them from within `onComplete` (e.g. make a separate function for the second step and call `doSecondStep(accountAlreadyAvailable)` inside `onComplete`). – Tyler V Jan 01 '23 at 19:06
  • Finally, understand the asynchronous term as well as it worked by calling a method(contains the insert condition + codes) from **onComplete** section. – Arafat_19AK Jan 01 '23 at 19:30
  • 1
    I added an answer showing how to break the code up into a few functions and call those from within `onComplete` - once you understand that the callback code is not run until later, then you can structure the code around that knowledge. – Tyler V Jan 01 '23 at 19:31
  • Since you're using Java, I think that this [resource](https://medium.com/firebase-tips-tricks/how-to-create-a-clean-firebase-authentication-using-mvvm-37f9b8eb7336) will help. Here is the corresponding [repo](https://github.com/alexmamo/FirebaseAuthentication). – Alex Mamo Jan 02 '23 at 08:05

2 Answers2

1

Data is loaded from Firestore (and most modern cloud APIs) asynchronously, which changes the order in which the code executed from what you may be used to. It's easiest to see this by setting breakpoints and running in the debugger, or by adding some logging:

Log.i("Firestore", "Before calling get()");
FirebaseFirestore db = FirebaseFirestore.getInstance();
db.collection(COLLECTION_USER).document(email)
.get()
.addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
    @Override
    public void onComplete(@NonNull Task<DocumentSnapshot> task) {
        Log.i("Firestore", "Got data");
    }
});
Log.i("Firestore", "After calling get()");

When you run this code, it logs:

Before calling get()
After calling get()
Got data

This may not be the order in which you expected the code to execute, but it is working as designed and perfectly explains why your second read from the database doesn't work: by the time that runs accountAlreadyAvailable = true hasn't executed yet.


The solution for this type of problem is always the same: any code that needs the data from the database, has to be inside the onComplete, be called from there, or be otherwise sychronized.

The simplest fix is to move the second read operation into the onComplete of the first:

FirebaseFirestore db = FirebaseFirestore.getInstance();
db.collection(COLLECTION_USER).document(email)
.get()
.addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
    @Override
    public void onComplete(@NonNull Task<DocumentSnapshot> task) {
        if(task.getResult().exists()) {
            accountAlreadyAvailable = true;
            Log.d(TAG, "Account available changed to: " + accountAlreadyAvailable);
        }

        // 
        if(accountAlreadyAvailable == false) {
            db.collection(COLLECTION_USER).document(email).set(data).addOnCompleteListener(new OnCompleteListener<Void>() {
                @Override
                public void onComplete(@NonNull Task<Void> task) {
                    if(task.isSuccessful()) {
                        Log.d(TAG, "Data inserted as no account found");
                    }
                }
            });
        } else {
            Log.d(TAG, "Account available. Can not sign up!");
        }
    }
});

Now the second read only executes once the first one has completed, and the accountAlreadyAvailable has been set.


Dealing with asynchronous API calls is an incredibly common problem, so I recommend reading up on it. Some references:

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • As I mentioned in the question I have solved it the same way you suggest to me in the line "The simplest fix is to move the second read operation ... ...". After checking the links you noted in the end, I am unable to configure how can I implement custom callback. I have understood this [link](https://stackoverflow.com/a/57330767/15541176) (i have also read this [link](https://stackoverflow.com/a/50435519/15541176) but can't figure out in which section of my code I have to add to implement the callback. Can you give the solution using a custom callback? @Frank van Puffelen – Arafat_19AK Jan 01 '23 at 18:56
  • If you're having trouble implementing a specific solution based on my answer, I recommend posting a new question showing the [**minimal** code that reproduces where you got stuck](http://stackoverflow.com/help/mcve). – Frank van Puffelen Jan 01 '23 at 21:43
1

These calls are asynchronous, so the code you put in onComplete does not run in order - it runs sometime in the future when the data is available and you have to structure your code accordingly. You can't just write one long function and expect it to work like it would with a synchronous approach.

There are a lot of ways to solve this, but all of them require you to understand that callback code (whether your own custom callback or the callback passed to Firestore) does not run immediately. A custom callback is rarely necessary as a solution - Firestore already uses a callback. Instead, you just need to understand how a callback works and use it appropriately.

For example, you could solve your problem by splitting up your work into a few functions:

btnSignUp.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        String email = userEmail.getText().toString();
        String password = userPassword.getText().toString();
        createAccount(email, password);
    }
});

Where the first function you call makes the first async request

private void createAccount(String email, String password) {
    Map<String, Object> data = new HashMap<>();
    data.put("email", email);
    data.put("password", password);

    FirebaseFirestore db = FirebaseFirestore.getInstance();
    db.collection(COLLECTION_USER).document(email)
            .get()
            .addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
                @Override
                public void onComplete(@NonNull Task<DocumentSnapshot> task) {
                    // The "createAccount" function will exit before this is run,
                    // so this MUST be called inside onComplete
                    finishSignUp(task.getResult().exists());
                }
            });
}

and then calls the next function inside onComplete once the first call is done. You cannot call this until the data is retrieved, and that doesn't happen until your listener is called.

private void finishSignUp(boolean accountAvailable) {
    //As no email document found,
    //new data will insert to firebase firestore database
    Log.d(TAG, "Account available: " + accountAvailable);

    FirebaseFirestore db = FirebaseFirestore.getInstance();
    if(!accountAvailable) {
        db.collection(COLLECTION_USER).document(email).set(data).addOnCompleteListener(new OnCompleteListener<Void>() {
            @Override
            public void onComplete(@NonNull Task<Void> task) {
                if(task.isSuccessful()) {
                    Log.d(TAG, "Data inserted as no account found");
                }
            }
        });
    } else {
        Log.d(TAG, "Account available. Can not sign up!");
    }
}
Tyler V
  • 9,694
  • 3
  • 26
  • 52