Познакомьтесь с Firebase для Flutter

1. Прежде чем начать

В этом codelab, вы узнаете некоторые из основ Firebase для создания флаттера мобильных приложений для Android и IOS.

Предпосылки

Это codelab предполагает , что вы знакомы с флаттером, и вы установили флаттер SDK и редактор .

Что вы создадите

В этой лаборатории кода вы создадите приложение для RSVP и гостевой книги для Android, iOS, Интернета и macOS с использованием Flutter. Вы будете аутентифицировать пользователей с помощью Firebase Authentication и синхронизировать данные с помощью Cloud Firestore.

Что вам понадобится

Вы можете запустить эту лабораторию кода, используя любое из следующих устройств:

  • Физическое устройство (Android или iOS), подключенное к вашему компьютеру и переведенное в режим разработчика.
  • Симулятор iOS. (Требует установки инструментов Xcode .)
  • Эмулятор Android. (Требует установки в Android Studio .)

Помимо вышеперечисленного, вам также понадобятся:

  • Браузер на ваш выбор, например Chrome.
  • IDE или текстовый редактор вашего выбора, такие как Android Studio или VS кодекс сконфигурированных с плагиными Dart и флаттера.
  • Последняя stable версия флаттера (или beta , если вам нравится жить на краю).
  • Учетная запись Google, например учетная запись Gmail, для создания и управления вашим проектом Firebase.
  • firebase инструмент командной строки , вошли в свой аккаунт Gmail.
  • Пример кода лаборатории кода. См. следующий шаг, чтобы узнать, как получить код.

2. Получите пример кода

Начнем со скачивания начальной версии нашего проекта с GitHub.

Клон хранилище GitHub из командной строки:

git clone https://github.com/flutter/codelabs.git flutter-codelabs

С другой стороны , если у вас есть Cli GitHub в инструмент установлен:

gh repo clone flutter/codelabs flutter-codelabs

Пример кода должен быть клонирован в flutter-codelabs каталог, который содержит код для сбора codelabs. Код для этого codelab в 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 Auth с набором виджетов , чтобы создать пользовательский интерфейс входа в систему для проверки подлинности на основе 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

  1. Войдите Firebase .
  2. В консоли Firebase, нажмите кнопку Add Project (или Создать проект), и название вашего проекта Firebase Firebase-флаттер-Codelab.

4395e4e67c08043a.png

  1. Нажмите на параметры создания проекта. Примите условия Firebase, если будет предложено. Пропустите настройку Google Analytics, потому что вы не будете использовать Analytics для этого приложения.

b7138cde5f2c7b61.png

Чтобы узнать больше о проектах Firebase см Понимать проекты Firebase .

Приложение, которое вы создаете, использует несколько продуктов Firebase, доступных для веб-приложений:

  • Firebase Authentication , чтобы позволить пользователям войти в приложение.
  • Облако Firestore для сохранения структурированных данных в облаке и получить уведомление момент , когда данные изменения.
  • Firebase Правила безопасности для защиты вашей базы данных.

Некоторые из этих продуктов требуют специальной настройки или должны быть включены с помощью консоли Firebase.

Включить знак электронной почты в течение Firebase аутентификации

Для того, чтобы позволить пользователям войти в веб - приложение, вы будете использовать E - mail / пароль входа в методе для этого codelab:

  1. В Firebase консоли разверните меню сборки на левой панели.
  2. Нажмите Authentication, а затем нажмите кнопку Начало, а затем вкладку регистрации в методе (или нажмите здесь , чтобы перейти непосредственно к Знамению-во вкладке метода).
  3. Нажмите E - mail / пароль в регистрации в списке поставщиков, установите переключатель Включить в положение, а затем нажмите кнопку Сохранить. 58e3e3e23c2f16a4.png

Включить облачное хранилище Firestore

Веб - приложение использует облако Firestore для сохранения сообщений чата и получать новые сообщения чата.

Включить облачное хранилище Firestore:

  1. В разделе сборки Firebase консоли, нажмите Cloud Firestore.
  2. Нажмите кнопку Создать базу данных. 99e8429832d23fa3.png
  1. Выберите Пуск в опции тестового режима. Прочитайте отказ от ответственности о правилах безопасности. Тестовый режим гарантирует, что вы можете свободно писать в базу данных во время разработки. Нажмите кнопку Далее. 6be00e26c72ea032.png
  1. Выберите место для вашей базы данных (вы можете просто использовать значение по умолчанию). Обратите внимание, что это местоположение нельзя будет изменить позже. 278656eefcfb0216.png
  2. Нажмите кнопку Включить.

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 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

  1. Интерфейс командной строки FlutterFire зависит от базового интерфейса командной строки Firebase. Если вы еще не сделали этого, установить или обновить до последней версии Firebase CLI .
  2. Затем установите FlutterFire CLI, выполнив следующую команду:
    $ dart pub global activate flutterfire_cli
    

После установки flutterfire команда будет доступна по всему миру.

Настройка ваших приложений

CLI извлекает информацию из вашего проекта Firebase и выбранных приложений проекта, чтобы сгенерировать всю конфигурацию для конкретной платформы.

В корне вашего приложения запустите команду configure:

$ flutterfire configure

Команда конфигурации проведет вас через ряд процессов:

  1. Выбор проекта Firebase (на основе файла .firebaserc или из консоли Firebase).
  2. Подскажите, для каких платформ (например, Android, iOS, macOS и веб) вам нужна конфигурация.
  3. Определите, какие приложения Firebase для выбранных платформ следует использовать для извлечения конфигурации. По умолчанию интерфейс командной строки попытается автоматически сопоставить приложения Firebase на основе вашей текущей конфигурации проекта.
  4. Создайте файл 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>

См Entitlements и App Sandbox более подробно.

5. Добавьте вход пользователя (RSVP)

Теперь, когда вы добавили Firebase в приложение, вы можете настроить кнопку RSVP , который регистрирует людей , использующих Firebase аутентификации . Для нативного Android, нативного iOS и Интернета есть предварительно созданные пакеты аутентификации FirebaseUI, но для Flutter вам нужно будет создать эту возможность.

Проект, который вы получили на шаге 2, включал набор виджетов, реализующих пользовательский интерфейс для большей части потока аутентификации. Вы реализуете бизнес-логику для интеграции Firebase Authentication в приложение.

Бизнес-логика с провайдером

Вы собираетесь использовать provider пакет , чтобы сделать централизованное государство объект приложения доступна по всему дереву приложения виджетов флаттера. Начнем с того , изменения импорта в верхней части 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 сердечник и 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 путем обновления HomePage «сек build метод:

библиотека/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 виджете. Потребитель виджет обычным способом , что provider пакет может быть использован , чтобы восстановить часть дерева , когда состояние приложения изменяется. Authentication виджет интерфейс аутентификации, вы теперь будет тест.

Тестирование потока аутентификации

cdf2d25e436bd48d.png

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

2a2cd6d69d172369.png

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

e5e65065dba36b54.png

Обязательно попробуйте ввести короткий пароль (менее шести символов), чтобы проверить процесс обработки ошибок. Если пользователь зарегистрирован, вместо него он увидит пароль.

fbb3ea35fb4f67a.png

На этой странице убедитесь, что введены неправильные пароли, чтобы проверить обработку ошибок на этой странице. Наконец, как только пользователь войдет в систему, вы увидите вход в систему, который предлагает пользователю возможность снова выйти из системы.

4ed811a25b0cf816.png

И с этим вы внедрили поток аутентификации. Поздравляю!

6. Пишите сообщения в Cloud Firestore

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

Для хранения сообщений чата , которые пользователи пишут в приложении, вы будете использовать облако Firestore .

Модель данных

Cloud Firestore — это база данных NoSQL, и данные, хранящиеся в базе данных, разделены на коллекции, документы, поля и подколлекции. Вы будете хранить каждое сообщение в чате в качестве документа в коллекции верхнего уровня называется guestbook .

7c20dc8424bb1d84.png

Добавить сообщения в 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';

Для построения элементов пользовательского интерфейса из поля сообщения и кнопки отправки, добавить новый виджет Stateful 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'),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Здесь есть пара интересных моментов. Во-первых, вы создаете экземпляр формы, чтобы upi мог проверить, что сообщение действительно имеет некоторый контент, и показать пользователю сообщение об ошибке, если его нет. Способ проверки формы включает в себя доступ к государственной форме за формой, и для этого вы используете GlobalKey . Для получения более подробной информации о ключах, и как использовать их, пожалуйста , см флаттера 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)),

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

Предварительный просмотр приложения

Пользователь , нажав кнопку SEND вызовет фрагмент кода ниже. Он добавляет содержимое поля ввода сообщения в guestbook коллекции базы данных. В частности, addMessageToGuestBook метод добавляет содержание сообщения в новый документ (с автоматически генерируемым ID) в 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> сделать состояние приложения доступны в части дерева вы рендеринга. Это позволяет вам реагировать на то, что кто-то вводит сообщение в пользовательском интерфейсе, и публиковать его в базе данных. В следующем разделе вы проверите, публикуются ли добавленные сообщения в базе данных.

Тестовая отправка сообщений

  1. Убедитесь, что вы вошли в приложение.
  2. Введите сообщение , такие как «Эй!», А затем нажмите кнопку SEND.

Это действие записывает сообщение в вашу базу данных Cloud Firestore. Однако вы еще не увидите это сообщение в своем реальном приложении Flutter, потому что вам все еще нужно реализовать извлечение данных. Вы сделаете это на следующем шаге.

Но вы можете увидеть только что добавленное сообщение в консоли Firebase.

В Firebase консоли, в приборной панели базы данных , вы должны увидеть guestbook коллекцию с помощью нового добавленного сообщения. Если вы продолжите отправлять сообщения, ваша коллекция гостевой книги будет содержать много документов, например:

Консоль Firebase

713870af0b3b63c.png

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 «s детей вы добавить коллекцию , чтобы создать новый 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 автоматически и мгновенно синхронизирует данные с клиентами, подписанными на базу данных.

  1. Сообщения, созданные ранее в базе данных, должны отображаться в приложении. Не стесняйтесь писать новые сообщения; они должны появиться мгновенно.
  2. Если вы откроете свое рабочее пространство в нескольких окнах или вкладках, сообщения будут синхронизироваться в режиме реального времени между вкладками.
  3. (Необязательно) Вы можете попробовать вручную удалить, изменения или добавления новых сообщений непосредственно в разделе База данных консоли Firebase; любые изменения должны появиться в пользовательском интерфейсе.

Поздравляем! Вы читаете документы Cloud Firestore в своем приложении!

App р обзор

8. Установите основные правила безопасности

Изначально вы настроили Cloud Firestore для использования тестового режима, что означает, что ваша база данных открыта для чтения и записи. Однако вам следует использовать тестовый режим только на очень ранних стадиях разработки. Рекомендуется настроить правила безопасности для базы данных при разработке приложения. Безопасность должна быть неотъемлемой частью структуры и поведения вашего приложения.

Правила безопасности позволяют контролировать доступ к документам и коллекциям в базе данных. Гибкий синтаксис правил позволяет создавать правила, которые соответствуют чему угодно, от всех операций записи во всю базу данных до операций над конкретным документом.

Вы можете написать правила безопасности для Cloud Firestore в консоли Firebase:

  1. В разделе Develop Firebase консоли выберите базу данных, а затем выберите вкладку Правила (или нажмите здесь , чтобы перейти непосредственно к закладке Правила).
  2. Вы должны увидеть следующие правила безопасности по умолчанию, а также предупреждение о том, что правила являются общедоступными.

7767a2d2e64e7275.png

Определить коллекции

Сначала определите коллекции, в которые приложение записывает данные.

В 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});
  }
}

Обновление ApplicationState «s init метод следующим образом :

библиотека/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 метод сборки «s , чтобы воспользоваться 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? Может быть, вы хотите узнать о подколлекциях и транзакциях? Зайдем на веб - codelab Облако Firestore для codelab , который идет в большей глубины на облаке Firestore. Или проверить эту серию на YouTube , чтобы узнать Cloud Firestore !

Выучить больше

Как прошло?

Будем рады вашему отзыву! Пожалуйста , заполните (очень) короткую форму здесь .