Lesson Wednesday

Now that we can successfully register user accounts and save their information to their FirebaseUser profile, let's add functionality to allow users to log in. The code we'll write for signing a user in with Firebase will actually be very similar to the code we wrote to create a user in our CreateAccountActivity.

First, we will need to declare and set the instance of our FirebaseAuth object. At the same time, let's bind our EditTexts and Button views using ButterKnife:

LoginActivity.java
public class LoginActivity extends AppCompatActivity implements View.OnClickListener {
    public static final String TAG = LoginActivity.class.getSimpleName();

    @Bind(R.id.passwordLoginButton) Button mPasswordLoginButton;
    @Bind(R.id.emailEditText) EditText mEmailEditText;
    @Bind(R.id.passwordEditText) EditText mPasswordEditText;
    @Bind(R.id.registerTextView) TextView mRegisterTextView;

    private FirebaseAuth mAuth;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        ButterKnife.bind(this);

        mAuth = FirebaseAuth.getInstance();
        ...
    }

    ...
}

Next, let's add a click listener to our mPasswordLoginButton which will trigger a loginWithPassword() method we will create momentarily:

LoginActivity.java
public class LoginActivity extends AppCompatActivity implements View.OnClickListener {
    ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        mPasswordLoginButton.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        ...
        if (view == mPasswordLoginButton) {
            loginWithPassword();
        }
    }
}

As you can see, the user will complete the login form, clickmPasswordLoginButton, and our app will run a method called loginWithPassword(). This method will eventually be responsible for reaching out to Firebase and authenticating the account with the user-provided credentials. But first, we'll gather the user's email and password, and double-check for blank fields:

LoginActivity.java
...
private void loginWithPassword() {
        String email = mEmailEditText.getText().toString().trim();
        String password = mPasswordEditText.getText().toString().trim();
        if (email.equals("")) {
            mEmailEditText.setError("Please enter your email");
            return;
        }
        if (password.equals("")) {
            mPasswordEditText.setError("Password cannot be blank");
            return;
        }
    }
...

If either the email of password values are empty we use setError() to display relevant error messages and then return to stop the method entirely before we ever reach out to Firebase.

Built-In Firebase Authentication Methods and Listeners

With the user's login credentials in hand, we can begin actually authenticating their account. To do this, we'll call a built-in Firebase method called signInWithEmailAndPassword() within our loginWithPassword() method.

As outlined in its documentation, signInWithEmailAndPassword() must be called on a FirebaseAuth object, and takes the user's email and password as arguments. In addition, we must provide an OnCompleteListener to handle the authentication attempt.

As the name suggests, this listener is responsible for determining when the authentication attempt is complete, and executes the onComplete() override when it is:

LoginActivity.java
private void loginWithPassword() {
        ...
        mAuth.signInWithEmailAndPassword(email, password)
                .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {

                    @Override
                    public void onComplete(@NonNull Task<AuthResult> task) {
                        Log.d(TAG, "signInWithEmail:onComplete:" + task.isSuccessful());
                        if (!task.isSuccessful()) {
                            Log.w(TAG, "signInWithEmail", task.getException());
                            Toast.makeText(LoginActivity.this, "Authentication failed.",
                                    Toast.LENGTH_SHORT).show();
                        }
                    }
                });

    }
  • If the user entered an email and password, we pass them to thesignInWithEmailAndPassword() method.

  • Just like with the createUserWithEmailAndPassword() method we utilized in our CreateAccountActivity, we add a completion listener to the signInWithEmailAndPassword() method.

  • If the sign-in attempt was unsuccessful, we display a toast to inform the user.

If we launch the application now, we can see whether our login attempts are successful in our logcat, but the application won't navigate anywhere after successful authentication quite yet.

Add AuthStateListener

Next, let's tell our app what to do when the user's account is successfully authenticated. When a user logs in we want them to see our MainActivity. Just like we did in our CreateAccountActivity, let's add an AuthStateListener to listen for changes in the current authentication state. If a user is authenticated, the app will navigate to the MainActivity.

Similar to its implementation in CreateAccountActivity, we will attach and remove the AuthStateListener in the onStart() and onStop() lifecycle methods:

LoginActivity.java
public class LoginActivity extends AppCompatActivity implements View.OnClickListener {
    ...
    private FirebaseAuth.AuthStateListener mAuthListener;

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

        ButterKnife.bind(this);

        mAuth = FirebaseAuth.getInstance();

        mAuthListener = new FirebaseAuth.AuthStateListener() {

            @Override
            public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) {
                FirebaseUser user = firebaseAuth.getCurrentUser();
                if (user != null) {
                    Intent intent = new Intent(LoginActivity.this, MainActivity.class);
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
                    startActivity(intent);
                    finish();
                }
            }
        };

        ...
    }

    ...

    @Override
    public void onStart() {
        super.onStart();
        mAuth.addAuthStateListener(mAuthListener);
    }

    @Override
    public void onStop() {
        super.onStop();
        if (mAuthListener != null) {
            mAuth.removeAuthStateListener(mAuthListener);
        }
    }
}

Let's run our app, sign out the current user in the MainActivity, and then log them back in. If everything is setup properly, our app will bring the us to the MainActivity after we click "Log In". If we restart the app our user should still be logged in, and our app should navigate to the MainActivity automatically.

Add ProgressDialog

Lastly, let's add a ProgressDialog to our LoginActivity to notify the user that the app is processing their authenticate request. This code should look identical to the ProgressDialog code we wrote in CreateAccountActivity:

LoginActivity.java
public class LoginActivity extends AppCompatActivity implements View.OnClickListener {
    ...
    private ProgressDialog mAuthProgressDialog;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        createAuthProgressDialog();
    }

    ...

    private void createAuthProgressDialog() {
        mAuthProgressDialog = new ProgressDialog(this);
        mAuthProgressDialog.setTitle("Loading...");
        mAuthProgressDialog.setMessage("Authenticating with Firebase...");
        mAuthProgressDialog.setCancelable(false);
    }

    private void loginWithPassword() {
        ...
        if (password.equals("")) {
            mPasswordEditText.setError("Password cannot be blank");
            return;
        }
        mAuthProgressDialog.show();
        mAuth.signInWithEmailAndPassword(email, password)
                .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {

                    @Override
                    public void onComplete(@NonNull Task<AuthResult> task) {
                        mAuthProgressDialog.dismiss();
                        ...
                    }
                });
    }

    ...
}

Let's run the app one more time, log out the currently authorized user and then log them back in to make sure our progress dialogs are displaying properly.


Example GitHub Repo for MyRestaurants