Как создать безопасный процесс аутентификации пользователя в Flutter с использованием Firebase и управления состоянием Bloc
Аутентификация пользователя является важным аспектом разработки мобильных приложений. Она помогает убедиться, что только авторизованные пользователи имеют доступ к конфиденциальной информации и могут выполнять действия в приложении. В этом руководстве мы рассмотрим, как построить безопасную аутентификацию пользователей в Flutter с использованием Firebase для аутентификации и паттерна управления состоянием Bloc.
Проверка подлинности пользователя критична для разработки мобильных приложений. Она помогает убедиться, что только авторизованные пользователи могут получать доступ к конфиденциальной информации и выполнять действия внутри приложения.
В этом руководстве мы рассмотрим, как создать безопасную проверку подлинности пользователя в Flutter с использованием Firebase для аутентификации и шаблона управления состоянием Bloc для управления состоянием приложения. В конце у вас будет прочное понимание того, как интегрировать аутентификацию Firebase и реализовать безопасный процесс входа и регистрации с использованием Bloc.
Необходимые предварительные условия:
Для достижения максимальной эффективности в этом руководстве у вас должно быть следующее:
- Хорошее понимание Flutter и Dart
- Учетная запись Firebase: создайте учетную запись Firebase, если у вас еще нет такой. Вы можете настроить проект Firebase через Firebase Console.
Как работает аутентификация Firebase
Аутентификация Firebase – это мощный сервис, упрощающий процесс аутентификации пользователей в вашем приложении. Он поддерживает различные методы аутентификации, включая электронную почту/пароль, социальные сети и другие.
Одним из ключевых преимуществ аутентификации Firebase являются встроенные функции безопасности, такие как безопасное хранение учетных данных пользователей и шифрование конфиденциальных данных.
Описание графика потоков
Давайте визуализируем ход действий с помощью графика потоков, чтобы понять концепцию, которую вы собираетесь изучить. Взгляните на диаграмму ниже, чтобы лучше понять:
Изображение выше представляет собой график потоков, чтобы визуализировать поток приложения. Давайте обсудим, что представляет каждая часть. Округлые прямоугольники представляют начальные и конечные точки потока; фиолетовые прямоугольники представляют экраны; голубые прямоугольники представляют процессы, которые происходят; и, наконец, ромбы представляют принятие решения.
- Приложение начинается с
AuthenticationFlowScreen
. StreamBuilder
следит за изменениями состояния аутентификации.- Если пользователь аутентифицирован, направляется на
HomeScreen
; в противном случае, переходит наSignupScreen
. AuthenticationBloc
управляет событиями и состояниями аутентификации пользователя.- Когда пользователь регистрируется (
SignUpUser
событие срабатывает): - Он инициирует состояние загрузки аутентификации (
AuthenticationLoadingState
). - Вызывается
signUpUser
изAuthService
для регистрации пользователя. - Если успешно, он выдает
AuthenticationSuccessState
с данными пользователя; в противном случае, выдаетAuthenticationFailureState
. - Когда пользователь инициирует процесс выхода из системы (
SignOut
событие срабатывает): - Он запускает состояние загрузки аутентификации (
AuthenticationLoadingState
). - Вызывается
signOutUser
изAuthService
для выхода пользователя из системы. - Если произошла ошибка при выходе из системы, он записывает сообщение об ошибке.
Настройка проекта
Чтобы начать работу с аутентификацией Firebase, вам необходимо настроить Firebase в вашем проекте Flutter.
Следуйте этим шагам, чтобы добавить Firebase и bloc в ваш проект:
Добавьте зависимости в ваш проект
Откройте ваш проект в вашем предпочитаемом редакторе кода.
Добавьте следующие зависимости в файл pubspec.yaml
вашего проекта:
dependencies:firebase_core: ^2.20.0firebase_auth: ^4.12.0flutter_bloc: ^8.1.3
Затем сохраните файл pubspec.yaml
, чтобы получить зависимости.
Настройка Firebase
Создайте новый проект Firebase через консоль Firebase Console. Щелкните по разделу “аутентификация” в проекте и следуйте предоставленным инструкциям.
Для получения дополнительной информации вы можете ознакомиться с веб-сайтом Firebase website.
Инициализация Firebase
Сначала откройте файл main.dart
в папке lib
.
Добавьте следующий код в файл для инициализации Firebase:
void main() async {WidgetsFlutterBinding.ensureInitialized();await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform);
Код выше показывает код для запуска приложения. В этом коде нет ничего необычного, кроме того, что мы добавили некоторый код в void main
для инициализации Firebase.
Модель пользователя
Перед созданием класса Firebase для взаимодействия с сервисом Firebase давайте определим модель UserModel для представления данных пользователя.
Начните с создания файла user.dart
в папке lib
вашего проекта.
Затем добавьте следующий код в файл:
class UserModel {final String? id;final String? email;final String? displayName;UserModel({ this.id, this.email, this.displayName, });}
Теперь, когда вы настроили Firebase и создали модель пользователя, вам нужно создать класс сервиса, который будет взаимодействовать с Firebase напрямую.
Сервис аутентификации
Создайте папку с названием services
, создайте в этой папке файл с названием authentication.dart
. Теперь вы можете добавить в этот файл следующий код.
import 'package:firebase_auth/firebase_auth.dart';import '../models/user.dart';class AuthService { final FirebaseAuth _firebaseAuth = FirebaseAuth.instance; /// создание пользователя Future<UserModel?> signUpUser( String email, String password, ) async { try { final UserCredential userCredential = await _firebaseAuth.createUserWithEmailAndPassword( email: email.trim(), password: password.trim(), ); final User? firebaseUser = userCredential.user; if (firebaseUser != null) { return UserModel( id: firebaseUser.uid, email: firebaseUser.email ?? '', displayName: firebaseUser.displayName ?? '', ); } } on FirebaseAuthException catch (e) { print(e.toString()); } return null; } ///signOutUser Future<void> signOutUser() async { final User? firebaseUser = FirebaseAuth.instance.currentUser; if (firebaseUser != null) { await FirebaseAuth.instance.signOut(); } } // ... (другие методы)}}
Вышеуказанный сниппет кода является методом создания пользователя в приложении с использованием Firebase. С помощью этого метода метод signUpUser
принимает два строки параметров: email
и password
соответственно. Затем вы вызываете метод Firebase для создания пользователя, используя добавленные нами параметры.
Теперь, когда вы знаете, как создавать метод регистрации, вы также можете создать метод входа в систему. Класс в конечном итоге описывает взаимодействие между Firebase и приложением.
Следующий этап – связать сервис с управлением состоянием, о чем мы сейчас узнаем.
Как работает управление состоянием Bloc
Bloc – это популярный шаблон управления состоянием для Flutter, который помогает предсказуемо и тестируемо управлять сложными состояниями приложений. Bloc означает “Business Logic Component” и разделяет бизнес-логику и пользовательский интерфейс. Bloc будет мостом между вашим приложением и Firebase.
Существует расширение для VScode, которое создает код заготовки для Bloc. Вы можете использовать расширение, чтобы ускорить процесс разработки.
Настройка блока аутентификации Firebase
Bloc состоит из событий и состояний. Давайте сначала создадим состояния и события для Bloc. Затем мы создадим AuthenticationBloc
, который будет обрабатывать логику, используя созданные события, состояния и сервис.
Класс AuthenticationState
Класс AuthenticationState
отвечает за различные состояния процесса аутентификации. Как мы увидим в коде, существуют состояния начала, загрузки, успеха и ошибки, чтобы мы знали, что происходит в процессе аутентификации.
Сначала создайте файл authentication_state.dart
в папке bloc
вашего проекта.
часть 'authentication_bloc.dart';
абстрактный класс AuthenticationState {
const AuthenticationState();
Список<Object> получитьПропс => [];
}
класс AuthenticationInitialState extends AuthenticationState {}
класс AuthenticationLoadingState extends AuthenticationState {
final bool загрузка;
AuthenticationLoadingState({required this.загрузка});
}
класс AuthenticationSuccessState extends AuthenticationState {
final UserModel пользователь;
const AuthenticationSuccessState(this.пользователь);
@override
Список<Object> получитьПропс => [пользователь];
}
класс AuthenticationFailureState extends AuthenticationState {
final String сообщениеОбОшибке;
const AuthenticationFailureState(this.сообщениеОбОшибке);
@override
Список<Object> получитьПропс => [сообщениеОбОшибке];
}
Давайте разберем этот код:
Абстрактный класс AuthenticationState
:
AuthenticationState
– это базовый класс для различных состояний, в которых может находиться процесс аутентификации.- Он содержит метод
получитьПропс
, который возвращает список объектов. Этот метод используется для проверки равенства при сравнении экземпляров этого класса.
Класс AuthenticationInitialState
:
AuthenticationInitialState
представляет начальное состояние процесса аутентификации.
Класс AuthenticationLoadingState
:
AuthenticationLoadingState
представляет состояние, когда процесс аутентификации находится в процессе, и на пользовательском интерфейсе может отображаться индикатор загрузки.- Он принимает булевый параметр
загрузка
, чтобы указать, загружается ли в данный момент процесс аутентификации или нет.
Класс AuthenticationSuccessState
:
AuthenticationSuccessState
представляет состояние, когда процесс аутентификации завершен.- Он включает свойство пользователя типа UserModel, представляющее авторизованного пользователя.
Класс AuthenticationFailureState
:
AuthenticationFailureState
представляет состояние, когда процесс аутентификации завершился неудачно.- Он содержит свойство
сообщениеОбОшибке
, содержащее информацию об ошибке.
Класс AuthenticationEvent
Класс AuthenticationEvent
отвечает за события, которые будут выполняться AuthenticationBloc
. В данном случае это событие входа в систему. Вы можете написать здесь и другие события, например, регистрацию и выход из системы.
Создайте файл authentication_Event.dart
в каталоге вашего проекта bloc
.
часть 'authentication_bloc.dart';
абстрактный класс AuthenticationEvent {
const AuthenticationEvent();
Список<Object> получитьПропс => [];
}
класс SignUpUser extends AuthenticationEvent {
final String электроннаяПочта;
final String пароль;
const SignUpUser(this.электроннаяПочта, this.пароль);
@override
Список<Object> получитьПропс => [электроннаяПочта, пароль];
}
класс SignOut extends AuthenticationEvent {}
Класс AuthenticationEvent
похож на AuthenticationState
. Давайте рассмотрим код, чтобы понять, что он делает:
Абстрактный класс AuthenticationEvent
:
- Это базовый класс для различных событий, вызывающих изменение состояния аутентификации.
Класс SignUpUser
:
- Этот класс представляет событие, когда пользователь пытается зарегистрироваться.
- Он принимает два параметра:
электроннаяПочта
ипароль
, представляющие учетные данные, которые пользователь использует для регистрации. - Экземпляры этого класса будут сигнализировать о том, что пользователь пытается зарегистрироваться, и
Bloc
может ответить, инициировав процесс регистрации и соответствующим образом перейдя к состоянию аутентификации.
Класс SignOut
:
- Экземпляры этого класса будут сигнализировать
Bloc
, что пользователь пытается выйти из системы.Bloc
может ответить, инициировав процесс выхода из системы и обновив состояние аутентификации соответствующим образом.
Класс AuthenticationBloc
AuthenticationBloc
будет обрабатывать общее состояние аутентификации, от того, что происходит, когда пользователь нажимает кнопку, до того, что отображается на экране. Он также взаимодействует непосредственно с созданным нами сервисом Firebase.
Сначала создайте файл с именем authentication_bloc.dart
в каталоге вашего проекта bloc
.
Добавьте следующий код, чтобы определить класс AuthenticationBloc
:
import 'package:bloc/bloc.dart';import 'package:meta/meta.dart';import '../models/user.dart';import '../services/authentication.dart';part 'authentication_event.dart';part 'authentication_state.dart';class AuthenticationBloc extends Bloc<AuthenticationEvent, AuthenticationState> { final AuthService authService = AuthService(); AuthenticationBloc() : super(AuthenticationInitialState()) { on<AuthenticationEvent>((event, emit) {}); on<SignUpUser>((event, emit) async { emit(AuthenticationLoadingState(isLoading: true)); try { final UserModel? user = await authService.signUpUser(event.email, event.password); if (user != null) { emit(AuthenticationSuccessState(user)); } else { emit(const AuthenticationFailureState('create user failed')); } } catch (e) { print(e.toString()); } emit(AuthenticationLoadingState(isLoading: false)); }); on<SignOut>((event, emit) async { emit(AuthenticationLoadingState(isLoading: true)); try { authService.signOutUser(); } catch (e) { print('error'); print(e.toString()); } emit(AuthenticationLoadingState(isLoading: false)); });}}
В этом фрагменте кода мы создали экземпляр класса AuthService
, который обрабатывает операции аутентификации пользователя, такие как регистрация и выход.
on<SignUpUser>((event, emit) async { ... }
определяет обработчик для события SignUpUser
. Когда это событие срабатывает, bloc
выполняет следующие шаги:
- Он отправляет
AuthenticationLoadingState
для обозначения того, что процесс аутентификации находится в процессе. - Он вызывает метод
signUpUser
authService
, чтобы попытаться создать учетную запись пользователя с указанным электронной почтой и паролем. - Если создание учетной записи пользователя успешно (то есть пользователь не равен null), он отправляет
AuthenticationSuccessState
с данными пользователя. - Если создание учетной записи пользователя не удалось, он отправляет
AuthenticationFailureState
с сообщением об ошибке и регистрирует ошибку. - Безусловно, отправляет еще один
AuthenticationLoadingState
для сигнализации окончания процесса аутентификации.
on<SignOut>((event, emit) async { ... }
определяет обработчик для события SignOut
. Когда это событие срабатывает, bloc
выполняет следующие шаги:
- Он отправляет
AuthenticationLoadingState
для обозначения того, что процесс выхода из системы находится в процессе. - Он вызывает метод
signOutUser
authService
для выхода пользователя из системы. - Если в процессе выхода из системы происходят какие-либо ошибки, он регистрирует ошибку.
- Он отправляет еще один
AuthenticationLoadingState
для сигнализации окончания процесса выхода из системы.
AuthenticationBloc
управляет состоянием процесса аутентификации, включая состояния загрузки, успеха и ошибки, на основе событий, вызванных действиями пользователя. authService
отвечает за осуществление фактических операций аутентификации. С настройкой блока мы можем реализовать процесс аутентификации с использованием Bloc.
Как реализовать процесс аутентификации с использованием Bloc
Для реализации процесса аутентификации вы создадите отдельный Stateiless виджет для проверки, вошел ли пользователь в систему, чтобы мы знали, какой экран показать пользователю. Страница будет отображать разные экраны в зависимости от состояния аутентификации пользователя.
AuthenticationFlowScreen
:
Создайте новый файл с названием authentication_page.dart
в директории screens
вашего проекта.
import 'package:bloc_authentication_flow/screens/home.dart';import 'package:bloc_authentication_flow/screens/sign_up.dart';import 'package:firebase_auth/firebase_auth.dart';import 'package:flutter/material.dart';class AuthenticationFlowScreen extends StatelessWidget { const AuthenticationFlowScreen({super.key}); static String id = 'main screen'; @override Widget build(BuildContext context) { return Scaffold( body: StreamBuilder<User?>( stream: FirebaseAuth.instance.authStateChanges(), builder: (context, snapshot) { if (snapshot.hasData) { return const HomeScreen(); } else { return const SignupScreen(); } }, ), ); }}
В приведенном выше коде у вас есть StatelessWidget
с StreamBuilder
в качестве дочернего элемента. StreamBuilder
выступает в роли судьи, используя Firebase для проверки изменений состояния и того, вошел ли пользователь в систему или нет. Если пользователь вошел в систему, он перенаправляет их на домашний экран, иначе он переходит на экран регистрации.
Измените маршрут домашней страницы на AuthenticationFlowScreen
, чтобы приложение проверяло перед переходом на любую страницу.
home: const AuthenticationFlowScreen()
Экран регистрации
Сначала создайте новый файл с именем sign_up.dart
в каталоге screens
.
import 'package:bloc_authentication_flow/screens/home.dart';import 'package:flutter/material.dart';import 'package:flutter_bloc/flutter_bloc.dart';import '../bloc/authentication_bloc.dart';class SignupScreen extends StatefulWidget { static String id = 'login_screen'; const SignupScreen({ Key? key, }) : super(key: key); @override State<SignupScreen> createState() => _SignupScreenState();}class _SignupScreenState extends State<SignupScreen> { // Text Controllers final emailController = TextEditingController(); final passwordController = TextEditingController(); @override void dispose() { emailController.dispose(); passwordController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text( 'Войти в свою учетную запись', style: TextStyle( color: Colors.deepPurple, ), ), centerTitle: true, ), body: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 20), const Text('Адрес электронной почты'), const SizedBox(height: 10), TextFormField( controller: emailController, decoration: const InputDecoration( border: OutlineInputBorder(), hintText: 'Введите адрес электронной почты', ), ), const SizedBox(height: 10), const Text('Пароль'), TextFormField( controller: passwordController, decoration: const InputDecoration( border: OutlineInputBorder(), hintText: 'Введите свой пароль', ), obscureText: false, ), const SizedBox(height: 10), GestureDetector( onTap: () {}, child: const Text( 'Забыли пароль?', style: TextStyle( color: Colors.deepPurple, ), ), ), const SizedBox(height: 20), BlocConsumer<AuthenticationBloc, AuthenticationState>( listener: (context, state) { if (state is AuthenticationSuccessState) { Navigator.pushNamedAndRemoveUntil( context, HomeScreen.id, (route) => false, ); } else if (state is AuthenticationFailureState) { showDialog( context: context, builder: (context) { return const AlertDialog( content: Text('Ошибка'), ); }); } }, builder: (context, state) { return SizedBox( height: 50, width: double.infinity, child: ElevatedButton( onPressed: () { BlocProvider.of<AuthenticationBloc>(context).add( SignUpUser( emailController.text.trim(), passwordController.text.trim(), ), ); }, child: Text( state is AuthenticationLoadingState ? '.......', : 'Зарегистрироваться', style: TextStyle( fontSize: 20, ), ), ), ); }, ), const SizedBox(height: 20), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text("У вас уже есть учетная запись? "), GestureDetector( onTap: () {}, child: const Text( 'Войти', style: TextStyle( color: Colors.deepPurple, ), ), ) ], ), ], ), ), ); }}
Этот код – это простой интерфейс входа в систему с двумя textfields
и кнопкой с поднятыми краями. Виджет BlocConsumer
оборачивает кнопку Регистрация
и прослушивает изменения состояния AuthenticationBloc
. Когда пользователь нажимает на кнопку, он отправляет событие в AuthenticationBloc
, чтобы начать процесс регистрации пользователя.
В зависимости от состояния аутентификации, эта кнопка может отображать разные отзывы или переходить на другой экран. Она проверяет состояния AuthenticationSuccessState
, AuthenticationLoadingState
и AuthenticationFailureState
, чтобы реагировать соответствующим образом.
Экран домашней страницы
Создайте еще один файл с названием home_screen.dart
в директории screens
и добавьте следующий код в файл.
import 'package:flutter/material.dart';import 'package:flutter_bloc/flutter_bloc.dart';import '../bloc/authentication_bloc.dart';class HomeScreen extends StatelessWidget { static String id = 'home_screen'; const HomeScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text( 'Привет, Пользователь', style: TextStyle( fontSize: 20, ), ), const SizedBox( height: 20, ), BlocConsumer<AuthenticationBloc, AuthenticationState>( listener: (context, state) { if (state is AuthenticationLoadingState) { const CircularProgressIndicator(); } else if (state is AuthenticationFailureState){ showDialog(context: context, builder: (context){ return const AlertDialog( content: Text('Ошибка'), ); }); } }, builder: (context, state) { return ElevatedButton( onPressed: () { BlocProvider.of<AuthenticationBloc>(context) .add(SignOut()); }, child: const Text( 'Выйти' )); }, ), ], ), ), ); }}
Приведенный выше код представляет HomeScreen
и это также простая страница, состоящая из конструкции “Scaffold”, столбца и виджета с текстом, но интересную часть составляет BlocConsumer
, который находится на кнопке с надписью “Выйти”.
Давайте рассмотрим это поближе.
Помние, что BlocConsumer
отслеживает изменения состояний из AuthenticationBloc
. Он имеет два параметра – listener и builder.
- listener: Отслеживает изменения состояния и реагирует на него в зависимости от текущего состояния, полученного из
AuthenticationBloc
. - Если состояние – это
AuthenticationLoadingState
, то выводится компонентCircularProgressIndicator
. - Если состояние – это
AuthenticationFailureState
, то выводится диалоговое окноAlertDialog
с сообщением “Ошибка”. - builder: Строит интерфейс на основе текущего состояния, полученного из
AuthenticationBloc
. - Отображается компонент
ElevatedButton
с надписью “Выйти”. - При нажатии кнопки, событие
SignOut
вызывается вAuthenticationBloc
через BlocProvider.
С реализацией процесса аутентификации с использованием Bloc вы можете запустить свое приложение Flutter и протестировать функциональность регистрации. Убедитесь также в обработке других связанных с аутентификацией сценариев, таких как вход пользователя и восстановление пароля, в соответствии с требованиями вашего приложения. Также важно грамотно обрабатывать ошибки, чтобы пользователь получил наилучший опыт использования приложения.
Если вы хотите склонировать репозиторий, вы можете посмотреть его на GitHub и оставить отметку “Мне нравится”.
Заключение
В этой статье мы рассмотрели построение процесса аутентификации пользователя в Flutter с использованием Firebase для аутентификации и паттерна управления состоянием Bloc для управления состоянием приложения.
Мы узнали, как настроить Firebase в проекте Flutter, создать Blocs для аутентификации и реализовать процесс аутентификации с использованием Bloc.
Используя мощь Firebase и предсказуемость Bloc, вы можете обеспечить безопасный и плавный процесс аутентификации пользователей в ваших приложениях Flutter.</p
Leave a Reply