La autenticación es necesaria en una aplicación móvil para poder garantizar la seguridad de la información y asegurarse de que solo los usuarios autorizados tengan acceso a ella. Puede ser necesaria para permitir la personalización de la experiencia de usuario y la recopilación de datos sobre el uso de la aplicación.
En este artículo veremos el proceso de integración del servicio de Autenticación de Firebase en una aplicación Flutter y los beneficios que brinda a los programadores.
¿Qué es el servicio de Autenticación de Firebase?
Firebase Authentication es un funcionalidad que proporciona una forma segura de autenticar a los usuarios en una aplicación. Permite a los desarrolladores autenticar a los usuarios mediante correo electrónico y contraseña, teléfono, proveedores de terceros como Google, Facebook, Twitter, entre otros.
Proporciona una interfaz fácil de usar para administrar las cuentas de usuario. Con Firebase Authentication, los desarrolladores pueden concentrarse en la funcionalidad principal de su aplicación en lugar de preocuparse por la seguridad de la autenticación.
Además de las características mencionadas anteriormente, Firebase Authentication también ofrece las siguientes funcionalidades:
Manejo de sesiones: permite mantener a los usuarios autenticados mientras usan la aplicación y también permite cerrar sesión de forma segura cuando sea necesario.
Verificación de correo electrónico: permite a los usuarios verificar su correo electrónico mediante un enlace de verificación enviado a su correo electrónico. Esto ayuda a prevenir la creación de cuentas fraudulentas.
Restablecimiento de contraseña: permite a los usuarios restablecer sus contraseñas mediante un enlace de restablecimiento enviado a su correo electrónico.
Protección contra ataques de fuerza bruta: ayuda a proteger contra ataques automatizados que intentan adivinar las contraseñas mediante la implementación de medidas de seguridad como el bloqueo de cuentas después de varios intentos fallidos de inicio de sesión.
Integración con Firebase Realtime Database y Firebase Storage: permite a los desarrolladores controlar el acceso a la base de datos y al almacenamiento en la nube en función del estado de la autenticación del usuario.
Integración con Google Sign-In, Facebook Login, Twitter Login, entre otros: permite a los usuarios iniciar sesión con sus cuentas de proveedores de terceros existentes en lugar de crear una nueva cuenta.
Para más información puede visitar la documentación oficial ofrecida por Firebase.
Integración de Firebase Authentication con Flutter
Firebase Authentication se puede integrar fácilmente con aplicaciones desarrolladas en Flutter mediante el uso del paquete oficial de Firebase. El paquete firebase_auth proporciona una interfaz de programación de aplicaciones (API) para interactuar con el servicio de Firebase Authentication.
Para comenzar, primero se debe agregar el paquete firebase_auth. Esto se puede hacer agregando la dependencia correspondiente en el archivo pubspec.yaml y luego ejecutando flutter packages get en la línea de comandos.
Una vez agregado el paquete, se puede utilizar el método FirebaseAuth.instance para obtener una instancia del objeto de autenticación de Firebase. Con esta instancia, se pueden realizar acciones como iniciar sesión, registrarse, verificar el estado de la sesión actual y cerrar sesión. También se pueden agregar escuchadores (listeners) para detectar cambios en el estado de la sesión, como cuando un usuario inicia sesión o cierra sesión.
Es importante mencionar que antes de utilizar Firebase Authentication en una aplicación de Flutter, es necesario configurar la aplicación en la plataforma de Firebase y agregar las credenciales correspondientes en el archivo de configuración de la aplicación.
Ejemplo de implementación del servicio de Autenticación de Firebase con Flutter Bloc
En el siguiente ejemplo llevaremos a cabo una aplicación Flutter que permitirá Registrarse, Iniciar sesión, Recuperar Contraseña y Cerrar Sesión, teniendo como proveedor un usuario y contraseña. En artículos posteriores analizaremos estas acciones teniendo como proveedores de acceso como Google y Apple.
Primeros necesitaremos agregar y habilitar el proveedor de acceso por correo electrónico/contraseña que nos proporciona Firebase.
Se presiona el botón “Agregar proveedor nuevo” y seleccionamos los proveedores que necesitemos. En esta imagen se encuentran seleccionados los proveedores adicionales Google y Apple, pero estos los analizaremos en futuras publicaciones.
Para el manejo de estados emplearemos el paquete flutter_bloc. Si no conoces al respecto, te recomendamos leer nuestro artículo Tutorial de Flutter Bloc para el manejo de estados.
A continuación, presentamos las pantallas de la aplicación relacionadas con el proceso de autenticación.
La estructura de archivos del proyecto que se relaciona con la autenticación, registro y recuperación de contraseña queda de la siguiente forma:
Es decir, se han creado tres ficheros para Bloc y las tres pantallas presentadas anteriormente.
En el fichero auth_state.dart se establecieron los estados: Loading, Authenticated, UnAuthenticated, PassRecovered, AuthError que extienden de la clase abstracta AuthState. Para el caso del estado Authenticated tenemos un objeto UserCredential como parámetro de la clase que pertenece al paquete firebase_auth, con este manejamos toda la información del usuario autenticado.
part of 'auth_bloc.dart';
abstract class AuthState {
List<Object?> get props => [];
}
class Loading extends AuthState {
@override
List<Object?> get props => [];
}
class Authenticated extends AuthState {
final UserCredential user;
Authenticated(this.user);
@override
List<Object?> get props => [user];
}
class UnAuthenticated extends AuthState {
@override
List<Object?> get props => [];
}
class PassRecovered extends AuthState {
@override
List<Object?> get props => [];
}
class AuthError extends AuthState {
final String error;
AuthError(this.error);
@override
List<Object?> get props => [error];
}
En el fichero auth_event.dart se definieron los eventos SignInRequested, SignUpRequested, SignOutRequested, PassRecoverRequested que extienden de la clase abstracta AuthState. Para los eventos SignInRequested, SignUpRequested tenemos como parámetros: correo electrónico y contraseña; mientras que para el evento PassRecoverRequested, contamos con el parámetro correo electrónico.
part of 'auth_bloc.dart';
abstract class AuthEvent {
List<Object> get props => [];
}
class SignInRequested extends AuthEvent {
final String email;
final String password;
SignInRequested({
required this.email,
required this.password,
});
}
class SignUpRequested extends AuthEvent {
final String email;
final String password;
SignUpRequested({required this.email, required this.password});
}
class SignOutRequested extends AuthEvent {}
class PassRecoverRequested extends AuthEvent {
final String email;
PassRecoverRequested({
required this.email,
});
}
En el fichero auth_bloc.dart se escuchan los eventos y emiten los estados correspondientes en base al procesamiento que se realice.
import 'package:bloc/bloc.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:octa_app/repository/auth_repository.dart';
part 'auth_state.dart';
part 'auth_event.dart';
class AuthBloc extends Bloc<AuthEvent, AuthState> {
final AuthRepository authRepository;
AuthBloc({required this.authRepository}) : super(UnAuthenticated()) {
on<SignInRequested>((event, emit) async {
emit(Loading());
try {
UserCredential user = await authRepository.signIn(
email: event.email,
password: event.password,
);
emit(Authenticated(user));
} catch (e) {
emit(AuthError(e.toString()));
emit(UnAuthenticated());
}
});
on<SignUpRequested>((event, emit) async {
emit(Loading());
try {
UserCredential user = await authRepository.signUp(
email: event.email,
password: event.password,
);
emit(Authenticated(user));
} catch (e) {
emit(AuthError(e.toString()));
emit(UnAuthenticated());
}
});
on<SignOutRequested>((event, emit) async {
emit(Loading());
try {
await authRepository.signOut();
emit(UnAuthenticated());
} catch (e) {
emit(AuthError(e.toString()));
}
});
on<PassRecoverRequested>((event, emit) async {
emit(Loading());
try {
await authRepository.resetPass(
email: event.email,
);
emit(PassRecovered());
} catch (e) {
emit(AuthError(e.toString()));
}
});
}
}
Como complemento a la lógica, existe el fichero auth_repository.dart, que gestiona las llamadas a los métodos de la instancia del objeto de autenticación de Firebase Authentication.
Para el registro del usuario emplearemos el método createUserWithEmailAndPassword, la autenticación signInWithEmailAndPassword, cerrar sesión signOut y recuperación de la contraseña resetPass. Adicionalmente se ha agregado la funcionalidad para enviar un correo electrónico de verificación al usuario con el método sendEmailVerification luego de registrarse.
Si se quiere obtener la información del usuario actual podemos llamar al parámetro currentUser.
import 'package:easy_localization/easy_localization.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
class AuthRepository {
final _firebaseAuth = FirebaseAuth.instance;
final BuildContext context;
AuthRepository({required this.context});
Future signUp({
required String email,
required String password,
}) async {
try {
UserCredential userCredential =
await _firebaseAuth.createUserWithEmailAndPassword(
email: email,
password: password,
);
userCredential.user!.sendEmailVerification();
return userCredential;
} on FirebaseAuthException catch (e) {
if (e.code == 'weak-password') {
throw Exception(tr("weak-password"));
} else if (e.code == 'email-already-in-use') {
throw Exception(tr("email-already-in-use"));
} else {
throw Exception(tr("error"));
}
} catch (e) {
throw Exception(e.toString());
}
}
Future signIn({
required String email,
required String password,
}) async {
try {
return await _firebaseAuth.signInWithEmailAndPassword(
email: email,
password: password,
);
} on FirebaseAuthException catch (e) {
if (e.code == 'user-not-found') {
throw Exception(tr("user-not-found"));
} else if (e.code == 'wrong-password') {
throw Exception(tr("wrong-password"));
} else {
throw Exception(tr("user-not-found"));
}
}
}
Future<void> signOut() async {
try {
await _firebaseAuth.signOut();
} catch (e) {
throw Exception(e.toString());
}
}
Future<void> resetPass({
required String email,
}) async {
try {
await _firebaseAuth.sendPasswordResetEmail(email: email);
} on FirebaseAuthException catch (e) {
if (e.code == 'user-not-found') {
throw Exception(tr("user-not-found"));
} else if (e.code == 'invalid-email') {
throw Exception(tr("invalid-email"));
} else {
throw Exception(tr("user-not-found"));
}
}
}
User getCurrentUser() {
return _firebaseAuth.currentUser!;
}
}
Para poder utilizar la lógica Bloc en los widgets de interfaz de usuario, se agregaron en el generador de rutas de la aplicación: un objeto del repositorio de autenticación como parámetro del widget RepositoryProvider y un objeto AuthBloc como parámetro del widget BlocProvider para cada clase relacionada con las pantallas de la aplicación.
Puede visitar la página del paquete flutter_bloc en GitHub para más información sobre widgets Provider.
class AppRouter {
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
...
case RouterPaths.forgotPass:
return PageTransition(
settings: settings,
child: RepositoryProvider(
create: (context) => AuthRepository(context: context),
child: BlocProvider(
create: (context) => AuthBloc(
authRepository:
RepositoryProvider.of<AuthRepository>(context)),
child: const ForgotPassScreen(),
),
),
type: PageTransitionType.bottomToTop,
);
case RouterPaths.home:
return PageTransition(
settings: settings,
child: RepositoryProvider(
create: (context) => AuthRepository(context: context),
child: BlocProvider(
create: (context) => AuthBloc(
authRepository:
RepositoryProvider.of<AuthRepository>(context)),
child: const HomeScreen(),
),
),
type: PageTransitionType.bottomToTop,
);
case RouterPaths.login:
return PageTransition(
settings: settings,
child: RepositoryProvider(
create: (context) => AuthRepository(context: context),
child: BlocProvider(
create: (context) => AuthBloc(
authRepository: RepositoryProvider.of<AuthRepository>(context),
),
child: const LoginScreen(),
),
),
type: PageTransitionType.bottomToTop,
);
case RouterPaths.signUp:
return PageTransition(
settings: settings,
child: RepositoryProvider(
create: (context) => AuthRepository(context: context),
child: BlocProvider(
create: (context) => AuthBloc(
authRepository: RepositoryProvider.of<AuthRepository>(context),
),
child: const SignUpScreen(),
),
),
type: PageTransitionType.bottomToTop,
);
...
}
}
}
A continuación presentamos las pantallas de la aplicación integradas a la lógica establecida.
Registro de Usuario
En la pantalla de registro de usuario, presentamos un formulario con los campos: correo electrónico, contraseña y repetir contraseña. El usuario al presionar el botón “Registrarse”, ejecutará el evento SignUpRequested de la lógica Bloc, que será escuchado por AuthBloc. Seguido se emite el estado Loading, se registra el usuario en Firebase y si no ocurre ningún error se lanzará el estado Authenticated.
on<SignUpRequested>((event, emit) async {
emit(Loading());
try {
UserCredential user = await authRepository.signUp(
email: event.email,
password: event.password,
);
emit(Authenticated(user));
} catch (e) {
emit(AuthError(e.toString()));
emit(UnAuthenticated());
}
});
En el lado de la vista del usuario, clase SignUpScreen, se escucharán los estados con la ayuda del widget BlocConsumer, similar a la unión de los widgets BlocListener y BlocBuilder. Navegándose hacia la pantalla HomeScreen en caso de que el registro sea exitoso, otra alternativa sería redirigir al usuario hacia la pantalla LoginScreen, todo depende del propósito de su aplicación.
import 'package:easy_localization/easy_localization.dart';
import 'package:email_validator/email_validator.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:octa_app/routes/router_paths.dart';
import 'package:octa_app/scenes/auth/bloc/auth_bloc.dart';
import 'package:octa_app/scenes/widgets/text_form_field_widget.dart';
class SignUpScreen extends StatefulWidget {
const SignUpScreen({Key? key}) : super(key: key);
@override
State<SignUpScreen> createState() => _SignUpScreenState();
}
class _SignUpScreenState extends State<SignUpScreen> {
final _formKey = GlobalKey<FormState>();
TextEditingController emailCtrl = TextEditingController();
TextEditingController passCtrl = TextEditingController();
TextEditingController verifyPassCtrl = TextEditingController();
@override
Widget build(BuildContext context) {
return BlocConsumer<AuthBloc, AuthState>(
listener: (context, listener) {
if (listener is AuthError) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(listener.error)));
}
if (listener is Authenticated) {
Navigator.of(context).pushReplacementNamed(RouterPaths.home);
}
},
builder: (context, state) {
return Scaffold(
body: Form(
key: _formKey,
child: Container(
height: MediaQuery.of(context).size.height,
padding: const EdgeInsets.symmetric(horizontal: 26),
child: ListView(
children: [
const Padding(padding: EdgeInsets.only(top: 12)),
Image.asset(
"assets/img/signup.png",
width: MediaQuery.of(context).size.width * 0.8,
height: MediaQuery.of(context).size.height * 0.3,
fit: BoxFit.cover,
),
const Padding(padding: EdgeInsets.only(top: 12)),
Text(
tr("sign_up"),
style: Theme.of(context).textTheme.headline4!.copyWith(
color: Colors.black87, fontWeight: FontWeight.bold),
),
const Padding(padding: EdgeInsets.only(top: 15)),
TextFormFieldWidget(
prefixIcon: Icons.alternate_email_outlined,
controller: emailCtrl,
labelText: tr("email"),
keyboardType: TextInputType.emailAddress,
onChanged: (value) {
setState(() {});
},
obscureText: false,
validator: (value) {
if (value == null ||
value.isEmpty ||
!EmailValidator.validate(value)) {
return tr("valid_email");
}
return null;
},
),
TextFormFieldWidget(
prefixIcon: Icons.lock_outlined,
labelText: tr("pass"),
controller: passCtrl,
keyboardType: TextInputType.text,
onChanged: (value) {
setState(() {});
},
obscureText: true,
validator: (value) {
if (value == null || value.isEmpty) {
return tr("required_field");
}
return null;
},
),
TextFormFieldWidget(
prefixIcon: Icons.lock_outlined,
labelText: tr("verify_pass"),
controller: verifyPassCtrl,
keyboardType: TextInputType.text,
onChanged: (value) {
setState(() {});
},
obscureText: true,
validator: (value) {
if (value == null || value.isEmpty) {
return tr("required_field");
}
if (value != passCtrl.text) {
return tr("same_pass");
}
return null;
},
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 4.0, vertical: 16.0),
child: Wrap(
children: [
Text(
tr("agree_with_terms_1"),
),
Text(
tr("terms_conditions"),
style: const TextStyle(
fontWeight: FontWeight.bold, fontSize: 13),
),
Text(tr("and")),
Text(
tr("privacy_policy"),
style: const TextStyle(
fontWeight: FontWeight.bold, fontSize: 13),
)
],
),
),
ElevatedButton(
onPressed: _signUp, child: Text(tr("sign_up"))),
Padding(
padding: const EdgeInsets.symmetric(vertical: 12.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
color: Colors.black87,
height: 1,
width: MediaQuery.of(context).size.width * 0.37,
),
const Icon(Icons.lock_open),
Container(
color: Colors.black87,
height: 1,
width: MediaQuery.of(context).size.width * 0.37,
),
],
),
),
...
],
),
),
),
);
},
);
}
_signUp() {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
context.read<AuthBloc>().add(SignUpRequested(
email: emailCtrl.text,
password: passCtrl.text,
));
}
}
}
Autenticación
En la pantalla de autenticación, presentamos un formulario con los campos: correo electrónico y contraseña. El usuario al presionar el botón “Iniciar Sesión”, ejecutará el evento SignInRequested de la lógica Bloc, que será escuchado por AuthBloc. Seguido se emite el estado Loading, se autentica el usuario en Firebase y si no ocurre ningún error se lanzará el estado Authenticated.
on<SignInRequested>((event, emit) async {
emit(Loading());
try {
UserCredential user = await authRepository.signIn(
email: event.email,
password: event.password,
);
emit(Authenticated(user));
} catch (e) {
emit(AuthError(e.toString()));
emit(UnAuthenticated());
}
});
En el lado de la vista del usuario, clase LoginScreen, se escucharán los estados con la ayuda del widget BlocConsumer. Si es exitoso el inicio de sesión del usuario, la aplicación navegará hacia la pantalla HomeScreen.
import 'package:easy_localization/easy_localization.dart';
import 'package:email_validator/email_validator.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:octa_app/routes/router_paths.dart';
import 'package:octa_app/scenes/auth/bloc/auth_bloc.dart';
import 'package:octa_app/scenes/widgets/text_form_field_widget.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:octa_app/tools/app_tools.dart';
class LoginScreen extends StatefulWidget {
const LoginScreen({Key? key}) : super(key: key);
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final _formKey = GlobalKey<FormState>();
TextEditingController emailCtrl = TextEditingController();
TextEditingController passCtrl = TextEditingController();
@override
Widget build(BuildContext context) {
return BlocConsumer<AuthBloc, AuthState>(
listener: (context, listener) {
if (listener is AuthError) {
log(listener.error);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
AppTools.parseException(message: listener.error),
),
),
);
}
if (listener is Authenticated) {
Navigator.of(context).pushReplacementNamed(RouterPaths.home);
}
},
builder: (context, state) {
return Scaffold(
body: Form(
key: _formKey,
child: Container(
height: MediaQuery.of(context).size.height,
padding: const EdgeInsets.symmetric(horizontal: 26),
child: ListView(
children: [
const Padding(padding: EdgeInsets.only(top: 16)),
Image.asset(
"assets/img/login.png",
width: MediaQuery.of(context).size.width * 0.8,
height: MediaQuery.of(context).size.height * 0.35,
fit: BoxFit.cover,
),
const Padding(padding: EdgeInsets.only(top: 8)),
Text(
tr("auth"),
style: Theme.of(context).textTheme.headline4!.copyWith(
color: Colors.black87, fontWeight: FontWeight.bold),
),
const Padding(padding: EdgeInsets.only(top: 15)),
TextFormFieldWidget(
prefixIcon: Icons.alternate_email_outlined,
controller: emailCtrl,
labelText: tr("user"),
keyboardType: TextInputType.emailAddress,
onChanged: (value) {
setState(() {});
},
obscureText: false,
validator: (value) {
if (value == null ||
value.isEmpty ||
!EmailValidator.validate(value)) {
return tr("valid_email");
}
return null;
},
),
TextFormFieldWidget(
prefixIcon: Icons.lock_outlined,
labelText: tr("pass"),
controller: passCtrl,
keyboardType: TextInputType.text,
onChanged: (value) {
setState(() {});
},
obscureText: true,
validator: (value) {
if (value == null || value.isEmpty) {
return tr("required_field");
}
return null;
},
),
Align(
alignment: Alignment.bottomRight,
child: TextButton(
onPressed: () => Navigator.of(context)
.pushNamed(RouterPaths.forgotPass),
child: Text(tr("forgot_pass")),
),
),
const Padding(padding: EdgeInsets.only(top: 8)),
ElevatedButton(
style: ElevatedButton.styleFrom(
minimumSize:
Size(MediaQuery.of(context).size.width, 45)),
onPressed: _login,
child: Text(tr("login"))),
Padding(
padding: const EdgeInsets.symmetric(vertical: 20.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
color: Colors.black87,
height: 1,
width: MediaQuery.of(context).size.width * 0.37,
),
const Icon(Icons.lock_open),
Container(
color: Colors.black87,
height: 1,
width: MediaQuery.of(context).size.width * 0.37,
),
],
),
),
...
],),
),
),
);
},
);
}
_login() {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
context.read<AuthBloc>().add(SignInRequested(
email: emailCtrl.text,
password: passCtrl.text,
));
}
}
}
Recuperación de Contraseña
En la pantalla de recuperación de contraseña, presentamos un formulario con el campo: correo electrónico. El usuario al presionar el botón “Enviar”, ejecutará el evento PassRecoverRequested de la lógica Bloc, que será escuchado por AuthBloc.
Seguido se emite el estado Loading, se ejecuta el método resetPass que envía un correo electrónico con el enlace para la recuperación de la contraseña y si no ocurre ningún error se lanzará el estado PassRecovered.
on<PassRecoverRequested>((event, emit) async {
emit(Loading());
try {
await authRepository.resetPass(
email: event.email,
);
emit(PassRecovered());
} catch (e) {
emit(AuthError(e.toString()));
}
});
En el lado de la vista del usuario, clase ForgotPassScreen, se escucharán los estados con la ayuda del widget BlocConsumer. Si se envía el correo de recuperación de contraseña con éxito, la aplicación navegará hacia la pantalla LoginScreen.
import 'package:easy_localization/easy_localization.dart';
import 'package:email_validator/email_validator.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:octa_app/routes/router_paths.dart';
import 'package:octa_app/scenes/auth/bloc/auth_bloc.dart';
import 'package:octa_app/scenes/widgets/icon_button_widget.dart';
import 'package:octa_app/scenes/widgets/text_form_field_widget.dart';
class ForgotPassScreen extends StatefulWidget {
const ForgotPassScreen({Key? key}) : super(key: key);
@override
State<ForgotPassScreen> createState() => _ForgotPassScreenState();
}
class _ForgotPassScreenState extends State<ForgotPassScreen> {
final _formKey = GlobalKey<FormState>();
TextEditingController emailCtrl = TextEditingController();
@override
Widget build(BuildContext context) {
return BlocConsumer<AuthBloc, AuthState>(
listener: (context, listener) {
if (listener is AuthError) {
log(listener.error);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(listener.error)),
);
}
if (listener is PassRecovered) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(tr("send_pass_recovered")),
),
);
Navigator.of(context).pushReplacementNamed(RouterPaths.login);
}
},
builder: (context, state) {
return Scaffold(
body: Form(
key: _formKey,
child: SingleChildScrollView(
child: Container(
height: MediaQuery.of(context).size.height,
padding: const EdgeInsets.all(26),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Padding(padding: EdgeInsets.only(top: 16)),
IconButtonWidget(
onPressed: () => Navigator.pop(context),
icon: Icons.arrow_back),
Align(
alignment: Alignment.center,
child: Image.asset(
"assets/img/forgotpass.png",
width: MediaQuery.of(context).size.width * 0.8,
height: MediaQuery.of(context).size.height * 0.4,
fit: BoxFit.cover,
),
),
const Padding(padding: EdgeInsets.only(top: 8)),
Text(
tr("forgot_pass"),
style: Theme.of(context).textTheme.headline4!.copyWith(
color: Colors.black87, fontWeight: FontWeight.bold),
),
const Padding(padding: EdgeInsets.only(top: 16)),
Text(
tr("forgot_pass_content"),
style: Theme.of(context).textTheme.bodyText2,
),
const Padding(padding: EdgeInsets.only(top: 16)),
TextFormFieldWidget(
prefixIcon: Icons.alternate_email_outlined,
controller: emailCtrl,
labelText: tr("email"),
keyboardType: TextInputType.emailAddress,
onChanged: (value) {
setState(() {});
},
obscureText: false,
validator: (value) {
if (value == null ||
value.isEmpty ||
!EmailValidator.validate(value)) {
return tr("valid_email");
}
return null;
},
),
const Padding(padding: EdgeInsets.only(top: 8)),
const Spacer(),
ElevatedButton(
style: ElevatedButton.styleFrom(
minimumSize:
Size(MediaQuery.of(context).size.width, 45)),
onPressed: _resetPass,
child: Text(tr("submit"))),
],
),
),
),
),
);
},
);
}
_resetPass() {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
context.read<AuthBloc>().add(PassRecoverRequested(
email: emailCtrl.text,
));
}
}
}
Cerrar sesión
Para el cierre de sesión en la lógica Bloc escucharemos el evento SignOutRequested que ejecutará el método signOut del repositorio de autenticación.
on<SignOutRequested>((event, emit) async {
emit(Loading());
try {
await authRepository.signOut();
emit(UnAuthenticated());
} catch (e) {
emit(AuthError(e.toString()));
}
});
Del lado de la vista del usuario, se ejecutará el evento:
context.read<AuthBloc>().add(SignOutRequested());
y en base a la respuesta, el widget BlocConsumer nos redirigirá hacia la pantalla de autenticación o mostrará un mensaje de error.
De esta manera tenemos las partes funcionales para realizar un proceso de autenticación, registro de usuarios, recuperación de contraseña y cerrar sesión con Firebase y Flutter Bloc.
Síguenos para continuar aprendiendo sobre el desarrollo de aplicaciones con Flutter.