Welcome to the third part of the Firebase Authentication series. In the previous part we built the layout for the app. In this part we will connect the xml files with java code to make it possible for users to register, log in and log out. To accomplish this, fragments will be used for the registering and login screen, along with a loading dialog so the users know that the app is processing and not freezing when they are registering, logging in or logging out. The logged in state will be managed from the MainActivity class.
Create a new Android Resource Directory and name it Fragments, and add two java classes named LoginFragment and RegisterFragment in that directory. Create another directory and name it DialogFragments, and add a class named LoadingDialog. Lastly, add a class in the same directory where MainActivity is located, and name it AuthActivity. Your directory tree should now look like this:
Don't worry about the classes outside of the red rectangles yet, they will be created in the following parts. The LoginFragment and RegisterFragment need to extend Fragment, leave them as empty fragments for the moment, we will come back to them after we are done with AuthActivity and the LoadingDialog.
public class LoginFragment extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View parentView = inflater.inflate(R.layout.fragment_login, container, false);
return parentView;
}
}
public class RegisterFragment extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View parentView = inflater.inflate(R.layout.fragment_register, container, false);
return parentView;
}
}
AuthActivity.java
package com.frogitecture.authenticatedgoose;
import android.os.Bundle;
import com.frogitecture.authenticatedgoose.Fragments.LoginFragment;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
public class AuthActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_auth);
if (savedInstanceState != null) {
return;
}
LoginFragment loginFragment = new LoginFragment();
getSupportFragmentManager().beginTransaction().add(R.id.container, loginFragment).commit();
}
public void changeFragment(Fragment fragment) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.container, fragment);
transaction.addToBackStack(null);
transaction.commit();
}
}
The first thing we do here is to connect the java class with the xml file we created in the last part, activity_auth.xml, with the line setContentView(R.layout.activity_auth)
. Then we check if savedInstanceState is not null, because if it isn't, we don't need to initialize anything again. The two last lines in the onCreate method creates an object of the LoginFragment and puts it in the container. This means that when you open the app, the first thing you will see is the login screen, if you aren't logged in already. We will check if a user is logged in already in MainActivity soon.
The method changeFragment will be called from either LoginFragment or RegisterFragment when a user want to switch from login to register or vice versa.
The loading dialog has a gif in it, to display gifs, or images in general, I recommend using the library Glide. You can learn more about Glide here if you're not familiar with the library. To be able to use Glide it's required to add the dependency in build.gradle:
dependencies {
...
implementation 'com.google.firebase:firebase-auth:19.3.1'
// Add these lines
implementation 'com.github.bumptech.glide:glide:4.10.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.10.0'
}
Before we create the Java class for the loading dialog, we need to create an xml file that will be inflated in that class. Go ahead and create a new xml file in res/layout and name it dialog_loading.xml.
dialog_loading.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="10dp">
<ImageView
android:id="@+id/image"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="center"
android:src="@drawable/loadinggoose" />
<TextView
android:layout_marginTop="10dp"
android:id="@+id/message"
android:textSize="16sp"
android:textStyle="bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"/>
</LinearLayout>
This is going to show a gif located in res/drawable, with the name loadinggoose, with a TextView under it specifying what's currently loading. Here's the gif that I use for the loading screen, feel free to download it:
LoadingDialog.java
package com.frogitecture.authenticatedgoose.DialogFragments;
import android.app.Dialog;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.RequestOptions;
import com.frogitecture.authenticatedgoose.R;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
public class LoadingDialog extends DialogFragment {
private String message;
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
LayoutInflater inflater = requireActivity().getLayoutInflater();
View view = inflater.inflate(R.layout.dialog_loading, null);
ImageView imageView = view.findViewById(R.id.image);
TextView textView = view.findViewById(R.id.message);
Glide.with(getActivity())
.asGif()
.load(R.drawable.loadinggoose)
.apply(new RequestOptions().diskCacheStrategy(DiskCacheStrategy.NONE))
.into(imageView);
textView.setText(message);
builder.setView(view);
return builder.create();
}
public void setMessage(String message) {
this.message = message;
}
}
The loading dialog will show up every time a user is registering or logging in. It's making use of the AlertDialog class and has a gif and a text that can be changed to tell the user what's going on, like "Registering.." or "Logging in...".
RegisterFragment.java
package com.frogitecture.authenticatedgoose.Fragments;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import com.frogitecture.authenticatedgoose.AuthActivity;
import com.frogitecture.authenticatedgoose.DialogFragments.LoadingDialog;
import com.frogitecture.authenticatedgoose.R;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.auth.AuthResult;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.auth.UserProfileChangeRequest;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
public class RegisterFragment extends Fragment {
private LoadingDialog loadingDialog;
private TextView loginTxt;
private Button registerBtn;
private EditText displayNameTxt;
private EditText emailTxt;
private EditText passwordTxt;
private FirebaseAuth firebaseAuth;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View parentView = inflater.inflate(R.layout.fragment_register, container, false);
loadingDialog = new LoadingDialog();
loginTxt = parentView.findViewById(R.id.already_registered_txt);
displayNameTxt = parentView.findViewById(R.id.display_name);
emailTxt = parentView.findViewById(R.id.email);
passwordTxt = parentView.findViewById(R.id.password);
registerBtn = parentView.findViewById(R.id.register_btn);
firebaseAuth = FirebaseAuth.getInstance();
loginTxt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
((AuthActivity) getActivity()).changeFragment(new LoginFragment());
}
});
registerBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
loadingDialog.setMessage("Registering...");
loadingDialog.show(getActivity().getSupportFragmentManager(), "Registering");
final String displayName = displayNameTxt.getText().toString();
String email = emailTxt.getText().toString();
String password = passwordTxt.getText().toString();
register(displayName, email, password);
}
});
return parentView;
}
private void register(final String displayName, String email, String password) {
firebaseAuth.createUserWithEmailAndPassword(email, password)
.addOnCompleteListener(getActivity(), new OnCompleteListener<AuthResult>() {
@Override
public void onComplete(@NonNull Task<AuthResult> task) {
if (task.isSuccessful()) {
FirebaseUser user = firebaseAuth.getCurrentUser();
UserProfileChangeRequest profileChangeRequest = new UserProfileChangeRequest.Builder()
.setDisplayName(displayName)
.build();
user.updateProfile(profileChangeRequest)
.addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
loadingDialog.dismiss();
getActivity().finish();
startActivity(new Intent(getActivity(), MainActivity.class));
}
});
} else {
Toast.makeText(getActivity(), "Authentication failed.",
Toast.LENGTH_SHORT).show();
}
loadingDialog.dismiss();
}
});
}
}
We got variables for every dynamic text and buttons, and a LoadingDialog object. The most important object here is FirebaseAuth, which we initialize with firebaseAuth = FirebaseAuth.getInstance()
which gives us an instance of the class corresponding to the default FirebaseApp instance.
A click listener is attached to the text saying "Already registered? Click here to login" so users can switch between the fragments easily. When clicked, the method changeFragment in AuthActivity is called will the argument new LoginFragment(), and the fragments change.
The following code is the heart of the fragment, the register button. First thing that happens when a user click on the register button is that the loading dialog pops up. Well, if this was a real app, you would probably want to check if the email is valid and set some demands on the display name and password before showing the loading dialog, but we assume that the users enter rational information here. Display name, email and password is passed to register(displayName, email, password)
, email and password is then passed to the method
firebaseAuth.createUserWithEmailAndPassword(email, password).addOnCompleteListener(getActivity(), new OnCompleteListener<AuthResult>()
which does exactly what is says, creating a new account with email and password. We also attach a complete listener to this so we can wait for the account to be created and proceed when we know it's done. The method
public void onComplete(@NonNull Task<AuthResult> task)
is used for this, and we can check if everything went fine with the argument variable task
by checking if task.isSuccessful()
is true. If it is successful, we should update the users display name that they entered when they registered. The user is automatically signed in after a successful registering, and a user object can be retrieved by
FirebaseUser user = firebaseAuth.getCurrentUser();
In order to change the display name, we need to create a UserProfileChangeRequest object, set the display name, and then pass it to user.updateProfile(), as such:
UserProfileChangeRequest profileChangeRequest = new UserProfileChangeRequest.Builder()
.setDisplayName(displayName)
.build();
user.updateProfile(profileChangeRequest)
.addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
loadingDialog.dismiss();
getActivity().finish();
startActivity(new Intent(getActivity(), MainActivity.class));
}
});
updateProfile is telling us when it's completed in the same way as createWithEmailAndPassword did, so in the onComplete method in there we can dismiss the loading dialog, finish the activity and start MainActivity.
LoginFragment.java
package com.frogitecture.authenticatedgoose.Fragments;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.frogitecture.authenticatedgoose.AuthActivity;
import com.frogitecture.authenticatedgoose.DialogFragments.LoadingDialog;
import com.frogitecture.authenticatedgoose.MainActivity;
import com.frogitecture.authenticatedgoose.R;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.auth.AuthResult;
import com.google.firebase.auth.FirebaseAuth;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
public class LoginFragment extends Fragment {
private LoadingDialog loadingDialog;
private FirebaseAuth firebaseAuth;
private TextView emailTxt;
private TextView passwordTxt;
private TextView registerTxt;
private Button signInBtn;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View parentView = inflater.inflate(R.layout.fragment_login, container, false);
loadingDialog = new LoadingDialog();
firebaseAuth = FirebaseAuth.getInstance();
emailTxt = parentView.findViewById(R.id.email_txt);
passwordTxt = parentView.findViewById(R.id.password_txt);
registerTxt = parentView.findViewById(R.id.not_registered_txt);
signInBtn = parentView.findViewById(R.id.login_btn);
registerTxt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
((AuthActivity)getActivity()).changeFragment(new RegisterFragment());
}
});
signInBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String email = emailTxt.getText().toString();
String password = passwordTxt.getText().toString();
loadingDialog.setMessage("Signing in...");
loadingDialog.show(getActivity().getSupportFragmentManager(),"Signing In");
signIn(email, password);
}
});
return parentView;
}
private void signIn(String email, String password) {
firebaseAuth.signInWithEmailAndPassword(email, password)
.addOnCompleteListener(getActivity(), new OnCompleteListener<AuthResult>() {
@Override
public void onComplete(@NonNull Task<AuthResult> task) {
loadingDialog.dismiss();
if (task.isSuccessful()) {
getActivity().finish();
startActivity(new Intent(getActivity(), MainActivity.class));
} else {
Toast.makeText(getActivity(), "Authentication failed.", Toast.LENGTH_SHORT).show();
}
}
});
}
}
The setup here is almost identical. The difference is that when a user click on the registering textview, fragments are switched from LoginFragment to RegisterFragment instead, and the sign in button signs a user in instead of registering. To sign in, the email and password variables are passed to
firebaseAuth.signInWithEmailAndPassword(email, password).addOnCompleteListener(getActivity(), new OnCompleteListener<AuthResult>() {
When completed, the loading dialog is dismissed and MainActivity is started if successful.
MainActivity.java
package com.frogitecture.authenticatedgoose;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.bumptech.glide.Glide;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
public class MainActivity extends AppCompatActivity {
private FirebaseAuth firebaseAuth;
private FirebaseUser firebaseUser;
private ImageView imageView;
private TextView displayNameTxt;
private TextView useridTxt;
private TextView emailTxt;
private TextView verifiedTxt;
private TextView sendVerificationTxt;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
firebaseAuth = FirebaseAuth.getInstance();
if (firebaseAuth.getCurrentUser() == null) {
finish();
startActivity(new Intent(this, AuthActivity.class));
return;
} else {
firebaseUser = firebaseAuth.getCurrentUser();
}
imageView = findViewById(R.id.image);
useridTxt = findViewById(R.id.uid_txt);
displayNameTxt = findViewById(R.id.edittext);
emailTxt = findViewById(R.id.email_txt);
verifiedTxt = findViewById(R.id.verified_txt);
sendVerificationTxt = findViewById(R.id.send_verification_txt);
Uri profilePicture = firebaseUser.getPhotoUrl();
String uid = firebaseUser.getUid();
String displayName = firebaseUser.getDisplayName();
final String email = firebaseUser.getEmail();
String verifiedEmail = "No";
if (firebaseUser.isEmailVerified()) {
verifiedEmail = "Yes";
sendVerificationTxt.setVisibility(View.GONE);
}
Glide.with(this).load(profilePicture).into(imageView);
useridTxt.setText(uid);
displayNameTxt.setText(displayName);
emailTxt.setText(email);
verifiedTxt.setText(verifiedEmail);
Toolbar toolbar = findViewById(R.id.toolbar);
toolbar.setTitle("Authenticated Goose");
setSupportActionBar(toolbar);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.change_profile_picture:
break;
case R.id.change_display_name:
break;
case R.id.change_email:
break;
case R.id.change_password:
break;
case R.id.delete_account:
break;
case R.id.sign_out:
FirebaseAuth.getInstance().signOut();
finish();
startActivity(new Intent(MainActivity.this, AuthActivity.class));
break;
}
return super.onOptionsItemSelected(item);
}
}
In MainActivity, we first create an instance of FirebaseAuth, and use this to check if a user is logged in or not. To check if a user is logged in we look at the variable firebaseAuth.getCurrentUser()
, if it's null it means that the user is not logged in, and we can redirect the users to AuthActivity. Otherwise a user is logged in, and we store the users information in the variable firebaseUser. Then we just connect our variables with the xml elements and update them with the information stored in the firebaseUser object. Pay attention to these lines:
Uri profilePicture = firebaseUser.getPhotoUrl();
String uid = firebaseUser.getUid();
String displayName = firebaseUser.getDisplayName();
final String email = firebaseUser.getEmail();
String verifiedEmail = "No";
if (firebaseUser.isEmailVerified()) {
verifiedEmail = "Yes";
sendVerificationTxt.setVisibility(View.GONE);
}
See how easy it is for us to get information from the FirebaseUser object?
Next, we initialize the toolbar. Every option in the toolbar will be left out for the moment except for the option to sign out. Each option will get its own part in the series. To sign out, all we have to do is to call
FirebaseAuth.getInstance().signOut();
and the user is signed out. Immediately after that, we redirect the user to AuthActivity.
That's it for this part! In the next part I will show you how to reset a lost, or hacked, password.
Author
2020-06-20
Changing a display name in Android Studio with firebase auth
Changing email in Android with Firebase Authentication
Changing a password in Android with Firebase Authentication
Deleting an account in Android with Firebase Authentication