ANDROID LOGIN AND REGISTRATION USING CLOUD FIRESTORE
You like what you Read then Vote!
0 / 5 4.86 21

Your page rank:

One of the main features of most android application is to provide a login system for its users to get access to certain data inside the app that non login users will be privy to.

In this tutorial, we are going to create an android login and registration system using new android Cloud Firestore real-time database for user authentication and storage.

If you have not use Android Cloud Firestore before, this is your opportunity to get familiar with it.

We will start off with a brief introduction to Android Cloud Firestore real-time database.

WHAT IS CLOUD FIRESTORE

Android Cloud Firestore is a cloud hosted NoSQL database from Google cloud team.

At the time of writing this tutorial, Cloud Firestore is still in beta version.

Although you can use it now to get familiar with its features but it is not yet recommended for production app. This is because there might be changes and fixes in the SDK before the first version comes out.

With Cloud Firestore, you can sync your data across multiple devices and each connected device is notified by an event listener when there is a change in app data.

Another important features that Cloud Firestore offers mobile and web platforms is offline data support.

BELOW ARE REASONS WHY YOU SHOULD CONSIDER USING CLOUD FIRESTORE.

The Cloud Firestore data model supports flexible, hierarchical data structures. Store your data in documents, organized into collections. Documents can contain complex nested objects in addition to sub-collections.

In Cloud Firestore, you can use queries to retrieve individual, specific documents or to retrieve all the documents in a collection that match your query parameters. Your queries can include multiple, chained filters and combine filtering and sorting.

Like Real-time Database, Cloud Firestore uses data synchronization to update data on any connected device. However, it’s also designed to make simple, one-time fetch queries efficiently.

Cloud Firestore caches data that your app is actively using, so the app can write, read, listen to, and query data even if the device is offline. When the device comes back online, Cloud Firestore synchronizes any local changes back to Cloud Firestore.

I know you are blown away already but come to think of it, what is the difference between Firebase real-time database and Cloud Firestore?

WHAT IS THE DIFFERENCE BETWEEN FIREBASE REAL-TIME DATABASE AND CLOUD FIRESTORE?

Firebase Real-Time DatabaseAndroid Cloud Function
It stores data as one large JSON tree.It stores data in documents organized in collections.
Offline support for mobile clients on iOS and Android onlyOffline support for iOS, Android, and web clients
Deep queries with limited sorting and filtering functionality.Indexed queries with compound sorting and filtering.
Basic write and transaction operations.Atomic write and transaction operations.
Realtime Database is a mature product.Cloud Firestore is currently in beta.

CLOUD FIRESTORE DATA  MODEL

Let’s dive into Cloud Firestore data model.

As stated earlier,  Cloud Firestore is a NoSQL cloud database. If you have worked with any NoSQL database before you will be familiar with somethings we will cover here.

Cloud Firestore stores data in a Document. Document has fields. Each document can store any data type ranging from String, numbers and complex objects. Documents can also contain a sub-collection data type

Each document field is represented by a key and a value.

Cloud Firestore wrappers similar documents inside a collection. This makes modelling and querying Cloud Firestore very simple.

The Cloud Firestore data model supports whatever data structure that works best for your app.

Structuring your Cloud Firestore database will have an impact in your app if it is not well model. This is what is generally refer to as deep nesting of data.

This can occur when you structure your data model in such a way that any output query to your database will retrieve all the collection. Using data snapshot to listener and retrieve only changed data is important.

HOW TO ENABLE FIRESTORE

Go to Firebase Console. Login or create a new account if you have not created an account before.

Click on add a project and fill out the project information detail as shown below

Click on the Database link at the left sidebar.

Select Cloud Firestore Database and click on the get started with Cloud Firestore button.

When you get to this page it means that you have successfully created a new Cloud Firestore database. The next thing we will look at is how we can secure our Cloud Firestore to avoid users getting access to unauthorized data.

HOW TO SECURE YOUR CLOUD FIRESTORE

As can be seen in the image above, click on the Rules tab to open the Cloud Firestore rules.

Since we are creating a demo application we will select the Test Mode.

This part is from Cloud Firestore doc – “Test Mode – Good for getting started with the mobile and web client libraries, but allows anyone to read and overwrite your data. After testing, make sure you secure your data”

When we want to secure our Cloud Firestore database, we can use the Firebase Authentication and Cloud Firestore Security Rules to achieve this.

The Test Mode rules is shown below

// Allow read/write access to all users under any conditions
// Warning: **NEVER** use this rule set in production; it allows
// anyone to overwrite your entire database.
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if true;
    }
  }
}

As you can see, everybody is allowed to read and write data. If you want to add some authentication to allow only login users to read and write data then you can use the rules below.

// Allow read/write access on all documents to any user signed in to the application
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if request.auth.uid != null;
    }
  }
}

You will find more information on Cloud Firestore Security Rule here.

In our next tutorial we will dive deeper into Cloud Firestore Security Rule but since we are creating a simple Login and Registration system without Firebase Authentication, we will use only Test Mode Cloud Firestore rules.

This tutorial is basically like learning how to add, retrieve and check data in Cloud Firestore in the form of Login system.

LET’S TEST CLOUD FIRESTORE WITH LOGIN AND REGISTRATION SYSTEM

We have done with explaining what Cloud Firestore is about. Now we will soil our hands with code by creating an android app that uses Cloud Firestore.

SCREENSHOTS OF THE CLOUD FIRESTORE LOGIN SYSTEM

1. CREATE A NEW ANDROID PROJECT

  • Open Android Studio
  • Go to file menu
  • Select  new
  • Enter project name
  • Enter activity name
  • Keep other default settings
  • Click on finish button to create a new android project

2. ADD LIBRARY DEPENDENCIES IN BUILD.GRADLE FILE

To access Cloud Firestore and other libraries we will use in this project, we need to add the libraries in our build.gradle file.

At the time of writing this tutorial the Cloud Firestore version is shown below. Make sure you use the latest version in your application.

implementation 'com.google.firebase:firebase-firestore:17.1.5'

Now open the build.gradle file and add all the dependencies we will use in this android login and registration system.

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha2'
    implementation 'com.google.android.material:material:1.0.0-alpha1'
    implementation 'androidx.legacy:legacy-support-v4:1.0.0-alpha1'

    implementation 'com.google.firebase:firebase-core:16.0.5'
    implementation 'com.google.firebase:firebase-firestore:17.1.3'

    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.1.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
}

3. UPDATE COLORS.XML

Open res folder > colors.xml and add the code below to it.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#FF9900</color>
    <color name="colorPrimaryDark">#e48900</color>
    <color name="colorAccent">#500897</color>
    <color name="colorWhite">#ffffff</color>
    <color name="colorBlack">#000000</color>
    <color name="colorText">#666666</color>

</resources>

4. WORKING WITH CLOUD FIREBASE IN ANDROID

One thing to have in mind is that this project is big so we will only cover the most valuable part. For the remaining part, you will have access to the project github repo. You can download the whole project and check it out.

4.1. We will create a repository package inside our src folder. Right click on src folder and select New > Package. In the open package dialog, enter the name of your package. I will use repository but feel free to use any name of your choice.

4.2. Inside the src folder > repository folder,  create a new interface and name it IUserRepository.java. Open the file and add three methods to it.

public interface IUserRepository {

    void doesUserEmailExist(String email, FirestoreUserModel firestoreUserModel);

    void addNewRegisteredUser(FirestoreUserModel firestoreUserModel);

    void getLoginUserByEmail(String email);
}

he method names are very explanatory. We are going to implement this methods in another class.

4.3. Inside the src folder > repository folder, create a new java file as before and name the file UserImpl.java. This class will implement the IUserRepository interface.

Open the UserImpl class and add the code below.

public class UserImpl implements IUserRepository {

    private static final String TAG = UserImpl.class.getSimpleName();

    private Context context;

    private FirebaseFirestore db;

    private CustomApplication app;


    public UserImpl(Context context) {
        this.context = context;
        app = CustomApplication.getInstance();
        db = app.getDbInstance();
    }

    @Override
    public void doesUserEmailExist(String email, FirestoreUserModel firestoreUserModel) {
        Query query = db.collection(Constants.USER_COLLECTION).whereEqualTo("email", email);
        query.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
            @Override
            public void onComplete(@NonNull Task<QuerySnapshot> task) {
                if(Objects.requireNonNull(task.getResult()).size() > 0){
                    Toast.makeText(context, "User email already exist in the database ", Toast.LENGTH_SHORT).show();
                }else{
                    //add a new user to Firestore database
                    addNewRegisteredUser(firestoreUserModel);
                }
            }
        });
    }

    @Override
    public void addNewRegisteredUser(FirestoreUserModel firestoreUserModel) {
        String userEnteredName = firestoreUserModel.getUsername();

        Map<String, Object> user = new HashMap<>();
        user.put(Constants.DocumentFields.USERNAME, firestoreUserModel.getUsername());
        user.put(Constants.DocumentFields.EMAIL, firestoreUserModel.getEmail());
        user.put(Constants.DocumentFields.PASSWORD, firestoreUserModel.getPassword());

        Task<Void> newUser = db.collection(Constants.USER_COLLECTION).document(firestoreUserModel.getEmail()).set(user);
        newUser.addOnSuccessListener(new OnSuccessListener<Void>() {
            @Override
            public void onSuccess(Void aVoid) {
                Log.d(TAG, "User was successfully added");
                NavUtil.moveToNextPage(context, ProfileActivity.class, userEnteredName);
            }
        }).addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                Log.d(TAG, "Error has occurred " + e.getMessage());
            }
        });
    }

    @Override
    public void getLoginUserByEmail(String email) {
        DocumentReference docRef = db.collection(Constants.USER_COLLECTION).document(email);
        docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
            @Override
            public void onComplete(@NonNull Task<DocumentSnapshot> task) {
                if(task.isSuccessful()){
                    FirestoreUserModel user = Objects.requireNonNull(task.getResult()).toObject(FirestoreUserModel.class);
                    //Navigate to profile page
                    if(user != null){
                        NavUtil.moveToNextPage(context, ProfileActivity.class, user.getUsername());
                    }else{
                        Toast.makeText(context, "Missing user record ", Toast.LENGTH_SHORT).show();
                    }

                }else{
                    String excep = Objects.requireNonNull(task.getException()).getMessage();
                    Log.d(TAG, "Error reading user data " + excep);

                }
            }
        });
    }
}

As can be seen in the code snippet above, we have a concrete implementation of these methods. To use any of them in our Activity class is to call the method.

Let’s step back a bit to learn how data is stored in Cloud Firestore.

Cloud Firestore stores data in Documents, which are stored in Collections. Cloud Firestore creates collections and documents implicitly the first time you add data to the document. You do not need to explicitly create collections or documents1.

You can add data in different ways in Cloud Firestore.

  1. When you use the code below, Cloud Firestore will generate an id for your doc. 
db.collection("users").add(user).
db.collection(Constants.USER_COLLECTION).document(firestoreUserModel.getEmail()).set(user); we are using email as an id for our document.

With the reference to each Document, you can update the data or delete the data. To learn more about how to add data in Cloud Firebase click here.

5. CREATE LOGINACTIVITY.JAVA

Create a new activity file and name it LoginActivity.java. The layout of this activity file will contain EditText, TextView and Button widgets for user login input and submission.

5.1. Open the activity_login.xml and add the code below.

<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat 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=".login.LoginActivity"
    android:padding="24dp"
    android:background="@color/colorPrimaryDark"
    android:orientation="vertical">

    <androidx.appcompat.widget.LinearLayoutCompat
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="3"
        android:gravity="center"
        android:orientation="vertical">

        <androidx.appcompat.widget.AppCompatImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            app:srcCompat="@drawable/chat_logo"/>

        <androidx.appcompat.widget.AppCompatTextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="One LOVE CHAT"
            android:textColor="@color/colorWhite"
            android:textAllCaps="true"
            android:layout_marginTop="10dp"
            android:textSize="15sp"/>

    </androidx.appcompat.widget.LinearLayoutCompat>

    <androidx.appcompat.widget.LinearLayoutCompat
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="5"
        android:orientation="vertical">

        <com.google.android.material.textfield.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textColorHint="@color/colorWhite"
            android:layout_marginStart="20dp"
            android:layout_marginTop="30dp"
            android:layout_marginEnd="20dp">

            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/email"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:inputType="textEmailAddress"
                android:textColor="@color/colorWhite"
                android:drawableStart="@drawable/ic_person_white_18dp"
                android:textSize="16sp"
                android:drawablePadding="4dp"
                android:hint="Email"/>
        </com.google.android.material.textfield.TextInputLayout>

        <com.google.android.material.textfield.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textColorHint="@color/colorWhite"
            android:layout_marginStart="20dp"
            android:layout_marginTop="20dp"
            android:layout_marginEnd="20dp">

            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/password"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:inputType="textPassword"
                android:textColor="@color/colorWhite"
                android:drawableStart="@drawable/ic_remove_red_eye_white_18dp"
                android:drawablePadding="4dp"
                android:textSize="16sp"
                android:hint="Password"/>
        </com.google.android.material.textfield.TextInputLayout>

        <androidx.appcompat.widget.AppCompatTextView
            android:id="@+id/forgot_password"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="20dp"
            android:layout_marginEnd="20dp"
            android:textColor="@color/colorWhite"
            android:textSize="12sp"
            android:text="Forgot Password?"
            android:gravity="end"/>


        <androidx.appcompat.widget.AppCompatButton
            android:id="@+id/login_btn"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/colorAccent"
            android:text="Login"
            android:textColor="@color/colorWhite"
            android:layout_marginTop="40dp"
            android:layout_marginEnd="20dp"
            android:layout_marginStart="20dp"/>
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center|bottom"
            android:orientation="vertical">

            <androidx.appcompat.widget.AppCompatTextView
                android:id="@+id/signup_btn"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="20dp"
                android:layout_marginEnd="20dp"
                android:textColor="@color/colorWhite"
                android:textSize="12sp"
                android:textStyle="bold"
                android:layout_marginBottom="12dp"
                android:padding="4dp"
                android:text="New User? SIGN UP"/>
        </LinearLayout>
    </androidx.appcompat.widget.LinearLayoutCompat>

</androidx.appcompat.widget.LinearLayoutCompat>

5.2. Open the LoginActivity.java and add the code below.

public class LoginActivity extends AppCompatActivity {

    private static final String TAG = LoginActivity.class.getSimpleName();

    private AppCompatEditText emailBox;
    private AppCompatEditText passwordBox;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

        super.onCreate(savedInstanceState);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
            getWindow().setStatusBarColor(Color.TRANSPARENT);
        }

        setContentView(R.layout.activity_login);

        ActionBar actionBar = getSupportActionBar();
        if(null != actionBar){
            actionBar.hide();
        }

        AppCompatTextView forgotPasswordBtn = (AppCompatTextView)findViewById(R.id.forgot_password);
        forgotPasswordBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(LoginActivity.this, "Todo - Forgot password implementation", Toast.LENGTH_SHORT).show();
            }
        });

        AppCompatTextView signUpBtn = (AppCompatTextView)findViewById(R.id.signup_btn);
        signUpBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(LoginActivity.this, "Todo - Sign implementation", Toast.LENGTH_SHORT).show();
                Intent signUpIntent = new Intent(LoginActivity.this, SignUpActivity.class);
                startActivity(signUpIntent);
            }
        });

        emailBox = (AppCompatEditText)findViewById(R.id.email);
        passwordBox = (AppCompatEditText)findViewById(R.id.password);

        AppCompatButton submitBtn = (AppCompatButton)findViewById(R.id.login_btn);
        submitBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String emailAddress = Objects.requireNonNull(emailBox.getText()).toString();
                Log.d(TAG, "Log email " + emailAddress);
                String password = Objects.requireNonNull(passwordBox.getText()).toString();

                if(TextUtils.isEmpty(emailAddress) || TextUtils.isEmpty(password)){
                    Toast.makeText(LoginActivity.this, "Login Fields must not be empty", Toast.LENGTH_SHORT).show();
                }else if(!emailAddress.contains("@")){
                    Toast.makeText(LoginActivity.this, "Invalid email address", Toast.LENGTH_SHORT).show();
                }else{
                    UserImpl userImplementation = new UserImpl(LoginActivity.this);
                    userImplementation.getLoginUserByEmail(emailAddress);
                }
            }
        });
    }
}

You can see from the code above that after all the check and validation of user input, the getLoginUserByEmail(String email)was called.

UserImpl userImplementation = new UserImpl(LoginActivity.this);
userImplementation.getLoginUserByEmail(emailAddress);

PROJECT SOURCE CODE

To see all the source code associated with this project, please head over to the project Github repository and download the source code.

Please note that I have set my Cloud Firestore Security Rules back to Authentication to avoid giving access to my database. You are free to test your source code with your own Cloud Firestore database.

If you have any questions and suggestions, kindly use the comment box below or contact us using our contact page.

Recent Articles

Related Stories

Stay on op - Ge the daily news in your inbox