Skip to main content

[Make-A] Simple Chat Application using Cloud Firestore Part 3 – Adding a Simple “Authentication”

 

1. Simple Authentication

As a user,
When I open the app,
If there’s an existing account,
Then log in with that account,
Else create a new account,
Then log in with that account
  • Display rooms screen
  • Display a message if a new account has been created
  • Display a message if user has logged in

 

Before we proceed to how we will implement authentication feature, we need to learn some basic jargons first in NoSQL land.

collection – A collection is what you call a table in a relational database

document – A document is what you call a row in a relational database

They are not exactly the same but the concept is somehow similar.

Table of Contents

  1. Part 1 – Specs and Introduction
  2. Part 2 – Setting up Firebase Cloud Firestore
  3. Part 3 – Adding a Simple Authentication
  4. Part 4 – Adding a FloatingActionButton (FAB)
  5. Part 5 – Creating a Chat Room
  6. Part 6 – Entering The Chat Room
  7. Part 7 – Displaying The Chat Rooms
  8. Part 8 – Chat
  9. 10 Exercises and Thanks!

 

1. Enabling Cloud Firestore in Project Console

1. In your project console, click Database

2. Look for Cloud Firestore Beta card and click Get Started

3. Select Start in test mode then click Enable

 

2. Creating our class for handling authentication

What we are going to do is a pseudo-authentication. The process is similar to how real authentication works but we won’t have any logic. Here’s a brief explanation on how the process will look like:

Note: Every time you insert a document in Firestore, it will have a key generated by Firestore. Just think of document as a row in traditional relational database.

  1. When we open the app, we check if a key exist in our Shared Preferences.
  2. If no key is found, we create a new user in Firestore and store the key in our Shared Preferences.
  3. If a key exist in our SharedPreferences, we check first if that key exist in Firestore
  4. If the key exists and returns a user, use that user.
  5. If the key doesn’t exist, then do step 2 (This is just for some unknown/weird reason your user disappeared)

1. Create a new class called AuthenticationRepository

import android.support.annotation.NonNull;

import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.firestore.DocumentReference;
import com.google.firebase.firestore.DocumentSnapshot;
import com.google.firebase.firestore.FirebaseFirestore;

import java.util.HashMap;
import java.util.Map;

public class AuthenticationRepository {

    private static final String TAG = "AuthenticationRepo";

    private FirebaseFirestore db;

    public AuthenticationRepository(FirebaseFirestore db) {
        this.db = db;
    }

    public void login(String key,
                      final OnSuccessListener<DocumentReference> successCallback,
                      final OnFailureListener failureCallback) {

        DocumentReference userRef = db.collection("users").document(key);
        userRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
            @Override
            public void onComplete(@NonNull Task<DocumentSnapshot> task) {
                if (task.isSuccessful()) {
                    DocumentSnapshot userSnapshot = task.getResult();
                    if (userSnapshot != null && userSnapshot.exists()) {
                        successCallback.onSuccess(userSnapshot.getReference());
                    } else {
                        createNewUser(successCallback, failureCallback);
                    }
                } else {
                    failureCallback.onFailure(task.getException());
                }
            }
        });
    }

    public void createNewUser(final OnSuccessListener<DocumentReference> successCallback,
                              final OnFailureListener failureCallback) {

        Map<String, Object> user = new HashMap<>();
        user.put("name", "Arth Limchiu");

        db.collection("users")
                .add(user)
                .addOnSuccessListener(new OnSuccessListener<DocumentReference>() {
                    @Override
                    public void onSuccess(DocumentReference documentReference) {
                        successCallback.onSuccess(documentReference);
                    }
                })
                .addOnFailureListener(new OnFailureListener() {
                    @Override
                    public void onFailure(@NonNull Exception e) {
                        failureCallback.onFailure(e);
                    }
                });
    }
}

You can implement your own callback classes also but for this one we will just reuse OnSuccessListener<..> and OnFailureListener classes.

  • login(…) – takes care of fetching the user in Firestore. If a user is not found it will call createNewUser(…) passing in the callbacks.
  • createNewUser(…) – takes care of creating a new user in Firestore and passes a DocumentReference(which is the user) in successCallback .

Firestore takes care of creating a collection if it doesn’t exist like our users collection.

3. Setting up our Shared Preferences

1. In your MainActivity.class declare a static variable

private static final String CURRENT_USER_KEY = "CURRENT_USER_KEY";

2. Create two new methods in your MainActivity.class

private String getCurrentUserKey() {
    SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
    return preferences.getString(CURRENT_USER_KEY, "");
}

private void saveCurrentUserKey(String key) {
    SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
    SharedPreferences.Editor editor = preferences.edit();
    editor.putString(CURRENT_USER_KEY, key);
    editor.apply();
}

4. Implement our authentication feature

1. In your MainActivity.class, declare an AuthenticationRepository class and initialize it in onCreate()

public class MainActivity extends AppCompatActivity {

    ...

    AuthenticationRepository authentication;

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

        authentication = new AuthenticationRepository(FirebaseFirestore.getInstance());
    }
}

2. In your MainActivity.class, create a new function authenticate() and call it after authentication is initialized.

public class MainActivity extends AppCompatActivity {

    ...

    AuthenticationRepository authentication;

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

        authentication = new AuthenticationRepository(FirebaseFirestore.getInstance());

        authenticate();
    }

    private void authenticate() {
        String currentUserKey = getCurrentUserKey();
        if (currentUserKey.isEmpty()) {
            authentication.createNewUser(
                    new OnSuccessListener<DocumentReference>() {
                        @Override
                        public void onSuccess(DocumentReference documentReference) {
                            saveCurrentUserKey(documentReference.getId());
                            Toast.makeText(MainActivity.this, "New user created", Toast.LENGTH_SHORT).show();
                        }
                    },
                    new OnFailureListener() {
                        @Override
                        public void onFailure(@NonNull Exception e) {
                            Toast.makeText(
                                    MainActivity.this,
                                    "Error creating user. Check your internet connection",
                                    Toast.LENGTH_SHORT)
                                    .show();
                        }
                    }
            );
        } else {
            authentication.login(
                    currentUserKey,
                    new OnSuccessListener<DocumentReference>() {
                        @Override
                        public void onSuccess(DocumentReference documentReference) {
                            Toast.makeText(MainActivity.this, "Logged in", Toast.LENGTH_SHORT).show();
                        }
                    },
                    new OnFailureListener() {
                        @Override
                        public void onFailure(@NonNull Exception e) {
                            Toast.makeText(
                                    MainActivity.this,
                                    "Error signing in. Check your internet connection",
                                    Toast.LENGTH_SHORT)
                                    .show();
                        }
                    }
            );
        }
    }

    ...
}

5. Let’s test it out!

1. Run the app. You should see a Toast message or a different one if you implemented it differently.

2. Kill your app by swiping it off from your recent applications menu. Then open it again and you should see a different message.

 

6. Review

1. Simple Authentication

As a user,
When I open the app,
If there’s an existing account,
Then log in with that account,
Else create a new account,
Then log in with that account
  • Display rooms screen
  • Display a message if a new account has been created
  • Display a message if user has logged in

Authentication logic
Display rooms screen (our MainActivity IS our rooms screen. Just imagine it for now 😂)
Display a message if a new account has been created
Display a message if user has logged in

Remember that you can always implement this differently like show the name in a TextView or set the title of your MainActivity to the user’s name. As long as you achieved the goal.