1. Прежде чем начать
В этой лаборатории кода вы изучите некоторые основы Firebase для создания мобильных приложений Flutter для Android и iOS.
Предпосылки
- Знакомство с флаттером
- Флаттер SDK
- Текстовый редактор на ваш выбор
Что вы узнаете
- Как создать приложение для RSVP и гостевой книги на 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.
-
Firebase
CLI вошел в вашу учетную запись Google.
2. Получите пример кода
Загрузите начальную версию вашего проекта с GitHub:
- Из командной строки клонируйте репозиторий GitHub в директорию
flutter-codelabs
:
git clone https://github.com/flutter/codelabs.git flutter-codelabs
Каталог flutter-codelabs
содержит код для коллекции 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 Meetup.
Найдите файлы, которые нуждаются в работе
Код в этом приложении распределен по нескольким каталогам. Такое разделение функций упрощает работу, поскольку оно группирует код по функциям.
- Найдите следующие файлы:
-
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 .
- В консоли нажмите «Добавить проект» или «Создать проект» .
- В поле «Название проекта» введите Firebase-Flutter-Codelab и нажмите «Продолжить» .
- Нажмите на параметры создания проекта. При появлении запроса примите условия Firebase, но пропустите настройку Google Analytics, поскольку вы не будете использовать ее для этого приложения.
Дополнительные сведения о проектах Firebase см. в статье Общие сведения о проектах Firebase .
Приложение использует следующие продукты Firebase, доступные для веб-приложений:
- Аутентификация: позволяет пользователям входить в ваше приложение.
- Firestore: сохраняет структурированные данные в облаке и получает мгновенные уведомления при изменении данных.
- Правила безопасности Firebase: защищает вашу базу данных.
Некоторые из этих продуктов требуют специальной настройки или их необходимо включить в консоли Firebase.
Включить аутентификацию для входа по электронной почте
- На панели обзора проекта консоли Firebase разверните меню «Сборка» .
- Нажмите «Аутентификация» > «Начать» > «Метод входа» > «Электронная почта/пароль» > «Включить» > «Сохранить» .
Включить Firestore
Веб-приложение использует Firestore для сохранения сообщений чата и получения новых сообщений чата.
Включить Firestore:
- В меню «Сборка» нажмите «Cloud Firestore» > «Создать базу данных» .
- Выберите Запустить в тестовом режиме , а затем прочитайте заявление об отказе от ответственности за правила безопасности. Тестовый режим гарантирует, что вы можете свободно писать в базу данных во время разработки.
- Нажмите «Далее» , а затем выберите место для вашей базы данных. Вы можете использовать значение по умолчанию. Вы не можете изменить местоположение позже.
- Щелкните Включить .
4. Настройте Firebase
Чтобы использовать Firebase с Flutter, вам необходимо выполнить следующие задачи, чтобы настроить проект Flutter для правильного использования библиотек FlutterFire
:
- Добавьте зависимости
FlutterFire
в свой проект. - Зарегистрируйте нужную платформу в проекте Firebase.
- Загрузите файл конфигурации для конкретной платформы, а затем добавьте его в код.
В каталоге верхнего уровня вашего приложения Flutter есть подкаталоги android
, ios
, macos
и web
, которые содержат файлы конфигурации для конкретной платформы для iOS и Android соответственно.
Настроить зависимости
Вам нужно добавить библиотеки FlutterFire
для двух продуктов Firebase, которые вы используете в этом приложении: Authentication и Firestore.
- Из командной строки добавьте следующие зависимости:
$ flutter pub add firebase_core
Пакет firebase_core
— это общий код, необходимый для всех плагинов Firebase Flutter.
$ flutter pub add firebase_auth
Пакет firebase_auth
обеспечивает интеграцию с аутентификацией.
$ flutter pub add cloud_firestore
Пакет cloud_firestore
обеспечивает доступ к хранилищу данных Firestore.
$ flutter pub add provider
Пакет firebase_ui_auth
предоставляет набор виджетов и утилит для увеличения скорости разработки с потоками аутентификации.
$ flutter pub add firebase_ui_auth
Вы добавили необходимые пакеты, но вам также необходимо настроить проекты iOS, Android, macOS и Web runner для надлежащего использования Firebase. Вы также используете пакет provider
, который позволяет отделить бизнес-логику от логики отображения.
Установите интерфейс командной строки FlutterFire
Интерфейс командной строки FlutterFire зависит от базового интерфейса командной строки Firebase.
- Если вы еще этого не сделали, установите интерфейс командной строки Firebase на свой компьютер.
- Установите интерфейс командной строки FlutterFire:
$ dart pub global activate flutterfire_cli
После установки команда flutterfire
доступна глобально.
Настройте свои приложения
CLI извлекает информацию из вашего проекта Firebase и выбранных приложений проекта, чтобы сгенерировать всю конфигурацию для конкретной платформы.
В корне вашего приложения запустите команду configure
:
$ flutterfire configure
Команда конфигурации проведет вас через следующие процессы:
- Выберите проект Firebase на основе файла
.firebaserc
или в консоли Firebase. - Определите платформы для настройки, такие как Android, iOS, macOS и Интернет.
- Определите приложения Firebase, из которых нужно извлечь конфигурацию. По умолчанию интерфейс командной строки пытается автоматически сопоставить приложения 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
:
библиотека/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
:
библиотека/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
:
библиотека/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.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,
),
primarySwatch: Colors.deepPurple,
textTheme: GoogleFonts.robotoTextTheme(
Theme.of(context).textTheme,
),
visualDensity: VisualDensity.adaptivePlatformDensity,
useMaterial3: true,
),
routerConfig: _router, // new
);
}
}
С каждым экраном связан другой тип действия, основанный на новом состоянии потока аутентификации. После большинства изменений состояния проверки подлинности вы можете вернуться к предпочтительному экрану, будь то главный экран или другой экран, например профиль.
- В методе сборки класса
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({Key? key}) : super(key: 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;
_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();
});
} else {
_loggedIn = false;
_guestBookMessages = [];
_guestBookSubscription?.cancel();
}
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
_GuestBookState createState() => _GuestBookState();
}
- В
_GuestBookState
измените методbuild
следующим образом, чтобы предоставить эту конфигурацию:
lib/guest_book.dart
class _GuestBookState extends State<GuestBook> {
final _formKey = GlobalKey<FormState>(debugLabel: '_GuestBookState');
final _controller = TextEditingController();
@override
// Modify from here...
Widget build(BuildContext context) {
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: const [
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
, а затем добавляете коллекцию for в конце дочерних элементов 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. Бонусный шаг: Практика того, что вы узнали
Запись статуса RSVP участника
Прямо сейчас ваше приложение позволяет людям общаться в чате только тогда, когда они заинтересованы в событии. Кроме того, единственный способ узнать, придет ли кто-то, — это сказать об этом в чате.
На этом этапе вы организуетесь и сообщаете людям, сколько людей придет. Вы добавляете пару возможностей в состояние приложения. Во-первых, это возможность для вошедшего в систему пользователя указать, присутствует ли он на мероприятии. Второй – это счетчик количества присутствующих.
- В файле
lib/app_state.dart
добавьте следующие строки в раздел средств доступа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) {
_loginState = ApplicationLoginState.loggedIn;
_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 {
_loginState = ApplicationLoginState.loggedOut;
_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
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
отправителя совпадает с документом, который они пишут:
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 для создания интерактивного веб-приложения, работающего в реальном времени!