Lesson Tuesday

Do not copy and paste the Sign In and Sign Up display code into your own personal project. Recreate the functionality shown here with your own design and layout.

Now that we've created the necessary components, we're ready to add functionality allowing users to register for accounts. In this lesson we'll learn how to create Firebase user accounts within an Android application. In subsequent lessons we'll walk through updating user profile information, logging users in and out, and adding additional features and personalization to improve user experience.

Registration Form

After following along with the previous lesson, we should be greeted by the LoginActivity when the application launches. But what if a user doesn't have an account yet? Below the login form is a TextView that reads "Don't have an account? Sign up here!":

link-to-registration-from-login-activity

Clicking this does not currently do anything. Let's make sure this link navigates to the CreateAccountActivity we created previously. We'll bind this view with ButterKnife, implement the View.OnClickListener interface in the LoginActivity, and set a click listener and corresponding onClick() function:

LoginActivity.java
...
public class LoginActivity extends AppCompatActivity implements View.OnClickListener {
    @Bind(R.id.registerTextView) TextView mRegisterTextView;

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

        ButterKnife.bind(this);
        mRegisterTextView.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        if (view == mRegisterTextView) {
            Intent intent = new Intent(LoginActivity.this, CreateAccountActivity.class);
            startActivity(intent);
            finish();
        }
    }
}

After an account is successfully authenticated, we no longer need the LoginActivity. In the code above, notice we include finish() after creating our Intent. This will destroy the LoginActivity as we depart for the CreateAccountActivity, saving us valuable resources. Check out the Android Documentation for more information on finish() and other methods to halt unused or unnecessary activities.

Now we can launch our app, click "Don't have an account? Sign up here!", and see our registration form:

user-registration-form-myrestaurants

Account Registration

Now that our registration page is easily accessible, let's allow users to create their own accounts using our registration form.

Basic Setup

First we'll implement click listeners, declare member variables, and bind elements from our activity_create_account.xml layout using ButterKnife. This should be review:

CreateAccountActivity.java
public class CreateAccountActivity extends AppCompatActivity implements View.OnClickListener {
    @Bind(R.id.createUserButton) Button mCreateUserButton;
    @Bind(R.id.nameEditText) EditText mNameEditText;
    @Bind(R.id.emailEditText) EditText mEmailEditText;
    @Bind(R.id.passwordEditText) EditText mPasswordEditText;
    @Bind(R.id.confirmPasswordEditText) EditText mConfirmPasswordEditText;
    @Bind(R.id.loginTextView) TextView mLoginTextView;

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

        mLoginTextView.setOnClickListener(this);
        mCreateUserButton.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {

        if (view == mLoginTextView) {
            Intent intent = new Intent(CreateAccountActivity.this, LoginActivity.class);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
            startActivity(intent);
            finish();
        }

        if (view == mCreateUserButton) {
            createNewUser();
        }

    }
}

Here, we bind all elements of our registration form and add a click listener that will call createNewUser() (which we will write momentarily) when the form is submitted. We also create a link back to the LoginActivity and add something called intent flags to manage our back stack of tasks.

Managing Stacks with Flags

Android manages tasks by placing activities started in succession in something called a stack. The last activity started is the first activity on the stack. When the system back button is selected, Android navigates to the last activity in the stack by default.

However, we don't actually want users navigating back to CreateAccountActivity after they've already created an account. They should no longer have any use for this form if they already have an account. To prevent this, we can explicitly remove these activities from our back stack altogether by setting flags on our intent.

Intent flags are essentially just extra pieces of information optionally added to an intent that determine how the specific intent is handled by Android. In the code above, we use the following flags:

  • FLAG_ACTIVITY_CLEAR_TASK will cause any existing task that would be associated with the activity to be cleared before the activity is started. This prevents the CreateAccountActivity from being unnecessarily accessed via the system back button.

  • FLAG_ACTIVITY_NEW_TASK will make the activity we are navigating to the start of a brand new task on this history stack.

For more information on tasks and the back stack, check out the Android Developer's guides article on Tasks and Back Stack.

Creating New Users

Now that we can successfully submit the registration form, let's write the createNewUser() method:

CreateAccountActivity.java
public class CreateAccountActivity extends AppCompatActivity implements View.OnClickListener {
    public static final String TAG = CreateAccountActivity.class.getSimpleName();
    ...
    private FirebaseAuth mAuth;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...

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

    ...
    private void createNewUser() {
        final String name = mNameEditText.getText().toString().trim();
        final String email = mEmailEditText.getText().toString().trim();
        String password = mPasswordEditText.getText().toString().trim();
        String confirmPassword = mConfirmPasswordEditText.getText().toString().trim();

        mAuth.createUserWithEmailAndPassword(email, password)
                .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
                    @Override
                    public void onComplete(@NonNull Task<AuthResult> task) {
                         if (task.isSuccessful()) {
                            Log.d(TAG, "Authentication successful");
                        } else {
                            Toast.makeText(CreateAccountActivity.this, "Authentication failed.",
                                    Toast.LENGTH_SHORT).show();
                        }
                    }
                });
    }
}

First, we add a member variable to get the instance of the FirebaseAuth object. This object is required in order to access the tools provided in the Firebase Authentication SDK. We then simply fetch the contents of our registration form (mNameEditText, mEmailEditText, mPasswordEditText and mConfirmPasswordEditText) and transform each value into a string.

Then, we call the built-in Firebase method createUserWithEmailAndPassword() to create a new user account in Firebase, passing in the user's email and password. If the account can be created successfully, we log a success message to the logcat; otherwise we display a toast notifying the user something went wrong.

Testing Account Registration

We should now be able to launch the application, navigate to the CreateAccountActivity, and submit our registration form. Note: Firebase Auth requires passwords be at least 6 characters long. Additionally, the email address must contain an @something.com domain, otherwise it will not be recognized as a valid email address. Make sure to use a long enough password and correctly-formatted email address or you may encounter errors:

registering-new-firebase-account

The app won't navigate anywhere after submitting this form (yet!), but if we visit the Auth tab in our Firebase dashboard and select Users , we can see a new Firebase user has been created:

user-listed-in-firebase

Listening for User Authentication

Next, we need to inform our application when the user's account is successfully authenticated. To do this, we'll add an AuthStateListener to respond to the change in the user's authentication state.

An AuthStateListener simply listens for an account being successfully authenticated, or un-authenticated through Firebase. Firebase can also automatically authenticate user accounts upon registration. Therefore, our users can submit the registration form and if their account is created successfully they will be logged in automatically, and this listener will be triggered.

Inside of the onAuthStateChanged() override, we will send the user to our MainActivity:

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

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

    ...

    private void createAuthStateListener() {
        mAuthListener = new FirebaseAuth.AuthStateListener() {

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

        };
    }

}
  • First, we add the mAuthStateListener member variable, setting it in our onCreate() method by calling a new method, createAuthStateListener().

  • Inside of the new createAuthStateListener() method, we create our new AuthStateListener by setting our member variable to the FirebaseAuth.AuthStateListener interface. This interface listens to changes in the current AuthState. When there is a change (ie. a user becomes authenticated or signs out), this interface triggers the onAuthStateChanged() method.

  • The onAuthStateChanged() method returns FirebaseAuth data. Using this data, we can create a FirebaseUser by calling the getCurrentUser() method. We double-check that this user is not null before traveling to the MainActivity.

onStop and onStart Overrides

Before we can test that our AuthStateListener is working, we need to add it to our FirebaseAuth object. We'll associate the two by calling addAuthStateListener() in the onStart() lifecycle method.

Additionally, we will also remove the listener before the activity is destroyed by calling the removeAuthStateListener() method in the onStop() lifecycle method:

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

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

The system calls the onStart() method every time your activity becomes visible (whether it's being restarted or created for the first time).

Conversely, Android calls onStop() when an activity is no longer visible. Once the activity is stopped, the system might destroy the instance if it needs to recover system memory. In extreme cases, the system might simply kill your app process without calling the activity's final onDestroy() callback. This is why it is important to use onStop() to release resources that might leak memory.

Note: If we forget to add the AuthStateListener to our FirebaseAuth object, our AuthStateListener will not work!

Now, let's run our app to see our AuthStateListener in action. If everything was configured properly, our app should bring the user to the MainActivity once their account is created

If we restart the app and navigate to CreateAccountActivity, you will notice that our app immediately brings us back to the MainActivity! When the AuthStateListener is added in the onStart() method, it checks to see if a user has already been authenticated. Because the user we created moments ago was logged in automatically, the AuthStateListener brings them to the MainActivity.

In the next lesson we'll learn how to log the current user out so we can continue to build our CreateAccountActivity, and see our new features in action.


Example GitHub Repo for MyRestaurants

Terminology


  • Stack: A collection of activities users interact with when performing a certain action with an application. The activities are arranged in a stack, ordered by when they were opened.

  • Intent flags: Additional information that can be optionally included with an intent that control how the specific intent is handled.

  • AuthStateListener: Part of an interface that listens to changes in the current AuthState. When there is a change (ie. a user becomes authenticated or signs out), this interface triggers the onAuthStateChanged() method automatically.

Tips


  • Android calls the onStart() method every time an activity becomes visible (whether being restarted or created for the first time). When an activity receives a call to the onStop() method, it's no longer visible and should release almost all resources that aren't necessary while the activity is not in use.

Examples


Additional Resources


  • Check out the Android Documentation for more information on finish() and other methods to halt activities.

  • For more information on tasks and the back stack, check out the Android Developer's guides article on Tasks and Back Stack.

  • For a list of all available intent flags, check out the documentaiton for the setFlags() method here.