https://firebase.google.com/docs/auth/android/start

https://github.com/gerardfp/auhentication

Selecciona "Navigation Drawer Activity" como plantilla para la activity principal.

Elimina el Boilerplate generado por el asistente:

  1. Elimina el package ui:


  2. Borra las Top-Level destinations del fichero MainActivity.java:


  3. Borra los archivos de layout de los fragments:


  4. Borra todos los item del fichero menu/activity_main_drawer.xml
  5. Borra todos los fragment del fichero navigation/mobile_navigation.xml

    Borra tambien el atributo app:startDestination="@+id/nav_home"

Connecta la app a Firebase

  1. Selecciona Herramientas > Firebase para abrir la ventana del Asistente.
  2. Haz clic en Authentication, y luego en "Email and password authentication".

  1. Haz clic en "Connect to Firebase"


    Selecciona una cuenta de Google y haz click en

    En la ventana que aparece selecciona un proyecto existente o crea uno nuevo. Haz click en "Connect to Firebase"

Añade la Autenticacion con Firebase

  1. Haz clic en "Add Firebase Authentication to your app"

Haz clic en "Accept changes"

  1. Ve a la Consola de Firebase: https://console.firebase.google.com

Accede al proyecto

  1. Ve al apartado "Authentication"


  2. Haz clic en "Setup sign-in method"


  3. Activa "Email/Password" y "Google"


  4. Añade la siguiente dependencia en el archivo build.gradle (Module: app)
implementation 'com.google.android.gms:play-services-auth:17.0.0'

Las dependencias del fichero build.gradle (Module: app) deberían quedar así:

implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'com.google.android.material:material:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.navigation:navigation-fragment:2.1.0'
implementation 'androidx.navigation:navigation-ui:2.1.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
implementation 'com.google.firebase:firebase-auth:19.2.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'com.google.android.gms:play-services-auth:17.0.0'

Destinaciones:

SignInFragment, SignOutFragment, RegisterFragment, ProfileFragment y HomeFragment

Archivos de Layout:

fragment_sign_in.xml:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   android:layout_width="match_parent"
   android:layout_height="match_parent">

   <LinearLayout
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:orientation="vertical"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent"
       android:gravity="center_vertical|center_horizontal"
       android:id="@+id/signInForm">

       <TextView
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:text="Login with email/password"/>
       <EditText
           android:id="@+id/emailEditText"
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:hint="Email"/>
       <EditText
           android:id="@+id/passwordEditText"
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:hint="Password"/>
       <Button
           android:id="@+id/emailSignInButton"
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:text="Sign In"/>
       <TextView
           android:id="@+id/gotoCreateAccountTextView"
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:text="Create account here"/>

       <View
           android:layout_width="match_parent"
           android:layout_height="1dp"
           android:layout_margin="48dp"
           android:background="@color/colorPrimaryDark"/>

       <TextView
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:text="Login with your Google account"/>
       <com.google.android.gms.common.SignInButton
           android:id="@+id/googleSignInButton"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"/>
   </LinearLayout>
  
   <ProgressBar
       android:id="@+id/signInProgressBar"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

fragment_register.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".RegisterFragment"
   android:orientation="vertical">

   <EditText
       android:id="@+id/emailEditText"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:hint="Email"/>

   <EditText
       android:id="@+id/passwordEditText"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:hint="Password"/>

   <Button
       android:id="@+id/registerButton"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:text="Create account"/>

</LinearLayout>

fragment_profile.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">

   <ImageView
       android:id="@+id/photoImageView"
       android:layout_width="254dp"
       android:layout_height="wrap_content"/>
   <TextView
       android:id="@+id/displayNameTextView"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"/>
   <TextView
       android:id="@+id/emailTextView"
       android:layout_width="match_parent"
       android:layout_height="wrap_content" />
</LinearLayout>

Menu drawer:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   tools:showIn="navigation_view">

   <group android:checkableBehavior="single">
       <item
           android:id="@+id/homeFragment"
           android:title="Home" />
       <item
           android:id="@+id/profileFragment"
           android:title="Perfil" />
       <item
           android:id="@+id/signOutFragment"
           android:title="Cerrar sesion" />
   </group>

</menu>

Añadir el NavController en cada fragment:

public class XXXXXXFragment extends Fragment {

   NavController navController;   // <-----------------

   public XXXXXXFragment() {}

   @Override
   public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
       return inflater.inflate(R.layout.fragment_XXXXXX, container, false);
   }

   @Override
   public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
       super.onViewCreated(view, savedInstanceState);

       navController = Navigation.findNavController(view);  // <-----------------
   }
}

SignInFragment.java:

Hacer que el enlace a "Create account here" lleve al RegisterFragment

public class SignInFragment extends Fragment {

   NavController navController;

   // ...

   @Override
   public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
       super.onViewCreated(view, savedInstanceState);

       navController = Navigation.findNavController(view);

       view.findViewById(R.id.gotoCreateAccountTextView).setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View view) {
               navController.navigate(R.id.registerFragment);
           }
       });
   }
}

activity_main_drawer.xml:

Añadir los siguientes items al menu:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   tools:showIn="navigation_view">

   <group android:checkableBehavior="single">
       <item
           android:id="@+id/homeFragment"
           android:title="@string/menu_home" />
       <item
           android:id="@+id/profileFragment"
           android:title="@string/menu_home" />
       <item
           android:id="@+id/signOutFragment"
           android:title="@string/menu_home" />
   </group>

</menu>

Guardar los EditText como variables de clase:

public class RegisterFragment extends Fragment {
   
   private EditText emailEditText, passwordEditText;

   // ...
   
   @Override
   public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
       
       // ...

       emailEditText = view.findViewById(R.id.emailEditText);
       passwordEditText = view.findViewById(R.id.passwordEditText);
   }
}

Añadir el listener al boton registerButton:

public class RegisterFragment extends Fragment {

   // ...
   private Button registerButton;


   @Override
   public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
       // ...

       registerButton = view.findViewById(R.id.registerButton);

       registerButton.setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View view) {
               crearCuenta();
           }
       });
   }

   private void crearCuenta() {

   }
}

crearCuenta()

El método crearCuenta() primero valida que se ha introducido un Email y Password en los campos de texto.

Luego, llama al método createUserWithEmailAndPassword() con los valores email/password introducidos en ellos.

Cuando se completa la creación del usuario, se ejecuta el método onComplete(). En él miramos si el registro ha tenido éxito o no. Si ha tenido éxito, navegamos al HomeFragment, y si ha habido algun error, lo mostramos al usuario con un SnackBar.

Es interesante desactivar el boton de registro mientras se está realizando el registro en Firebase, para que el usuario no le de dos veces. Una vez termine el registro, se vuelve a activar el boton (Otra opción sería ocultar el formulario con setVisibility(View.GONE), y mostrar con setVisibility(View.VISIBLE) un ProgressBar. Una vez termine el registro habría que mostrar de nuevo el formulario y ocultar el ProgressBar).

El primer paso es obtener el objeto FirebaseAuth que contiene todos los métodos de la API. Normalmente lo declaramos como variable de clase y lo inicializamos de primeras en el método onViewCreated().

public class RegisterFragment extends Fragment {

   private FirebaseAuth mAuth;

   // ...

   @Override
   public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
       // ...
       mAuth = FirebaseAuth.getInstance();
   }
}

Los métodos crearCuenta(), actualizarUI() y validarFormulario() quedan así:

private void crearCuenta() {
   if (!validarFormulario()) {
       return;
   }

   registerButton.setEnabled(false);

   mAuth.createUserWithEmailAndPassword(emailEditText.getText().toString(), passwordEditText.getText().toString())
           .addOnCompleteListener(requireActivity(), new OnCompleteListener<AuthResult>() {
               @Override
               public void onComplete(@NonNull Task<AuthResult> task) {
                   if (task.isSuccessful()) {
                       actualizarUI(mAuth.getCurrentUser());
                   } else {
                       Snackbar.make(requireView(), "Error: " + task.getException(), Snackbar.LENGTH_LONG).show();

                   }
                   registerButton.setEnabled(true);
               }
           });

}

private void actualizarUI(FirebaseUser currentUser) {
   if(currentUser != null){
       navController.navigate(R.id.homeFragment);
   }
}

private boolean validarFormulario() {
   boolean valid = true;

   if (TextUtils.isEmpty(emailEditText.getText().toString())) {
       emailEditText.setError("Required.");
       valid = false;
   } else {
       emailEditText.setError(null);
   }

   if (TextUtils.isEmpty(passwordEditText.getText().toString())) {
       passwordEditText.setError("Required.");
       valid = false;
   } else {
       passwordEditText.setError(null);
   }

   return valid;
}

Hacer los findViewById():

public class SignInFragment extends Fragment {
   
   // ...
   
   private EditText emailEditText, passwordEditText;
   private Button emailSignInButton;
   private LinearLayout signInForm;
   private ProgressBar signInProgressBar;


   // ...
   
   @Override
   public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
       // ...
       emailEditText = view.findViewById(R.id.emailEditText);
       passwordEditText = view.findViewById(R.id.passwordEditText);
       emailSignInButton = view.findViewById(R.id.emailSignInButton);
       signInForm = view.findViewById(R.id.signInForm);
       signInProgressBar = view.findViewById(R.id.signInProgressBar);
   }
}

Añadir el listener al botón emailSignIn:

public class SignInFragment extends Fragment {

   // ...

   @Override
   public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
       
       // ...

       emailSignInButton.setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View view) {
               accederConEmail();
           }
       });
   }

   private void accederConEmail() {

   }
}

Obtener el manejador de la Auth API:

public class SignInFragment extends Fragment {

   // ...
   private FirebaseAuth mAuth;


   @Override
   public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
       
       // ...

       mAuth = FirebaseAuth.getInstance();

       // ...
   }
}

El método accederConEmail() llamará al método signInWithEmailAndPassword() pasándole el email/password introducidos. Una vez finalizado el proceso de SignIn, se ejecuta el método onComplete() del listener. En él, miramos si se ha hecho el SignIn correctamente o no. Si ha tenido éxito, llamamos a actualizarUI(), para navegar al HomeFragment. I si ha habido algun error lo mostramos en un SnackBar.

Antes de iniciar el SignIn hemos ocultado el formulario de acceso y mostrado el ProgressBar. Cuando finaliza el SIgnIn, volvemos a mostrar el formulario y ocultar el ProgressBar.

Los métodos accederConEmail() y actualizarUI() quedan así:

public class SignInFragment extends Fragment {
   // ...

   private void accederConEmail() {
       signInForm.setVisibility(View.GONE);
       signInProgressBar.setVisibility(View.VISIBLE);

       mAuth.signInWithEmailAndPassword(emailEditText.getText().toString(), passwordEditText.getText().toString())
               .addOnCompleteListener(requireActivity(), new OnCompleteListener<AuthResult>() {
                   @Override
                   public void onComplete(@NonNull Task<AuthResult> task) {
                       if (task.isSuccessful()) {
                           actualizarUI(mAuth.getCurrentUser());
                       } else {
                           Snackbar.make(requireView(), "Error: " + task.getException(), Snackbar.LENGTH_LONG).show();
                       }
                       signInForm.setVisibility(View.VISIBLE);
                       signInProgressBar.setVisibility(View.GONE);
                   }
               });
   }

   private void actualizarUI(FirebaseUser currentUser) {
       if(currentUser != null){
           navController.navigate(R.id.homeFragment);
       }
   }
}

FindViewById y OnClickListener:

public class SignInFragment extends Fragment {

   private SignInButton googleSignInButton;

   @Override
   public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {

       googleSignInButton = view.findViewById(R.id.googleSignInButton);

       googleSignInButton.setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View view) {
               accederConGoogle();
           }
       });
   }

   private void accederConGoogle() {
      
   }

}

El acceso con cuenta de Google se realiza en dos pasos: (1) Acceder con la cuenta de Google y (2) Usar las credenciales de esta cuenta para acceder a Firebase.

  1. Acceder con la cuenta de Google:
private void accederConGoogle() {
   GoogleSignInClient googleSignInClient = GoogleSignIn.getClient(requireActivity(), new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
           .requestIdToken(getString(R.string.default_web_client_id))
           .requestEmail()
           .build());

   startActivityForResult(googleSignInClient.getSignInIntent(), 12345);
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
   super.onActivityResult(requestCode, resultCode, data);

   if (requestCode == 12345) {
       try {
         firebaseAuthWithGoogle(GoogleSignIn.getSignedInAccountFromIntent(data).getResult(ApiException.class));
       } catch (ApiException e) {
           Log.e("ABCD", "signInResult:failed code=" + e.getStatusCode());
       }
   }
}

private void firebaseAuthWithGoogle(GoogleSignInAccount acct) {

}

  1. Usar la cuenta de google para acceder a Firebase
private void firebaseAuthWithGoogle(GoogleSignInAccount acct) {
   if(acct == null) return;

   signInProgressBar.setVisibility(View.VISIBLE);
   signInForm.setVisibility(View.GONE);

   mAuth.signInWithCredential(GoogleAuthProvider.getCredential(acct.getIdToken(), null))
           .addOnCompleteListener(requireActivity(), new OnCompleteListener<AuthResult>() {
               @Override
               public void onComplete(@NonNull Task<AuthResult> task) {
                   if (task.isSuccessful()) {
                       Log.e("ABCD", "signInWithCredential:success");
                       actualizarUI(mAuth.getCurrentUser());
                   } else {
                       Log.e("ABCD", "signInWithCredential:failure", task.getException());
                       signInProgressBar.setVisibility(View.GONE);
                       signInForm.setVisibility(View.VISIBLE);
                   }
               }
           });
}

Podemos obtener los datos de la cuenta de Google del usuario (foto, nombre, etc.). Sin embargo para los usuarios que acceden con email/password, el usuario nos tendría que dar esa información y guardarla en la base de datos.

Para obtener los datos del usuario actual, usamos los getters del FirebaseUser que nos devuelve FirebaseAuth.getInstance().getCurrentUser().

El código del ProfileFragment, quedaría así (a falta de guardar la info del usuario en la base de datos):

public class ProfileFragment extends Fragment {

   ImageView photoImageView;
   TextView displayNameTextView, emailTextView;

   public ProfileFragment() {}


   @Override
   public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
       return inflater.inflate(R.layout.fragment_profile, container, false);
   }

   @Override
   public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
       super.onViewCreated(view, savedInstanceState);

       photoImageView = view.findViewById(R.id.photoImageView);
       displayNameTextView = view.findViewById(R.id.displayNameTextView);
       emailTextView = view.findViewById(R.id.emailTextView);

       FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();

       if(user != null){
           displayNameTextView.setText(user.getDisplayName());
           emailTextView.setText(user.getEmail());

           Glide.with(requireView()).load(user.getPhotoUrl()).into(photoImageView);
       }
   }
}

Para mostrar el perfil del usuario en el NavigationView (Drawer), tendremos que poner el código en la MainActivity, ya que es en ella donde esta el NavigationView.

Añade el siguiente código en el metodo onCreate():

View header = navigationView.getHeaderView(0);
final ImageView photo = header.findViewById(R.id.photoImageView);
final TextView name = header.findViewById(R.id.displayNameTextView);
final TextView email = header.findViewById(R.id.emailTextView);

FirebaseAuth.getInstance().addAuthStateListener(new FirebaseAuth.AuthStateListener() {
   @Override
   public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) {
       FirebaseUser user = firebaseAuth.getCurrentUser();

       if(user != null){
           Glide.with(MainActivity.this)
                   .load(FirebaseAuth.getInstance().getCurrentUser().getPhotoUrl().toString())
                   .circleCrop()
                   .into(photo);
           name.setText(FirebaseAuth.getInstance().getCurrentUser().getDisplayName());
           email.setText(FirebaseAuth.getInstance().getCurrentUser().getEmail());
       }
   }
});

El SignOutFragment, es solamente un fragment "de paso" en el que se cierra la sesión, tanto de Google como de Firebase, y se vuelve al SignInFragment:

public class SignOutFragment extends Fragment {


   public SignOutFragment() { }

   @Override
   public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
       return inflater.inflate(R.layout.fragment_sign_out, container, false);
   }

   @Override
   public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
       super.onViewCreated(view, savedInstanceState);

       GoogleSignIn.getClient(requireActivity(), new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
               .requestIdToken(getString(R.string.default_web_client_id))
               .build()).signOut();

       FirebaseAuth.getInstance().signOut();

       Navigation.findNavController(view).navigate(R.id.signInFragment);
   }
}