1. Прежде чем начать
В этой лаборатории кода вы изучите некоторые основы Firebase для создания мобильных приложений Flutter для Android и iOS.
Предпосылки
В этой кодовой лаборатории предполагается, что вы знакомы с Flutter, установили Flutter SDK и редактор .
Что вы создадите
В этой лаборатории кода вы создадите приложение для RSVP и гостевой книги для Android, iOS, Интернета и macOS с использованием Flutter. Вы будете аутентифицировать пользователей с помощью Firebase Authentication и синхронизировать данные с помощью Cloud Firestore.
Что вам понадобится
Вы можете запустить эту лабораторию кода, используя любое из следующих устройств:
- Физическое устройство (Android или iOS), подключенное к вашему компьютеру и переведенное в режим разработчика.
- Симулятор iOS. (Требуется установка инструментов Xcode .)
- Эмулятор Android. (Требуется установка в Android Studio .)
Помимо вышеперечисленного, вам также понадобятся:
- Браузер на ваш выбор, например Chrome.
- IDE или текстовый редактор по вашему выбору, например Android Studio или VS Code , настроенный с подключаемыми модулями Dart и Flutter.
- Последняя
stable
версия Flutter (илиbeta
, если вам нравится жить на грани). - Учетная запись Google, например учетная запись Gmail, для создания и управления вашим проектом Firebase.
- Инструмент командной строки
firebase
, вошедший в вашу учетную запись gmail. - Пример кода лаборатории кода. См. следующий шаг, чтобы узнать, как получить код.
2. Получите пример кода
Начнем со скачивания начальной версии нашего проекта с GitHub.
Клонируйте репозиторий GitHub из командной строки:
git clone https://github.com/flutter/codelabs.git flutter-codelabs
В качестве альтернативы, если у вас установлен инструмент GitHub cli :
gh repo clone flutter/codelabs flutter-codelabs
Образец кода должен быть клонирован в каталог flutter-codelabs
, который содержит код для коллекции codelabs. Код для этой кодлабы находится в flutter-codelabs/firebase-get-to-know-flutter
.
Структура каталогов под flutter-codelabs/firebase-get-to-know-flutter
представляет собой серию снимков того, где вы должны находиться в конце каждого именованного шага. Это шаг 2, поэтому найти соответствующие файлы так же просто, как:
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/src/widgets.dart
: этот файл содержит несколько виджетов, помогающих стандартизировать стиль приложения. Они используются для компоновки экрана начального приложения. -
lib/src/authentication.dart
: этот файл содержит частичную реализацию аутентификации FirebaseUI с набором виджетов для создания пользовательского интерфейса для аутентификации на основе электронной почты 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 нажмите «Добавить проект» (или «Создать проект» ) и назовите свой проект Firebase Firebase-Flutter-Codelab .
- Нажмите на параметры создания проекта. Примите условия Firebase, если будет предложено. Пропустите настройку Google Analytics, потому что вы не будете использовать Analytics для этого приложения.
Дополнительные сведения о проектах Firebase см. в статье Общие сведения о проектах Firebase .
Приложение, которое вы создаете, использует несколько продуктов Firebase, доступных для веб-приложений:
- Firebase Authentication , чтобы ваши пользователи могли входить в ваше приложение.
- Cloud Firestore для сохранения структурированных данных в облаке и получения мгновенных уведомлений при изменении данных.
- Правила безопасности Firebase для защиты вашей базы данных.
Некоторые из этих продуктов требуют специальной настройки или должны быть включены с помощью консоли Firebase.
Включить вход по электронной почте для аутентификации Firebase
Чтобы разрешить пользователям входить в веб-приложение, вы будете использовать метод входа по электронной почте/паролю для этой лаборатории кода:
- В консоли Firebase разверните меню « Сборка » на левой панели.
- Нажмите « Аутентификация » , затем нажмите кнопку « Начать », затем вкладку « Метод входа » (или щелкните здесь, чтобы перейти непосредственно на вкладку « Метод входа »).
- Нажмите « Электронная почта/пароль » в списке « Поставщики входа », установите переключатель « Включить » в положение «Вкл.», а затем нажмите « Сохранить » .
Включить облачное хранилище Firestore
Веб-приложение использует Cloud Firestore для сохранения сообщений чата и получения новых сообщений чата.
Включить облачное хранилище Firestore:
- В разделе « Сборка » консоли Firebase нажмите « Cloud Firestore ».
- Щелкните Создать базу данных .
- Выберите опцию Запустить в тестовом режиме . Прочитайте отказ от ответственности о правилах безопасности. Тестовый режим гарантирует, что вы можете свободно писать в базу данных во время разработки. Нажмите «Далее» .
- Выберите место для вашей базы данных (вы можете просто использовать значение по умолчанию). Обратите внимание, что это местоположение нельзя будет изменить позже.
- Щелкните Включить .
4. Конфигурация базы данных
Чтобы использовать Firebase с Flutter, вам необходимо выполнить процесс настройки проекта Flutter для правильного использования библиотек FlutterFire:
- Добавьте зависимости FlutterFire в свой проект.
- Зарегистрируйте нужную платформу в проекте Firebase.
- Загрузите файл конфигурации для конкретной платформы и добавьте его в код.
В каталоге верхнего уровня вашего приложения Flutter есть подкаталоги с именами android
, ios
, macos
и web
. Эти каталоги содержат файлы конфигурации для конкретных платформ для iOS и Android соответственно.
Настроить зависимости
Вам необходимо добавить библиотеки FlutterFire для двух продуктов Firebase, которые вы используете в этом приложении — Firebase Auth и Cloud Firestore. Выполните следующие три команды, чтобы добавить зависимости.
$ flutter pub add firebase_core Resolving dependencies... + firebase_core 1.10.5 + firebase_core_platform_interface 4.2.2 + firebase_core_web 1.5.2 + flutter_web_plugins 0.0.0 from sdk flutter + js 0.6.3 test_api 0.4.3 (0.4.8 available) Changed 5 dependencies!
firebase_core
— это общий код, необходимый для всех плагинов Firebase Flutter.
$ flutter pub add firebase_auth Resolving dependencies... + firebase_auth 3.3.3 + firebase_auth_platform_interface 6.1.8 + firebase_auth_web 3.3.4 + intl 0.17.0 test_api 0.4.3 (0.4.8 available) Changed 4 dependencies!
firebase_auth
обеспечивает интеграцию с функцией аутентификации Firebase.
$ flutter pub add cloud_firestore Resolving dependencies... + cloud_firestore 3.1.4 + cloud_firestore_platform_interface 5.4.9 + cloud_firestore_web 2.6.4 test_api 0.4.3 (0.4.8 available) Changed 3 dependencies!
cloud_firestore
обеспечивает доступ к хранилищу данных Cloud Firestore.
$ flutter pub add provider Resolving dependencies... + nested 1.0.0 + provider 6.0.1 test_api 0.4.3 (0.4.8 available) Changed 2 dependencies!
Хотя вы добавили необходимые пакеты, вам также необходимо настроить проекты iOS, Android, macOS и Web runner для надлежащего использования Firebase. Вы также используете пакет provider
, который позволит отделить бизнес-логику от логики отображения.
Установка flutterfire
Интерфейс командной строки FlutterFire зависит от базового интерфейса командной строки Firebase. Если вы еще этого не сделали, убедитесь, что Firebase CLI установлен на вашем компьютере.
Затем установите интерфейс командной строки 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>
Дополнительные сведения см. в разделе Права и песочница приложения .
5. Добавьте вход пользователя (RSVP)
Теперь, когда вы добавили Firebase в приложение, вы можете настроить кнопку RSVP, которая регистрирует людей с помощью Firebase Authentication . Для нативного Android, нативного iOS и Интернета есть предварительно созданные пакеты аутентификации FirebaseUI, но для Flutter вам нужно будет создать эту возможность.
Проект, который вы получили на шаге 2, включал набор виджетов, реализующих пользовательский интерфейс для большей части потока аутентификации. Вы реализуете бизнес-логику для интеграции Firebase Authentication в приложение.
Бизнес-логика с провайдером
Вы собираетесь использовать пакет provider
, чтобы сделать объект централизованного состояния приложения доступным в дереве виджетов Flutter приложения. Для начала измените импорт в верхней части lib/main.dart
:
библиотека/main.dart
import 'package:firebase_auth/firebase_auth.dart'; // new
import 'package:firebase_core/firebase_core.dart'; // new
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart'; // new
import 'firebase_options.dart'; // new
import 'src/authentication.dart'; // new
import 'src/widgets.dart';
Строки import
представляют Firebase Core и Auth, загружают пакет provider
, который вы используете, чтобы сделать объект состояния приложения доступным через дерево виджетов, и включают виджеты аутентификации из lib/src
.
Этот объект состояния приложения, ApplicationState
, имеет две основные обязанности на этом шаге, но получит дополнительные обязанности по мере того, как вы добавите дополнительные возможности в приложение на последующих шагах. Первая обязанность — инициализировать библиотеку Firebase с помощью вызова Firebase.initializeApp()
, а затем — обработка потока авторизации. Добавьте следующий класс в конец lib/main.dart
:
библиотека/main.dart
class ApplicationState extends ChangeNotifier {
ApplicationState() {
init();
}
Future<void> init() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
FirebaseAuth.instance.userChanges().listen((user) {
if (user != null) {
_loginState = ApplicationLoginState.loggedIn;
} else {
_loginState = ApplicationLoginState.loggedOut;
}
notifyListeners();
});
}
ApplicationLoginState _loginState = ApplicationLoginState.loggedOut;
ApplicationLoginState get loginState => _loginState;
String? _email;
String? get email => _email;
void startLoginFlow() {
_loginState = ApplicationLoginState.emailAddress;
notifyListeners();
}
Future<void> verifyEmail(
String email,
void Function(FirebaseAuthException e) errorCallback,
) async {
try {
var methods =
await FirebaseAuth.instance.fetchSignInMethodsForEmail(email);
if (methods.contains('password')) {
_loginState = ApplicationLoginState.password;
} else {
_loginState = ApplicationLoginState.register;
}
_email = email;
notifyListeners();
} on FirebaseAuthException catch (e) {
errorCallback(e);
}
}
Future<void> signInWithEmailAndPassword(
String email,
String password,
void Function(FirebaseAuthException e) errorCallback,
) async {
try {
await FirebaseAuth.instance.signInWithEmailAndPassword(
email: email,
password: password,
);
} on FirebaseAuthException catch (e) {
errorCallback(e);
}
}
void cancelRegistration() {
_loginState = ApplicationLoginState.emailAddress;
notifyListeners();
}
Future<void> registerAccount(
String email,
String displayName,
String password,
void Function(FirebaseAuthException e) errorCallback) async {
try {
var credential = await FirebaseAuth.instance
.createUserWithEmailAndPassword(email: email, password: password);
await credential.user!.updateDisplayName(displayName);
} on FirebaseAuthException catch (e) {
errorCallback(e);
}
}
void signOut() {
FirebaseAuth.instance.signOut();
}
}
Стоит отметить несколько ключевых моментов в этом классе. Пользователь начинает работу без аутентификации, приложение показывает форму, запрашивающую адрес электронной почты пользователя, в зависимости от того, находится ли этот адрес электронной почты в файле, приложение либо попросит пользователя зарегистрироваться, либо запросит его пароль, а затем, если все сработает, пользователь аутентифицирован.
Следует отметить, что это не полная реализация потока аутентификации FirebaseUI, поскольку он не обрабатывает случай пользователя с существующей учетной записью, у которого возникают проблемы со входом в систему. Реализация этой дополнительной возможности остается в качестве упражнения для мотивированный читатель.
Интеграция потока аутентификации
Теперь, когда у вас есть начало состояния приложения, пришло время связать состояние приложения с инициализацией приложения и добавить поток проверки подлинности в HomePage
. Обновите основную точку входа, чтобы интегрировать состояние приложения через пакет provider
:
библиотека/main.dart
void main() {
// Modify from here
runApp(
ChangeNotifierProvider(
create: (context) => ApplicationState(),
builder: (context, _) => App(),
),
);
// to here.
}
Модификация main
функции делает пакет провайдера ответственным за создание экземпляра объекта состояния приложения с помощью виджета ChangeNotifierProvider
. Вы используете этот конкретный класс провайдера, потому что объект состояния приложения расширяет ChangeNotifier
и это позволяет пакету provider
знать, когда повторно отображать зависимые виджеты. Наконец, интегрируйте состояние приложения с Authentication
, обновив метод build
HomePage
:
библиотека/main.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'),
// Add from here
Consumer<ApplicationState>(
builder: (context, appState, _) => Authentication(
email: appState.email,
loginState: appState.loginState,
startLoginFlow: appState.startLoginFlow,
verifyEmail: appState.verifyEmail,
signInWithEmailAndPassword: appState.signInWithEmailAndPassword,
cancelRegistration: appState.cancelRegistration,
registerAccount: appState.registerAccount,
signOut: appState.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!',
),
],
),
);
}
}
Вы создаете виджет Authentication
и заключаете его в виджет Consumer
. Виджет Consumer — это обычный способ использования пакета provider
для перестроения части дерева при изменении состояния приложения. Виджет Authentication
— это пользовательский интерфейс проверки подлинности, который вы сейчас будете тестировать.
Тестирование потока аутентификации
Вот начало процесса аутентификации, когда пользователь может нажать на кнопку RSVP, чтобы инициировать форму электронной почты.
При вводе электронной почты система подтверждает, зарегистрирован ли пользователь, и в этом случае у пользователя запрашивается пароль, или, если пользователь не зарегистрирован, он проходит через регистрационную форму.
Обязательно попробуйте ввести короткий пароль (менее шести символов), чтобы проверить процесс обработки ошибок. Если пользователь зарегистрирован, вместо него он увидит пароль.
На этой странице убедитесь, что введены неправильные пароли, чтобы проверить обработку ошибок на этой странице. Наконец, как только пользователь войдет в систему, вы увидите вход в систему, который предлагает пользователю возможность снова выйти из системы.
И с этим вы внедрили поток аутентификации. Поздравляю!
6. Пишите сообщения в Cloud Firestore
Знать, что пользователи приходят, — это здорово, но давайте дадим гостям что-нибудь еще в приложении. Что, если бы они могли оставлять сообщения в гостевой книге? Они могут рассказать, почему они рады приехать или с кем надеются встретиться.
Чтобы хранить сообщения чата, которые пользователи пишут в приложении, вы будете использовать Cloud Firestore .
Модель данных
Cloud Firestore — это база данных NoSQL, и данные, хранящиеся в базе данных, разделены на коллекции, документы, поля и подколлекции. Вы будете хранить каждое сообщение чата как документ в коллекции верхнего уровня, называемой guestbook
.
Добавить сообщения в Firestore
В этом разделе вы добавите пользователям возможность записывать новые сообщения в базу данных. Сначала вы добавляете элементы пользовательского интерфейса (поле формы и кнопку отправки), а затем добавляете код, который связывает эти элементы с базой данных.
Сначала добавьте импорт для пакета cloud_firestore
и dart:async
.
библиотека/main.dart
import 'dart:async'; // new
import 'package:cloud_firestore/cloud_firestore.dart'; // new
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';
import 'firebase_options.dart';
import 'src/authentication.dart';
import 'src/widgets.dart';
Чтобы создать элементы пользовательского интерфейса для поля сообщения и кнопки отправки, добавьте новый виджет GuestBook
с отслеживанием состояния в нижней части lib/main.dart
.
библиотека/main.dart
class GuestBook extends StatefulWidget {
const GuestBook({required this.addMessage});
final FutureOr<void> Function(String message) addMessage;
@override
_GuestBookState 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
. Для получения дополнительной информации о ключах и о том, как их использовать, см. эпизод Flutter Widgets 101 «Когда использовать ключи» .
Также обратите внимание на расположение виджетов: у вас есть 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
— это ссылка на автоматически сгенерированный уникальный идентификатор, который Firebase Authentication предоставляет всем вошедшим в систему пользователям.
Внесите еще одно изменение в файл lib/main.dart
. Добавьте метод addMessageToGuestBook
. На следующем шаге вы соедините пользовательский интерфейс и эту возможность вместе.
библиотека/main.dart
class ApplicationState extends ChangeNotifier {
// Current content of ApplicationState elided ...
// Add from here
Future<DocumentReference> addMessageToGuestBook(String message) {
if (_loginState != ApplicationLoginState.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
}
Подключение пользовательского интерфейса к базе данных
У вас есть пользовательский интерфейс, в котором пользователь может ввести текст, который он хочет добавить в гостевую книгу, и у вас есть код для добавления записи в Cloud Firestore. Теперь все, что вам нужно сделать, это соединить их вместе. В lib/main.dart
внесите следующие изменения в виджет HomePage
.
библиотека/main.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, _) => Authentication(
email: appState.email,
loginState: appState.loginState,
startLoginFlow: appState.startLoginFlow,
verifyEmail: appState.verifyEmail,
signInWithEmailAndPassword: appState.signInWithEmailAndPassword,
cancelRegistration: appState.cancelRegistration,
registerAccount: appState.registerAccount,
signOut: appState.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.loginState == ApplicationLoginState.loggedIn) ...[
const Header('Discussion'),
GuestBook(
addMessage: (message) =>
appState.addMessageToGuestBook(message),
),
],
],
),
),
// To here.
],
),
);
}
}
Вы заменили две строки, добавленные в начале этого шага, полной реализацией. Вы снова используете Consumer<ApplicationState>
, чтобы сделать состояние приложения доступным для той части дерева, которую вы визуализируете. Это позволяет вам реагировать на то, что кто-то вводит сообщение в пользовательском интерфейсе, и публиковать его в базе данных. В следующем разделе вы проверите, публикуются ли добавленные сообщения в базе данных.
Тестовая отправка сообщений
- Убедитесь, что вы вошли в приложение.
- Введите сообщение, например "Привет!", а затем нажмите ОТПРАВИТЬ .
Это действие записывает сообщение в вашу базу данных Cloud Firestore. Однако вы еще не увидите это сообщение в своем реальном приложении Flutter, потому что вам все еще нужно реализовать извлечение данных. Вы сделаете это на следующем шаге.
Но вы можете увидеть только что добавленное сообщение в консоли Firebase.
В консоли Firebase на панели управления базой данных вы должны увидеть коллекцию guestbook
с добавленным вами сообщением. Если вы продолжите отправлять сообщения, ваша коллекция гостевой книги будет содержать много документов, например:
Консоль Firebase
7. Читать сообщения
Прекрасно, что гости могут писать сообщения в базу данных, но пока не видят их в приложении. Давайте исправим это!
Синхронизировать сообщения
Чтобы отображать сообщения, вам нужно добавить прослушиватели, которые срабатывают при изменении данных, а затем создать элемент пользовательского интерфейса, который показывает новые сообщения. Вы добавите в состояние приложения код, который прослушивает новые добавленные сообщения из приложения.
Чуть выше виджета GuestBook
книги находится следующий класс значений. Этот класс предоставляет структурированное представление данных, которые вы храните в Cloud Firestore.
библиотека/main.dart
class GuestBookMessage {
GuestBookMessage({required this.name, required this.message});
final String name;
final String message;
}
В разделе ApplicationState
, где вы определяете состояние и геттеры, добавьте следующие новые строки:
библиотека/main.dart
ApplicationLoginState _loginState = ApplicationLoginState.loggedOut;
ApplicationLoginState get loginState => _loginState;
String? _email;
String? get email => _email;
// Add from here
StreamSubscription<QuerySnapshot>? _guestBookSubscription;
List<GuestBookMessage> _guestBookMessages = [];
List<GuestBookMessage> get guestBookMessages => _guestBookMessages;
// to here.
И, наконец, в разделе инициализации ApplicationState
добавьте следующее, чтобы подписаться на запрос к коллекции документов, когда пользователь входит в систему, и отказаться от подписки, когда они выходят из системы.
библиотека/main.dart
Future<void> init() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
FirebaseAuth.instance.userChanges().listen((user) {
if (user != null) {
_loginState = ApplicationLoginState.loggedIn;
// 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 {
_loginState = ApplicationLoginState.loggedOut;
// Add from here
_guestBookMessages = [];
_guestBookSubscription?.cancel();
// to here.
}
notifyListeners();
});
}
Этот раздел важен, так как здесь вы создаете запрос к коллекции guestbook
и обрабатываете подписку и отмену подписки на эту коллекцию. Вы слушаете поток, где восстанавливаете локальный кеш сообщений в коллекции guestbook
книги, а также сохраняете ссылку на эту подписку, чтобы потом от нее можно было отписаться. Здесь много чего происходит, и стоит потратить некоторое время на отладчик, проверяя, что происходит, когда получить более четкую ментальную модель.
Для получения дополнительной информации см. документацию Cloud Firestore .
В виджете « GuestBook
книга» нужно подключить это изменяющееся состояние к пользовательскому интерфейсу. Вы изменяете виджет, добавляя список сообщений как часть его конфигурации.
библиотека/main.dart
class GuestBook extends StatefulWidget {
// Modify the following line
const GuestBook({required this.addMessage, required this.messages});
final FutureOr<void> Function(String message) addMessage;
final List<GuestBookMessage> messages; // new
@override
_GuestBookState createState() => _GuestBookState();
}
Затем мы раскрываем эту новую конфигурацию в _GuestBookState
, изменяя метод build
следующим образом.
библиотека/main.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.
);
}
}
Вы оборачиваете предыдущее содержимое метода сборки с помощью виджета Column
, а затем в конце дочерних элементов Column
добавляете коллекцию для создания нового Paragraph
для каждого сообщения в списке сообщений.
Наконец, теперь вам нужно обновить тело HomePage
, чтобы правильно построить GuestBook
с новым параметром messages
.
библиотека/main.dart
Consumer<ApplicationState>(
builder: (context, appState, _) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (appState.loginState == ApplicationLoginState.loggedIn) ...[
const Header('Discussion'),
GuestBook(
addMessage: (message) =>
appState.addMessageToGuestBook(message),
messages: appState.guestBookMessages, // new
),
],
],
),
),
Проверка синхронизации сообщений
Cloud Firestore автоматически и мгновенно синхронизирует данные с клиентами, подписанными на базу данных.
- Сообщения, созданные ранее в базе данных, должны отображаться в приложении. Не стесняйтесь писать новые сообщения; они должны появиться мгновенно.
- Если вы откроете свое рабочее пространство в нескольких окнах или вкладках, сообщения будут синхронизироваться в режиме реального времени между вкладками.
- (Необязательно) Вы можете попробовать вручную удалить, изменить или добавить новые сообщения непосредственно в разделе « База данных » консоли Firebase; любые изменения должны появиться в пользовательском интерфейсе.
Поздравляем! Вы читаете документы Cloud Firestore в своем приложении!
Обзор приложения
8. Установите основные правила безопасности
Изначально вы настроили Cloud Firestore для использования тестового режима, что означает, что ваша база данных открыта для чтения и записи. Однако вам следует использовать тестовый режим только на очень ранних стадиях разработки. Рекомендуется настроить правила безопасности для базы данных при разработке приложения. Безопасность должна быть неотъемлемой частью структуры и поведения вашего приложения.
Правила безопасности позволяют контролировать доступ к документам и коллекциям в базе данных. Гибкий синтаксис правил позволяет создавать правила, которые соответствуют чему угодно, от всех операций записи во всю базу данных до операций над конкретным документом.
Вы можете написать правила безопасности для Cloud 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/main.dart
добавьте следующее в раздел средств доступа, чтобы код пользовательского интерфейса мог взаимодействовать с этим состоянием:
библиотека/main.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
следующим образом:
библиотека/main.dart
Future<void> init() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
// 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();
});
}
Вышеупомянутый добавляет всегда подписанный запрос, чтобы узнать количество посетителей, и второй запрос, который активен только тогда, когда пользователь вошел в систему, чтобы узнать, посещает ли пользователь. Затем добавьте следующее перечисление после объявления GuestBookMessage
:
библиотека/main.dart
enum Attending { yes, no, unknown }
Теперь вы собираетесь определить новый виджет, который действует как старые радиокнопки. Он начинается в неопределенном состоянии, когда ни да, ни нет не выбраны, но как только пользователь выбирает, посещают ли они его или нет, вы показываете этот вариант, выделенный с помощью заполненной кнопки, а другой вариант отступает с плоской визуализацией.
библиотека/main.dart
class YesNoSelection extends StatelessWidget {
const YesNoSelection({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: [
ElevatedButton(
style: ElevatedButton.styleFrom(elevation: 0),
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),
ElevatedButton(
style: ElevatedButton.styleFrom(elevation: 0),
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'),
),
],
),
);
}
}
}
Затем вам нужно обновить метод сборки HomePage
, чтобы использовать преимущества YesNoSelection
, позволяя вошедшему в систему пользователю назначать, посещают ли они его. Вы также отобразите количество участников этого события.
библиотека/main.dart
Consumer<ApplicationState>(
builder: (context, appState, _) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Add from here
if (appState.attendees >= 2)
Paragraph('${appState.attendees} people going')
else if (appState.attendees == 1)
const Paragraph('1 person going')
else
const Paragraph('No one going'),
// To here.
if (appState.loginState == ApplicationLoginState.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;
}
}
}
(Необязательно) Теперь вы можете просматривать результаты нажатия кнопок. Перейдите на панель инструментов Cloud Firestore в консоли Firebase.
Предварительный просмотр приложения
10. Поздравляем!
Вы использовали Firebase для создания интерактивного веб-приложения, работающего в реальном времени!
Что мы рассмотрели
- Аутентификация Firebase
- Облако Firestore
- Правила безопасности Firebase
Следующие шаги
- Хотите узнать больше о других продуктах Firebase? Может быть, вы хотите хранить файлы изображений, которые загружают пользователи? Или отправлять уведомления своим пользователям? Ознакомьтесь с документацией Firebase . Хотите узнать больше о плагинах Flutter для Firebase? Посетите FlutterFire для получения дополнительной информации.
- Хотите узнать больше о Cloud Firestore? Может быть, вы хотите узнать о подколлекциях и транзакциях? Перейдите в веб-лабораторию кода Cloud Firestore, чтобы получить более подробную информацию о Cloud Firestore. Или посмотрите эту серию YouTube, чтобы познакомиться с Cloud Firestore !
Учить больше
- Сайт Firebase: firebase.google.com
- Сайт флаттера: flutter.dev
- Виджеты FlutterFire Firebase Flutter: firebase.flutter.dev
- Канал Firebase на YouTube
- Канал флаттера на YouTube
Как прошло?
Будем рады вашему отзыву! Пожалуйста, заполните (очень) короткую форму здесь .