1. Прежде чем начать
В этом практическом занятии вы изучите основы Firebase для создания мобильных приложений Flutter для Android и iOS.
Предварительные требования
- Знание Flutter
- Flutter SDK
- Текстовый редактор на ваш выбор
Что вы узнаете
- Как создать приложение для подтверждения участия в мероприятии и чата с гостевой книгой на Android, iOS, в веб-версии и macOS с помощью Flutter.
- Как аутентифицировать пользователей с помощью Firebase Authentication и синхронизировать данные с Firestore .
|
|
|
|
Что вам понадобится
Любое из следующих устройств:
- Физическое устройство Android или iOS, подключенное к компьютеру и настроенное на режим разработчика.
- Симулятор iOS (требуются инструменты Xcode ).
- Эмулятор Android (требуется настройка в Android Studio ).
Вам также потребуется следующее:
- Любой браузер на ваш выбор, например, Google Chrome.
- IDE или текстовый редактор на ваш выбор, настроенный с поддержкой плагинов Dart и Flutter, например Android Studio или Visual Studio Code .
- Последняя
stableверсия Flutter илиbeta, если вам нравится рисковать. - Учетная запись Google для создания и управления вашим проектом Firebase.
-
FirebaseCLI авторизован в вашем аккаунте Google.
2. Получите пример кода.
Загрузите начальную версию своего проекта с GitHub:
- В командной строке клонируйте репозиторий GitHub в каталог
flutter-codelabs:
git clone https://github.com/flutter/codelabs.git flutter-codelabs
В каталоге flutter-codelabs находится код для набора практических заданий. Код для этого практического задания находится в каталоге flutter-codelabs/firebase-get-to-know-flutter . В этом каталоге содержится ряд снимков, показывающих, как должен выглядеть ваш проект в конце каждого шага. Например, вы находитесь на втором шаге.
- Найдите соответствующие файлы для второго шага:
cd flutter-codelabs/firebase-get-to-know-flutter/step_02
Если вы хотите перейти к следующему шагу или посмотреть, как что-то должно выглядеть после его завершения, загляните в каталог, название которого соответствует интересующему вас шагу.
Импортируйте стартовое приложение
- Откройте или импортируйте каталог
flutter-codelabs/firebase-get-to-know-flutter/step_02в вашей любимой IDE. Этот каталог содержит стартовый код для практического занятия, который представляет собой еще не работающее приложение для Flutter-встречи. При открытии приложения в IDE вы заметите ошибки компиляции, которые будут исправлены на шаге 4.
Найдите файлы, требующие обработки.
Код этого приложения разбросан по нескольким каталогам. Такое разделение функциональности упрощает работу, поскольку код группируется по функциональным параметрам.
- Найдите следующие файлы:
-
lib/main.dart: Этот файл содержит основную точку входа и виджет приложения. -
lib/home_page.dart: Этот файл содержит виджет домашней страницы. -
lib/src/widgets.dart: Этот файл содержит несколько виджетов, помогающих стандартизировать стиль приложения. Они формируют экран стартового приложения. -
lib/src/authentication.dart: Этот файл содержит частичную реализацию аутентификации с набором виджетов для создания пользовательского интерфейса входа в систему для аутентификации на основе электронной почты Firebase. Эти виджеты для процесса аутентификации пока не используются в стартовом приложении, но вы добавите их в ближайшее время.
-
Для сборки остальной части приложения вы добавляете дополнительные файлы по мере необходимости.
Просмотрите файл lib/main.dart
Это приложение использует пакет google_fonts , благодаря чему шрифт Roboto является шрифтом по умолчанию во всем приложении. Вы можете изучить fonts.google.com и использовать найденные там шрифты в разных частях приложения.
Вспомогательные виджеты из файла lib/src/widgets.dart представлены в виде элементов Header , Paragraph и IconAndDetail . Эти виджеты устраняют дублирование кода, уменьшая загромождение макета страницы, описанного в HomePage . Это также обеспечивает единообразный внешний вид.
Вот как ваше приложение выглядит на Android, iOS, в веб-версии и на macOS:
|
|
|
|
3. Создайте и настройте проект Firebase.
Отображение информации о мероприятии отлично подходит для ваших гостей, но само по себе оно малополезно для кого-либо. Вам необходимо добавить в приложение динамическую функциональность. Для этого вам нужно подключить Firebase к вашему приложению. Чтобы начать работу с Firebase, вам нужно создать и настроить проект Firebase.
Создайте проект Firebase.
- Войдите в консоль Firebase, используя свою учетную запись Google.
- Нажмите кнопку, чтобы создать новый проект, а затем введите название проекта (например,
Firebase-Flutter-Codelab). - Нажмите «Продолжить» .
- Если появится запрос, ознакомьтесь с условиями использования Firebase и примите их, после чего нажмите «Продолжить» .
- (Необязательно) Включите помощь ИИ в консоли Firebase (в Firebase она называется "Gemini").
- Для этого практического занятия вам не понадобится Google Analytics, поэтому отключите эту опцию.
- Нажмите «Создать проект» , дождитесь завершения подготовки проекта, а затем нажмите «Продолжить» .
Чтобы узнать больше о проектах Firebase, см. раздел «Понимание проектов Firebase» .
Настройка продуктов Firebase
В приложении используются следующие продукты Firebase, доступные для веб-приложений:
- Аутентификация: позволяет пользователям входить в ваше приложение.
- Firestore: Сохраняет структурированные данные в облаке и мгновенно уведомляет об изменениях данных.
- Правила безопасности Firebase: обеспечивают безопасность вашей базы данных.
Для некоторых из этих продуктов требуется специальная настройка, или же их необходимо включить в консоли Firebase.
Включить аутентификацию по электронной почте
- В панели обзора проекта в консоли Firebase разверните меню «Сборка» .
- Нажмите «Аутентификация» > «Начать» > «Способ входа» > «Электронная почта/Пароль» > «Включить» > «Сохранить» .

Настройте Firestore
Веб-приложение использует Firestore для сохранения сообщений чата и получения новых сообщений.
Вот как настроить Firestore в вашем проекте Firebase:
- В левой панели консоли Firebase разверните раздел «Сборка» , а затем выберите базу данных Firestore .
- Нажмите «Создать базу данных» .
- Выберите стандартную версию и нажмите «Далее».
- Оставьте значение параметра " Идентификатор базы данных" равным
(default). - Выберите местоположение для вашей базы данных, затем нажмите «Далее» .
Для создания настоящего приложения вам следует выбрать местоположение, расположенное недалеко от ваших пользователей. - Нажмите «Пуск» в тестовом режиме . Ознакомьтесь с отказом от ответственности в отношении правил безопасности.
В дальнейшем в этом практическом занятии вы добавите правила безопасности для защиты ваших данных. Не распространяйте и не предоставляйте публичный доступ к приложению, не добавив правила безопасности для вашей базы данных. - Нажмите «Создать» .
4. Настройка Firebase
Для использования Firebase с Flutter необходимо выполнить следующие действия, чтобы настроить проект Flutter для корректного использования библиотек FlutterFire :
- Добавьте зависимости
FlutterFireв свой проект. - Зарегистрируйте выбранную платформу в проекте Firebase.
- Загрузите конфигурационный файл, специфичный для вашей платформы, и добавьте его в код.
В корневом каталоге вашего Flutter-приложения находятся подкаталоги android , ios , macos и web , в которых хранятся конфигурационные файлы для iOS и Android соответственно.
Настройка зависимостей
Вам необходимо добавить библиотеки FlutterFire для двух продуктов Firebase, которые вы используете в этом приложении: Authentication и Firestore.
- В командной строке добавьте следующие зависимости из корневой папки вашего приложения (
.../firebase-get-to-know-flutter/step_02):
$ flutter pub add firebase_core firebase_auth cloud_firestore provider firebase_ui_auth
Для использования Firebase в вашем Flutter-приложении вам потребуется объединить несколько специализированных пакетов:
- Пакет
firebase_core: Это важнейшая отправная точка. Вам необходим этот пакет, поскольку все остальные инструменты Firebase для Flutter зависят от него. - Пакет
firebase_auth: Этот пакет предназначен для управления учетными записями пользователей. Он позволяет добавлять такие функции, как регистрация, вход и выход из системы. - Пакет
cloud_firestore: Используйте его для подключения вашего приложения к базе данных Firestore, что позволит вам сохранять и получать доступ к данным вашего приложения. - Пакет
firebase_ui_auth: Этот пакет значительно ускоряет настройку аутентификации. Он предоставляет готовые к использованию виджеты (например, предварительно созданные экраны входа в систему), поэтому вам не нужно создавать все с нуля. - Пакет
provider: это популярный выбор для управления состоянием. Он помогает вашему приложению отслеживать информацию (например, кто вошел в систему) и делать эти данные доступными на всех экранах, которым они необходимы.
Вы добавили необходимые пакеты, но вам также нужно настроить проекты iOS, Android, macOS и Web Runner для корректного использования Firebase. Кроме того, вы используете пакет provider , который обеспечивает разделение бизнес-логики и логики отображения.
Установите FlutterFire CLI.
Интерфейс командной строки FlutterFire зависит от базового интерфейса командной строки Firebase.
- Если вы еще этого не сделали, установите Firebase CLI на свой компьютер.
- Установите FlutterFire CLI:
$ dart pub global activate flutterfire_cli
После установки команда flutterfire становится доступна глобально.
Настройте свои приложения
Интерфейс командной строки извлекает информацию из вашего проекта Firebase и выбранных приложений проекта для генерации всей конфигурации для конкретной платформы.
В корневой директории вашего приложения ( flutter-codelabs/firebase-get-to-know-flutter/step_02 ) выполните команду configure :
$ flutterfire configure
Команда конфигурации выполняет следующие действия:
- Выберите проект Firebase на основе файла
.firebasercили из консоли Firebase. - Определите платформы для настройки, такие как Android, iOS, macOS и веб-платформа.
- Определите приложения Firebase, из которых нужно извлечь конфигурацию. По умолчанию CLI пытается автоматически сопоставить приложения Firebase на основе текущей конфигурации вашего проекта.
- Создайте файл
firebase_options.dartв вашем проекте.
Настройка macOS
Flutter на macOS позволяет создавать полностью изолированные приложения. Поскольку это приложение интегрируется с сетью для связи с серверами Firebase, вам необходимо настроить приложение с правами сетевого клиента.
macos/Runner/DebugProfile.entitlements
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<!-- Add the following two lines -->
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>
macos/Runner/Release.entitlements
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<!-- Add the following two lines -->
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>
Для получения дополнительной информации см. раздел «Поддержка Flutter для настольных компьютеров» .
5. Добавить функцию подтверждения участия (RSVP).
Теперь, когда вы добавили Firebase в приложение, вы можете создать кнопку RSVP , которая регистрирует участников с помощью аутентификации . Для нативных приложений Android, iOS и веб-приложений существуют готовые пакеты FirebaseUI Auth , но для Flutter вам потребуется реализовать эту функцию самостоятельно.
Проект, который вы получили ранее, включал набор виджетов, реализующих пользовательский интерфейс для большей части процесса аутентификации. Вам необходимо реализовать бизнес-логику для интеграции аутентификации с приложением.
Добавьте бизнес-логику с помощью пакета Provider .
Используйте пакет provider , чтобы сделать централизованный объект состояния приложения доступным для всего дерева виджетов Flutter в приложении:
- Создайте новый файл с именем
app_state.dartсо следующим содержимым:
lib/app_state.dart
import 'package:firebase_auth/firebase_auth.dart'
hide EmailAuthProvider, PhoneAuthProvider;
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';
import 'firebase_options.dart';
class ApplicationState extends ChangeNotifier {
ApplicationState() {
init();
}
bool _loggedIn = false;
bool get loggedIn => _loggedIn;
Future<void> init() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform);
FirebaseUIAuth.configureProviders([
EmailAuthProvider(),
]);
FirebaseAuth.instance.userChanges().listen((user) {
if (user != null) {
_loggedIn = true;
} else {
_loggedIn = false;
}
notifyListeners();
});
}
}
Операторы import подключают Firebase Core и Auth, загружают пакет provider , который делает объект состояния приложения доступным во всем дереве виджетов, и включают виджеты аутентификации из пакета firebase_ui_auth .
Класс состояния приложения ApplicationState выполняет на этом этапе одну основную задачу: оповестить дерево виджетов о том, что произошло обновление состояния аутентификации.
Для передачи в приложение информации о состоянии авторизации пользователя используется только провайдер. Для авторизации пользователя используются интерфейсы, предоставляемые пакетом firebase_ui_auth , что является отличным способом быстрой инициализации экранов входа в систему в ваших приложениях.
Интегрируйте процесс аутентификации.
- Измените импорты в верхней части файла
lib/main.dart:
lib/main.dart
import 'package:firebase_ui_auth/firebase_ui_auth.dart'; // new
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; // new
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart'; // new
import 'app_state.dart'; // new
import 'home_page.dart';
- Свяжите состояние приложения с инициализацией приложения, а затем добавьте поток аутентификации на
HomePage:
lib/main.dart
void main() {
// Modify from here...
WidgetsFlutterBinding.ensureInitialized();
runApp(ChangeNotifierProvider(
create: (context) => ApplicationState(),
builder: ((context, child) => const App()),
));
// ...to here.
}
Изменение функции main() делает пакет-провайдер ответственным за создание экземпляра объекта состояния приложения с виджетом ChangeNotifierProvider . Вы используете именно этот класс provider , потому что объект состояния приложения наследует класс ChangeNotifier , что позволяет пакету- provider знать, когда следует повторно отображать зависимые виджеты.
- Обновите свое приложение, чтобы оно обрабатывало навигацию по различным экранам, предоставляемым
FirebaseUI, создав конфигурациюGoRouter:
lib/main.dart
// Add GoRouter configuration outside the App class
final _router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomePage(),
routes: [
GoRoute(
path: 'sign-in',
builder: (context, state) {
return SignInScreen(
actions: [
ForgotPasswordAction(((context, email) {
final uri = Uri(
path: '/sign-in/forgot-password',
queryParameters: <String, String?>{
'email': email,
},
);
context.push(uri.toString());
})),
AuthStateChangeAction(((context, state) {
final user = switch (state) {
SignedIn state => state.user,
UserCreated state => state.credential.user,
_ => null
};
if (user == null) {
return;
}
if (state is UserCreated) {
user.updateDisplayName(user.email!.split('@')[0]);
}
if (!user.emailVerified) {
user.sendEmailVerification();
const snackBar = SnackBar(
content: Text(
'Please check your email to verify your email address'));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
context.pushReplacement('/');
})),
],
);
},
routes: [
GoRoute(
path: 'forgot-password',
builder: (context, state) {
final arguments = state.uri.queryParameters;
return ForgotPasswordScreen(
email: arguments['email'],
headerMaxExtent: 200,
);
},
),
],
),
GoRoute(
path: 'profile',
builder: (context, state) {
return ProfileScreen(
providers: const [],
actions: [
SignedOutAction((context) {
context.pushReplacement('/');
}),
],
);
},
),
],
),
],
);
// end of GoRouter configuration
// Change MaterialApp to MaterialApp.router and add the routerConfig
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'Firebase Meetup',
theme: ThemeData(
buttonTheme: Theme.of(context).buttonTheme.copyWith(
highlightColor: Colors.deepPurple,
),
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
textTheme: GoogleFonts.robotoTextTheme(
Theme.of(context).textTheme,
),
visualDensity: VisualDensity.adaptivePlatformDensity
),
routerConfig: _router, // new
);
}
}
Для каждого экрана предусмотрено определенное действие в зависимости от нового состояния процесса аутентификации. После большинства изменений состояния аутентификации вы можете вернуться на предпочитаемый экран, будь то главный экран или другой экран, например, профиль.
- В методе build класса
HomePageинтегрируйте состояние приложения с виджетомAuthFunc:
lib/home_page.dart
import 'package:firebase_auth/firebase_auth.dart' // new
hide EmailAuthProvider, PhoneAuthProvider; // new
import 'package:flutter/material.dart'; // new
import 'package:provider/provider.dart'; // new
import 'app_state.dart'; // new
import 'src/authentication.dart'; // new
import 'src/widgets.dart';
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Firebase Meetup'),
),
body: ListView(
children: <Widget>[
Image.asset('assets/codelab.png'),
const SizedBox(height: 8),
const IconAndDetail(Icons.calendar_today, 'October 30'),
const IconAndDetail(Icons.location_city, 'San Francisco'),
// Add from here
Consumer<ApplicationState>(
builder: (context, appState, _) => AuthFunc(
loggedIn: appState.loggedIn,
signOut: () {
FirebaseAuth.instance.signOut();
}),
),
// to here
const Divider(
height: 8,
thickness: 1,
indent: 8,
endIndent: 8,
color: Colors.grey,
),
const Header("What we'll be doing"),
const Paragraph(
'Join us for a day full of Firebase Workshops and Pizza!',
),
],
),
);
}
}
Вы создаете экземпляр виджета AuthFunc и оборачиваете его в виджет Consumer . Виджет Consumer — это обычный способ использования пакета provider для перестроения части дерева при изменении состояния приложения. Виджет AuthFunc — это дополнительные виджеты, которые вы тестируете.
Запустите приложение, чтобы протестировать процесс аутентификации.

- В приложении нажмите кнопку RSVP , чтобы перейти на
SignInScreen.

- Введите адрес электронной почты. Если вы уже зарегистрированы, система предложит вам ввести пароль. В противном случае система предложит вам заполнить регистрационную форму.

- Введите пароль короче шести символов, чтобы проверить обработку ошибок. Если вы зарегистрированы, вместо этого вы увидите форму для ввода пароля.
- Введите неверный пароль, чтобы проверить алгоритм обработки ошибок.
- Введите правильный пароль. Вы увидите экран авторизации, который предлагает пользователю выйти из системы.

6. Запись сообщений в Firestore
Здорово, что пользователи приходят, но нужно предложить гостям что-то ещё для развлечения в приложении. Что если бы они могли оставлять сообщения в гостевой книге? Они могли бы рассказать, почему с нетерпением ждут приезда или с кем надеются встретиться.
Для хранения сообщений чата, которые пользователи пишут в приложении, используется Firestore .
модель данных
Firestore — это база данных NoSQL, и данные в ней разделены на коллекции, документы, поля и подколлекции. Каждое сообщение чата хранится как документ в коллекции guestbook , которая является коллекцией верхнего уровня.

Добавить сообщения в Firestore
В этом разделе вы добавляете функциональность для отправки сообщений пользователями в базу данных. Сначала вы добавляете поле формы и кнопку отправки, а затем добавляете код, который связывает эти элементы с базой данных.
- Создайте новый файл с именем
guest_book.dart, добавьте в него виджетGuestBookс сохранением состояния для построения элементов пользовательского интерфейса: поля для сообщения и кнопки отправки:
lib/guest_book.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'src/widgets.dart';
class GuestBook extends StatefulWidget {
const GuestBook({required this.addMessage, super.key});
final FutureOr<void> Function(String message) addMessage;
@override
State<GuestBook> createState() => _GuestBookState();
}
class _GuestBookState extends State<GuestBook> {
final _formKey = GlobalKey<FormState>(debugLabel: '_GuestBookState');
final _controller = TextEditingController();
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Form(
key: _formKey,
child: Row(
children: [
Expanded(
child: TextFormField(
controller: _controller,
decoration: const InputDecoration(
hintText: 'Leave a message',
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Enter your message to continue';
}
return null;
},
),
),
const SizedBox(width: 8),
StyledButton(
onPressed: () async {
if (_formKey.currentState!.validate()) {
await widget.addMessage(_controller.text);
_controller.clear();
}
},
child: Row(
children: const [
Icon(Icons.send),
SizedBox(width: 4),
Text('SEND'),
],
),
),
],
),
),
);
}
}
Здесь есть несколько интересных моментов. Во-первых, вы создаёте экземпляр формы, чтобы проверить, действительно ли сообщение содержит контент, и показать пользователю сообщение об ошибке, если его нет. Для проверки формы вы получаете доступ к её состоянию с помощью GlobalKey . Дополнительную информацию о ключах и способах их использования см. в разделе «Когда использовать ключи» .
Обратите внимание также на расположение виджетов: у вас есть Row с TextFormField и StyledButton , которая содержит Row . Также обратите внимание, что TextFormField заключено в виджет Expanded , который заставляет TextFormField заполнять любое дополнительное пространство в строке. Чтобы лучше понять, почему это необходимо, см. раздел «Понимание ограничений» .
Теперь, когда у вас есть виджет, позволяющий пользователю вводить текст для добавления в гостевую книгу, вам нужно отобразить его на экране.
- Отредактируйте содержимое элемента
HomePage, добавив следующие две строки в конец дочерних элементовListView:
const Header("What we'll be doing"),
const Paragraph(
'Join us for a day full of Firebase Workshops and Pizza!',
),
// Add the following two lines.
const Header('Discussion'),
GuestBook(addMessage: (message) => print(message)),
Хотя этого достаточно для отображения виджета, этого недостаточно для выполнения каких-либо полезных действий. Вскоре вам потребуется обновить этот код, чтобы он заработал.
Предварительный просмотр приложения
|
|
|
|
Когда пользователь нажимает кнопку «ОТПРАВИТЬ» , запускается следующий фрагмент кода. Он добавляет содержимое поля ввода сообщения в коллекцию guestbook базы данных. В частности, метод addMessageToGuestBook добавляет содержимое сообщения в новый документ с автоматически сгенерированным идентификатором в коллекции guestbook .
Обратите внимание, что FirebaseAuth.instance.currentUser.uid — это ссылка на автоматически сгенерированный уникальный идентификатор, который аутентификация присваивает всем авторизованным пользователям.
- В файл
lib/app_state.dartдобавьте методaddMessageToGuestBook. На следующем шаге вы свяжете эту возможность с пользовательским интерфейсом.
lib/app_state.dart
import 'package:cloud_firestore/cloud_firestore.dart'; // new
import 'package:firebase_auth/firebase_auth.dart'
hide EmailAuthProvider, PhoneAuthProvider;
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';
import 'firebase_options.dart';
class ApplicationState extends ChangeNotifier {
// Current content of ApplicationState elided ...
// Add from here...
Future<DocumentReference> addMessageToGuestBook(String message) {
if (!_loggedIn) {
throw Exception('Must be logged in');
}
return FirebaseFirestore.instance
.collection('guestbook')
.add(<String, dynamic>{
'text': message,
'timestamp': DateTime.now().millisecondsSinceEpoch,
'name': FirebaseAuth.instance.currentUser!.displayName,
'userId': FirebaseAuth.instance.currentUser!.uid,
});
}
// ...to here.
}
Соедините пользовательский интерфейс и базу данных.
У вас есть пользовательский интерфейс, где пользователь может ввести текст, который он хочет добавить в гостевую книгу, и код для добавления записи в Firestore. Теперь вам нужно только соединить эти два элемента.
- В файле
lib/home_page.dartвнесите следующие изменения в виджетHomePage:
lib/home_page.dart
import 'package:firebase_auth/firebase_auth.dart'
hide EmailAuthProvider, PhoneAuthProvider;
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'app_state.dart';
import 'guest_book.dart'; // new
import 'src/authentication.dart';
import 'src/widgets.dart';
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Firebase Meetup'),
),
body: ListView(
children: <Widget>[
Image.asset('assets/codelab.png'),
const SizedBox(height: 8),
const IconAndDetail(Icons.calendar_today, 'October 30'),
const IconAndDetail(Icons.location_city, 'San Francisco'),
Consumer<ApplicationState>(
builder: (context, appState, _) => AuthFunc(
loggedIn: appState.loggedIn,
signOut: () {
FirebaseAuth.instance.signOut();
}),
),
const Divider(
height: 8,
thickness: 1,
indent: 8,
endIndent: 8,
color: Colors.grey,
),
const Header("What we'll be doing"),
const Paragraph(
'Join us for a day full of Firebase Workshops and Pizza!',
),
// Modify from here...
Consumer<ApplicationState>(
builder: (context, appState, _) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (appState.loggedIn) ...[
const Header('Discussion'),
GuestBook(
addMessage: (message) =>
appState.addMessageToGuestBook(message),
),
],
],
),
),
// ...to here.
],
),
);
}
}
Вы заменили две строки, добавленные в начале этого шага, полной реализацией. Вы снова используете Consumer<ApplicationState> , чтобы сделать состояние приложения доступным для той части дерева, которую вы отображаете. Это позволяет вам реагировать на сообщения, которые кто-то вводит в пользовательский интерфейс, и публиковать их в базе данных. В следующем разделе вы проверяете, опубликованы ли добавленные сообщения в базе данных.
Проверка отправки сообщений
- При необходимости войдите в приложение.
- Введите сообщение, например,
Hey there!, а затем нажмите «ОТПРАВИТЬ» .
Это действие записывает сообщение в вашу базу данных Firestore. Однако вы не увидите сообщение в самом приложении Flutter, поскольку вам еще нужно реализовать получение данных, что вы сделаете на следующем шаге. Тем не менее, на панели управления базами данных в консоли Firebase вы можете увидеть добавленное сообщение в коллекции guestbook . Если вы отправляете больше сообщений, вы добавляете больше документов в свою коллекцию guestbook . Например, см. следующий фрагмент кода:

7. Читайте сообщения
Замечательно, что гости могут оставлять сообщения в базе данных, но пока они не видят их в приложении. Пора это исправить!
Синхронизация сообщений
Для отображения сообщений необходимо добавить обработчики событий, которые срабатывают при изменении данных, а затем создать элемент пользовательского интерфейса, отображающий новые сообщения. В состояние приложения нужно добавить код, который будет отслеживать появление новых сообщений.
- Создайте новый файл
guest_book_message.dartи добавьте следующий класс, чтобы отобразить структурированное представление данных, которые вы храните в Firestore.
lib/guest_book_message.dart
class GuestBookMessage {
GuestBookMessage({required this.name, required this.message});
final String name;
final String message;
}
- В файл
lib/app_state.dartдобавьте следующие импорты:
lib/app_state.dart
import 'dart:async'; // new
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart'
hide EmailAuthProvider, PhoneAuthProvider;
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';
import 'firebase_options.dart';
import 'guest_book_message.dart'; // new
- В разделе
ApplicationState, где вы определяете состояние и геттеры, добавьте следующие строки:
lib/app_state.dart
bool _loggedIn = false;
bool get loggedIn => _loggedIn;
// Add from here...
StreamSubscription<QuerySnapshot>? _guestBookSubscription;
List<GuestBookMessage> _guestBookMessages = [];
List<GuestBookMessage> get guestBookMessages => _guestBookMessages;
// ...to here.
- В разделе инициализации объекта
ApplicationStateдобавьте следующие строки, чтобы подписаться на запрос к коллекции документов при входе пользователя в систему и отписаться при выходе из нее:
lib/app_state.dart
Future<void> init() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform);
FirebaseUIAuth.configureProviders([
EmailAuthProvider(),
]);
FirebaseAuth.instance.userChanges().listen((user) {
if (user != null) {
_loggedIn = true;
// Add from here...
_guestBookSubscription = FirebaseFirestore.instance
.collection('guestbook')
.orderBy('timestamp', descending: true)
.snapshots()
.listen((snapshot) {
_guestBookMessages = [];
for (final document in snapshot.docs) {
_guestBookMessages.add(
GuestBookMessage(
name: document.data()['name'] as String,
message: document.data()['text'] as String,
),
);
}
notifyListeners();
});
// ...to here.
} else {
_loggedIn = false;
// Add from here...
_guestBookMessages = [];
_guestBookSubscription?.cancel();
// to here.
}
notifyListeners();
});
}
Этот раздел важен, потому что именно здесь вы формируете запрос к коллекции guestbook и обрабатываете подписку и отписку от этой коллекции. Вы прослушиваете поток, где восстанавливаете локальный кэш сообщений из коллекции guestbook , а также сохраняете ссылку на эту подписку, чтобы позже отписаться от неё. Здесь происходит много всего, поэтому вам следует изучить это в отладчике, чтобы понять, что происходит, и получить более ясную модель. Для получения дополнительной информации см. раздел «Получение обновлений в реальном времени с помощью Firestore» .
- В файл
lib/guest_book.dartдобавьте следующий импорт:
import 'guest_book_message.dart';
- В виджете
GuestBookдобавьте список сообщений в качестве части конфигурации, чтобы связать это изменяющееся состояние с пользовательским интерфейсом:
lib/guest_book.dart
class GuestBook extends StatefulWidget {
// Modify the following line:
const GuestBook({
super.key,
required this.addMessage,
required this.messages,
});
final FutureOr<void> Function(String message) addMessage;
final List<GuestBookMessage> messages; // new
@override
State<GuestBook> createState() => _GuestBookState();
}
- В
_GuestBookStateизмените методbuildследующим образом, чтобы предоставить доступ к этой конфигурации:
lib/guest_book.dart
class _GuestBookState extends State<GuestBook> {
final _formKey = GlobalKey<FormState>(debugLabel: '_GuestBookState');
final _controller = TextEditingController();
@override
Widget build(BuildContext context) {
// Modify from here...
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// ...to here.
Padding(
padding: const EdgeInsets.all(8.0),
child: Form(
key: _formKey,
child: Row(
children: [
Expanded(
child: TextFormField(
controller: _controller,
decoration: const InputDecoration(
hintText: 'Leave a message',
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Enter your message to continue';
}
return null;
},
),
),
const SizedBox(width: 8),
StyledButton(
onPressed: () async {
if (_formKey.currentState!.validate()) {
await widget.addMessage(_controller.text);
_controller.clear();
}
},
child: Row(
children: [
Icon(Icons.send),
SizedBox(width: 4),
Text('SEND'),
],
),
),
],
),
),
),
// Modify from here...
const SizedBox(height: 8),
for (var message in widget.messages)
Paragraph('${message.name}: ${message.message}'),
const SizedBox(height: 8),
],
// ...to here.
);
}
}
Вы оборачиваете предыдущее содержимое метода build() виджетом Column , а затем добавляете коллекцию в конец дочерних элементов Column , чтобы сгенерировать новый Paragraph для каждого сообщения в списке сообщений.
- Обновите тело объекта
HomePage, чтобы корректно сформироватьGuestBookс новым параметромmessages:
lib/home_page.dart
Consumer<ApplicationState>(
builder: (context, appState, _) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (appState.loggedIn) ...[
const Header('Discussion'),
GuestBook(
addMessage: (message) =>
appState.addMessageToGuestBook(message),
messages: appState.guestBookMessages, // new
),
],
],
),
),
Проверка синхронизации сообщений
Firestore автоматически и мгновенно синхронизирует данные с клиентами, подписанными на базу данных.
Проверка синхронизации сообщений:
- В приложении найдите в базе данных сообщения, которые вы создали ранее.
- Пишите новые сообщения. Они появляются мгновенно.
- Откройте рабочее пространство в нескольких окнах или вкладках. Сообщения синхронизируются в режиме реального времени между окнами и вкладками.
- Дополнительно: В меню «База данных» консоли Firebase можно вручную удалять, изменять или добавлять новые сообщения. Все изменения отображаются в пользовательском интерфейсе.
Поздравляем! Вы прочитали документы Firestore в своем приложении!
Предварительный просмотр приложения
|
|
|
|
8. Настройте основные правила безопасности.
Изначально вы настроили Firestore на использование тестового режима, что означает, что ваша база данных открыта для чтения и записи. Однако тестовый режим следует использовать только на ранних этапах разработки. В качестве лучшей практики следует настраивать правила безопасности для вашей базы данных по мере разработки приложения. Безопасность является неотъемлемой частью структуры и поведения вашего приложения.
Правила безопасности Firebase позволяют контролировать доступ к документам и коллекциям в вашей базе данных. Гибкий синтаксис правил позволяет создавать правила, которые соответствуют чему угодно — от всех операций записи во всю базу данных до операций над конкретным документом.
Настройте основные правила безопасности:
- В меню «Разработка» консоли Firebase выберите «База данных» > «Правила» . Вы должны увидеть следующие правила безопасности по умолчанию и предупреждение о том, что эти правила являются общедоступными:

- Укажите коллекции, в которые приложение записывает данные:
В match /databases/{database}/documents укажите коллекцию, которую вы хотите защитить:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /guestbook/{entry} {
// You'll add rules here in the next step.
}
}
Поскольку вы использовали UID аутентификации в качестве поля в каждом документе гостевой книги, вы можете получить UID аутентификации и убедиться, что у любого, кто пытается записать данные в документ, совпадает UID аутентификации.
- Добавьте правила чтения и записи в свой набор правил:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /guestbook/{entry} {
allow read: if request.auth.uid != null;
allow write:
if request.auth.uid == request.resource.data.userId;
}
}
}
Теперь только авторизованные пользователи могут читать сообщения в гостевой книге, но редактировать сообщение может только его автор.
- Добавьте проверку данных, чтобы убедиться, что в документе присутствуют все необходимые поля:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /guestbook/{entry} {
allow read: if request.auth.uid != null;
allow write:
if request.auth.uid == request.resource.data.userId
&& "name" in request.resource.data
&& "text" in request.resource.data
&& "timestamp" in request.resource.data;
}
}
}
9. Дополнительный шаг: Примените полученные знания на практике.
Зафиксируйте статус подтверждения участия участника.
В настоящий момент ваше приложение позволяет пользователям общаться в чате только тогда, когда они заинтересованы в мероприятии. Кроме того, узнать о том, что кто-то придёт, можно только после того, как он сам сообщит об этом в чате.
На этом этапе вы организуете мероприятие и сообщаете людям, сколько человек придет. Вы добавляете несколько функций в состояние приложения. Первая — это возможность для авторизованного пользователя указать, будет ли он присутствовать. Вторая — это счетчик количества участников.
- В файле
lib/app_state.dartдобавьте следующие строки в раздел accessors объектаApplicationState, чтобы код пользовательского интерфейса мог взаимодействовать с этим состоянием:
lib/app_state.dart
int _attendees = 0;
int get attendees => _attendees;
Attending _attending = Attending.unknown;
StreamSubscription<DocumentSnapshot>? _attendingSubscription;
Attending get attending => _attending;
set attending(Attending attending) {
final userDoc = FirebaseFirestore.instance
.collection('attendees')
.doc(FirebaseAuth.instance.currentUser!.uid);
if (attending == Attending.yes) {
userDoc.set(<String, dynamic>{'attending': true});
} else {
userDoc.set(<String, dynamic>{'attending': false});
}
}
- Обновите метод
init()объектаApplicationStateследующим образом:
lib/app_state.dart
Future<void> init() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform);
FirebaseUIAuth.configureProviders([
EmailAuthProvider(),
]);
// Add from here...
FirebaseFirestore.instance
.collection('attendees')
.where('attending', isEqualTo: true)
.snapshots()
.listen((snapshot) {
_attendees = snapshot.docs.length;
notifyListeners();
});
// ...to here.
FirebaseAuth.instance.userChanges().listen((user) {
if (user != null) {
_loggedIn = true;
_emailVerified = user.emailVerified;
_guestBookSubscription = FirebaseFirestore.instance
.collection('guestbook')
.orderBy('timestamp', descending: true)
.snapshots()
.listen((snapshot) {
_guestBookMessages = [];
for (final document in snapshot.docs) {
_guestBookMessages.add(
GuestBookMessage(
name: document.data()['name'] as String,
message: document.data()['text'] as String,
),
);
}
notifyListeners();
});
// Add from here...
_attendingSubscription = FirebaseFirestore.instance
.collection('attendees')
.doc(user.uid)
.snapshots()
.listen((snapshot) {
if (snapshot.data() != null) {
if (snapshot.data()!['attending'] as bool) {
_attending = Attending.yes;
} else {
_attending = Attending.no;
}
} else {
_attending = Attending.unknown;
}
notifyListeners();
});
// ...to here.
} else {
_loggedIn = false;
_emailVerified = false;
_guestBookMessages = [];
_guestBookSubscription?.cancel();
_attendingSubscription?.cancel(); // new
}
notifyListeners();
});
}
Этот код добавляет постоянно активный запрос для определения количества участников и второй запрос, который активен только тогда, когда пользователь авторизован, для определения того, участвует ли пользователь в мероприятии.
- Добавьте следующее перечисление в начало файла
lib/app_state.dart.
lib/app_state.dart
enum Attending { yes, no, unknown }
- Создайте новый файл
yes_no_selection.dartи определите новый виджет, который будет работать как переключатели:
lib/yes_no_selection.dart
import 'package:flutter/material.dart';
import 'app_state.dart';
import 'src/widgets.dart';
class YesNoSelection extends StatelessWidget {
const YesNoSelection(
{super.key, required this.state, required this.onSelection});
final Attending state;
final void Function(Attending selection) onSelection;
@override
Widget build(BuildContext context) {
switch (state) {
case Attending.yes:
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
FilledButton(
onPressed: () => onSelection(Attending.yes),
child: const Text('YES'),
),
const SizedBox(width: 8),
TextButton(
onPressed: () => onSelection(Attending.no),
child: const Text('NO'),
),
],
),
);
case Attending.no:
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
TextButton(
onPressed: () => onSelection(Attending.yes),
child: const Text('YES'),
),
const SizedBox(width: 8),
FilledButton(
onPressed: () => onSelection(Attending.no),
child: const Text('NO'),
),
],
),
);
default:
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
StyledButton(
onPressed: () => onSelection(Attending.yes),
child: const Text('YES'),
),
const SizedBox(width: 8),
StyledButton(
onPressed: () => onSelection(Attending.no),
child: const Text('NO'),
),
],
),
);
}
}
}
Изначально всё находится в неопределённом состоянии, без выбора «Да» или «Нет» . После того, как пользователь выберет, будет ли он присутствовать, этот вариант отображается выделенной кнопкой, а другой вариант скрывается, оставаясь без предварительного выбора.
- Обновите метод
build()классаHomePage, чтобы он использовал преимуществаYesNoSelection, дайте авторизованному пользователю возможность указать, будет ли он присутствовать, и отобразите количество участников мероприятия:
lib/home_page.dart
import 'yes_no_selection.dart'; // new
Consumer<ApplicationState>(
builder: (context, appState, _) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Add from here...
switch (appState.attendees) {
1 => const Paragraph('1 person going'),
>= 2 => Paragraph('${appState.attendees} people going'),
_ => const Paragraph('No one going'),
},
// ...to here.
if (appState.loggedIn) ...[
// Add from here...
YesNoSelection(
state: appState.attending,
onSelection: (attending) => appState.attending = attending,
),
// ...to here.
const Header('Discussion'),
GuestBook(
addMessage: (message) =>
appState.addMessageToGuestBook(message),
messages: appState.guestBookMessages,
),
],
],
),
),
Добавить правила
Вы уже настроили некоторые правила, поэтому данные, которые вы добавляете с помощью кнопок, будут отклонены. Вам необходимо обновить правила, чтобы разрешить добавление attendees в коллекцию.
- В коллекции
attendeesнайдите UID аутентификации, который вы использовали в качестве имени документа, и убедитесь, чтоuidотправителя совпадает с UID документа, который он пишет:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// ... //
match /attendees/{userId} {
allow read: if true;
allow write: if request.auth.uid == userId;
}
}
}
Это позволяет всем желающим ознакомиться со списком участников, поскольку в нем нет конфиденциальных данных, и только создатель мероприятия может его обновлять.
- Добавьте проверку данных, чтобы убедиться, что в документе присутствуют все необходимые поля:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// ... //
match /attendees/{userId} {
allow read: if true;
allow write: if request.auth.uid == userId
&& "attending" in request.resource.data;
}
}
}
- Дополнительно: В приложении нажмите кнопки, чтобы просмотреть результаты на панели мониторинга Firestore в консоли Firebase.
Предварительный просмотр приложения
|
|
|
|
10. Поздравляем!
Вы использовали Firebase для создания интерактивного веб-приложения, работающего в режиме реального времени!















