Tìm hiểu về Firebase cho Flutter

1. Trước khi bạn bắt đầu

Trong bảng mã này, bạn sẽ tìm hiểu một số kiến ​​thức cơ bản về Firebase để tạo ứng dụng di động Flutter cho Android và iOS.

Điều kiện tiên quyết

Bộ mã này giả định rằng bạn đã quen thuộc với Flutter và bạn đã cài đặt Flutter SDKmột trình soạn thảo .

Những gì bạn sẽ tạo ra

Trong codelab này, bạn sẽ tạo RSVP sự kiện và ứng dụng trò chuyện sổ lưu bút trên Android, iOS, Web và macOS bằng Flutter. Bạn sẽ xác thực người dùng bằng Xác thực Firebase và đồng bộ hóa dữ liệu bằng Cloud Firestore.

Những gì bạn cần

Bạn có thể chạy bảng mã này bằng bất kỳ thiết bị nào sau đây:

  • Một thiết bị vật lý (Android hoặc iOS) được kết nối với máy tính của bạn và đặt ở chế độ nhà phát triển.
  • Trình mô phỏng iOS. (Yêu cầu cài đặt công cụ Xcode .)
  • Trình giả lập Android. (Yêu cầu thiết lập trong Android Studio .)

Ngoài những điều trên, bạn cũng sẽ cần:

  • Trình duyệt bạn chọn, chẳng hạn như Chrome.
  • IDE hoặc trình soạn thảo văn bản mà bạn chọn, chẳng hạn như Android Studio hoặc VS Code được định cấu hình bằng các plugin Dart và Flutter.
  • Phiên bản stable mới nhất của Flutter (hoặc beta nếu bạn thích sống bên cạnh).
  • Tài khoản Google, giống như tài khoản gmail, để tạo và quản lý dự án Firebase của bạn.
  • Công cụ dòng lệnh firebase , đã đăng nhập vào tài khoản gmail của bạn.
  • Mã mẫu của codelab. Xem bước tiếp theo để biết cách lấy mã.

2. Lấy mã mẫu

Hãy bắt đầu bằng cách tải xuống phiên bản đầu tiên của dự án của chúng tôi từ GitHub.

Sao chép kho lưu trữ GitHub từ dòng lệnh:

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

Ngoài ra, nếu bạn đã cài đặt công cụ cli của GitHub :

gh repo clone flutter/codelabs flutter-codelabs

Mã mẫu phải được sao chép vào thư flutter-codelabs , nơi chứa mã cho một bộ sưu tập codelabs. Mã cho codelab này nằm trong flutter-codelabs/firebase-get-to-know-flutter .

Cấu trúc thư mục trong flutter-codelabs/firebase-get-to-know-flutter là một loạt các ảnh chụp nhanh về vị trí bạn nên ở cuối mỗi bước được đặt tên. Đây là Bước 2, vì vậy việc định vị các tệp phù hợp dễ dàng như sau:

cd flutter-codelabs/firebase-get-to-know-flutter/step_02

Nếu bạn muốn bỏ qua về phía trước hoặc xem thứ gì đó sẽ trông như thế nào sau một bước, hãy tìm trong thư mục được đặt tên theo bước bạn quan tâm.

Nhập ứng dụng dành cho người mới bắt đầu

Mở hoặc nhập thư mục flutter-codelabs/firebase-get-to-know-flutter/step_02 vào IDE ưa thích của bạn. Thư mục này chứa mã bắt đầu cho codelab bao gồm một ứng dụng hội ngộ Flutter chưa hoạt động.

Định vị các tệp để làm việc

Mã trong ứng dụng này được trải rộng trên nhiều thư mục. Sự phân chia chức năng này được thiết kế để giúp bạn làm việc dễ dàng hơn, bằng cách nhóm mã theo chức năng.

Định vị các tệp sau trong dự án:

  • lib/main.dart : Tệp này chứa điểm nhập chính và tiện ích ứng dụng.
  • lib/src/widgets.dart : Tệp này chứa một số ít widget để giúp chuẩn hóa kiểu dáng của ứng dụng. Chúng được sử dụng để soạn màn hình của ứng dụng khởi động.
  • lib/src/authentication.dart : Tệp này chứa một phần triển khai của FirebaseUI Auth với một tập hợp các widget để tạo trải nghiệm người dùng đăng nhập cho xác thực dựa trên email Firebase. Các tiện ích con cho quy trình xác thực này chưa được sử dụng trong ứng dụng dành cho người mới bắt đầu, nhưng bạn sẽ sớm đưa chúng vào.

Bạn sẽ thêm các tệp bổ sung theo yêu cầu để xây dựng phần còn lại của ứng dụng.

Xem lại tệp lib/main.dart

Ứng dụng này tận dụng gói google_fonts để cho phép chúng tôi đặt Roboto làm phông chữ mặc định trong toàn bộ ứng dụng. Bài tập dành cho người đọc có động cơ là khám phá font.google.com và sử dụng các phông chữ bạn khám phá ở đó trong các phần khác nhau của ứng dụng.

Bạn đang sử dụng các widget trợ giúp từ lib/src/widgets.dart dưới dạng Header , ParagraphIconAndDetail . Các tiện ích con này giảm bớt sự lộn xộn trong bố cục trang được mô tả trong HomePage bằng cách loại bỏ mã trùng lặp. Điều này có lợi ích bổ sung là tạo ra giao diện nhất quán.

Đây là giao diện ứng dụng của bạn trên Android, iOS, Web và macOS:

Bản xem trước ứng dụng

3. Tạo và thiết lập dự án Firebase

Hiển thị thông tin sự kiện là rất tốt cho khách của bạn, nhưng chỉ hiển thị các sự kiện không phải là rất hữu ích cho bất kỳ ai. Hãy thêm một số chức năng động vào ứng dụng này. Đối với điều này, bạn sẽ cần kết nối Firebase với ứng dụng của mình. Để bắt đầu với Firebase, bạn cần tạo và thiết lập một dự án Firebase.

Tạo dự án Firebase

  1. Đăng nhập vào Firebase .
  2. Trong bảng điều khiển Firebase, nhấp vào Thêm dự án (hoặc Tạo dự án ) và đặt tên cho dự án Firebase của bạn là Firebase-Flutter-Codelab .

4395e4e67c08043a.png

  1. Nhấp qua các tùy chọn tạo dự án. Chấp nhận các điều khoản của Firebase nếu được nhắc. Bỏ qua thiết lập Google Analytics, vì bạn sẽ không sử dụng Analytics cho ứng dụng này.

b7138cde5f2c7b61.png

Để tìm hiểu thêm về các dự án Firebase, hãy xem Hiểu các dự án Firebase .

Ứng dụng bạn đang xây dựng sử dụng một số sản phẩm Firebase có sẵn cho các ứng dụng web:

  • Xác thực Firebase để cho phép người dùng đăng nhập vào ứng dụng của bạn.
  • Cloud Firestore để lưu dữ liệu có cấu trúc trên đám mây và nhận thông báo tức thì khi dữ liệu thay đổi.
  • Quy tắc bảo mật Firebase để bảo mật cơ sở dữ liệu của bạn.

Một số sản phẩm này cần có cấu hình đặc biệt hoặc cần được bật bằng bảng điều khiển Firebase.

Bật đăng nhập email để Xác thực Firebase

Để cho phép người dùng đăng nhập vào ứng dụng web, bạn sẽ sử dụng phương pháp đăng nhập Email / Mật khẩu cho bảng mã này:

  1. Trong bảng điều khiển Firebase, hãy mở rộng menu Xây dựng ở bảng điều khiển bên trái.
  2. Bấm Xác thực , sau đó bấm vào nút Bắt đầu , sau đó bấm vào tab Phương thức đăng nhập (hoặc bấm vào đây để chuyển trực tiếp đến tab Phương thức đăng nhập ).
  3. Bấm Email / Mật khẩu trong danh sách Nhà cung cấp dịch vụ đăng nhập , đặt công tắc Bật sang vị trí bật, sau đó bấm Lưu . 58e3e3e23c2f16a4.png

Bật Cloud Firestore

Ứng dụng web sử dụng Cloud Firestore để lưu tin nhắn trò chuyện và nhận tin nhắn trò chuyện mới.

Bật Cloud Firestore:

  1. Trong phần Xây dựng của bảng điều khiển Firebase, nhấp vào Cloud Firestore .
  2. Nhấp vào Tạo cơ sở dữ liệu . 99e8429832d23fa3.png
  1. Chọn tùy chọn Bắt đầu ở chế độ thử nghiệm . Đọc tuyên bố từ chối trách nhiệm về các quy tắc bảo mật. Chế độ kiểm tra đảm bảo rằng bạn có thể tự do ghi vào cơ sở dữ liệu trong quá trình phát triển. Nhấp vào Tiếp theo . 6be00e26c72ea032.png
  1. Chọn vị trí cho cơ sở dữ liệu của bạn (Bạn chỉ có thể sử dụng mặc định). Lưu ý rằng không thể thay đổi vị trí này sau đó. 278656eefcfb0216.png
  2. Nhấp vào Bật .

4. Cấu hình Firebase

Để sử dụng Firebase với Flutter, bạn cần làm theo quy trình để định cấu hình dự án Flutter để sử dụng đúng các thư viện FlutterFire:

  • Thêm các phụ thuộc FlutterFire vào dự án của bạn
  • Đăng ký nền tảng mong muốn trên dự án Firebase
  • Tải xuống tệp cấu hình dành riêng cho nền tảng và thêm tệp đó vào mã.

Trong thư mục cấp cao nhất của ứng dụng Flutter của bạn, có các thư mục con được gọi là android , ios , macosweb . Các thư mục này tương ứng chứa các tệp cấu hình dành riêng cho nền tảng dành cho iOS và Android.

Định cấu hình phần phụ thuộc

Bạn cần thêm các thư viện FlutterFire cho hai sản phẩm Firebase mà bạn đang sử dụng trong ứng dụng này - Firebase Auth và Cloud Firestore. Chạy ba lệnh sau để thêm các phần bổ sung.

$ 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 là mã chung được yêu cầu cho tất cả các plugin 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 cho phép tích hợp với khả năng Xác thực của 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 cho phép truy cập vào bộ lưu trữ dữ liệu 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!

Trong khi bạn đã thêm các gói bắt buộc, bạn cũng cần định cấu hình các dự án iOS, Android, macOS và Web runner để sử dụng Firebase một cách thích hợp. Bạn cũng đang sử dụng gói trình provider sẽ cho phép tách logic nghiệp vụ khỏi logic hiển thị.

Cài đặt flutterfire

FlutterFire CLI phụ thuộc vào Firebase CLI bên dưới. Nếu bạn chưa làm như vậy, hãy đảm bảo Firebase CLI đã được cài đặt trên máy của bạn.

Tiếp theo, cài đặt FlutterFire CLI bằng cách chạy lệnh sau:

$ dart pub global activate flutterfire_cli

Sau khi cài đặt, lệnh flutterfire sẽ có sẵn trên toàn cầu.

Định cấu hình ứng dụng của bạn

CLI trích xuất thông tin từ dự án Firebase của bạn và các ứng dụng dự án đã chọn để tạo tất cả cấu hình cho một nền tảng cụ thể.

Trong thư mục gốc của ứng dụng, hãy chạy lệnh cấu hình:

$ flutterfire configure

Lệnh cấu hình sẽ hướng dẫn bạn qua một số quy trình:

  1. Chọn một dự án Firebase (dựa trên tệp .firebaserc hoặc từ Bảng điều khiển Firebase).
  2. Nhắc nền tảng (ví dụ: Android, iOS, macOS và web) mà bạn muốn cấu hình.
  3. Xác định ứng dụng Firebase nào cho các nền tảng đã chọn sẽ được sử dụng để trích xuất cấu hình. Theo mặc định, CLI sẽ cố gắng tự động khớp các ứng dụng Firebase dựa trên cấu hình dự án hiện tại của bạn.
  4. Tạo tệp firebase_options.dart trong dự án của bạn.

Định cấu hình macOS

Flutter trên macOS xây dựng các ứng dụng hộp cát đầy đủ. Vì ứng dụng này đang tích hợp sử dụng mạng để giao tiếp với các máy chủ Firebase, bạn sẽ cần phải định cấu hình ứng dụng của mình với các đặc quyền của máy khách mạng.

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>

Xem Quyền lợi và Hộp cát ứng dụng để biết thêm chi tiết.

5. Thêm đăng nhập của người dùng (RSVP)

Bây giờ bạn đã thêm Firebase vào ứng dụng, bạn có thể thiết lập nút RSVP để đăng ký mọi người bằng Xác thực Firebase . Đối với Android gốc, iOS gốc và Web có các gói FirebaseUI Auth được tạo sẵn, nhưng đối với Flutter, bạn sẽ cần phải xây dựng khả năng này.

Dự án bạn đã truy xuất ở Bước 2 bao gồm một tập hợp các tiện ích con triển khai giao diện người dùng cho hầu hết quy trình xác thực. Bạn sẽ triển khai logic nghiệp vụ để tích hợp Xác thực Firebase vào ứng dụng.

Logic kinh doanh với nhà cung cấp

Bạn sẽ sử dụng gói trình provider để làm cho một đối tượng trạng thái ứng dụng tập trung có sẵn trong cây các tiện ích con Flutter của ứng dụng. Để bắt đầu, hãy sửa đổi các nhập ở đầu lib/main.dart :

lib / 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';

Các dòng import giới thiệu Firebase Core và Auth, kéo vào gói trình provider mà bạn đang sử dụng để cung cấp đối tượng trạng thái ứng dụng thông qua cây tiện ích con và bao gồm các tiện ích con xác thực từ lib/src .

Đối tượng trạng thái ứng dụng này, ApplicationState , có hai trách nhiệm chính cho bước này, nhưng sẽ có thêm trách nhiệm khi bạn thêm nhiều khả năng hơn vào ứng dụng trong các bước sau. Trách nhiệm đầu tiên là khởi tạo thư viện Firebase bằng lệnh gọi tới Firebase.initializeApp() , sau đó là xử lý luồng ủy quyền. Thêm lớp sau vào cuối lib/main.dart :

lib / 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();
  }
}

Cần lưu ý một vài điểm chính trong lớp này. Người dùng bắt đầu chưa được xác thực, ứng dụng hiển thị biểu mẫu yêu cầu địa chỉ email của người dùng, tùy thuộc vào việc địa chỉ email đó có trong hồ sơ hay không, ứng dụng sẽ yêu cầu người dùng đăng ký hoặc yêu cầu mật khẩu của họ, sau đó giả sử mọi thứ hoạt động tốt, người dùng được xác thực.

Cần lưu ý rằng đây không phải là cách triển khai hoàn chỉnh quy trình FirebaseUI Auth, vì nó không xử lý trường hợp người dùng có tài khoản hiện tại gặp sự cố khi đăng nhập. Việc triển khai khả năng bổ sung này được coi là một bài tập cho người đọc có động cơ.

Tích hợp quy trình xác thực

Bây giờ bạn đã bắt đầu trạng thái ứng dụng, đã đến lúc chuyển trạng thái ứng dụng vào quá trình khởi tạo ứng dụng và thêm luồng xác thực vào HomePage . Cập nhật điểm nhập chính để tích hợp trạng thái ứng dụng thông qua gói provider :

lib / main.dart

void main() {
  // Modify from here
  runApp(
    ChangeNotifierProvider(
      create: (context) => ApplicationState(),
      builder: (context, _) => App(),
    ),
  );
  // to here.
}

Việc sửa đổi chức năng main làm cho gói trình cung cấp chịu trách nhiệm khởi tạo đối tượng trạng thái ứng dụng bằng tiện ích con ChangeNotifierProvider . Bạn đang sử dụng lớp nhà cung cấp cụ thể này vì đối tượng trạng thái ứng dụng mở rộng ChangeNotifier và điều này cho phép gói provider biết khi nào hiển thị lại các tiện ích phụ thuộc. Cuối cùng, tích hợp trạng thái ứng dụng với Authentication bằng cách cập nhật phương pháp build của HomePage :

lib / 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!',
          ),
        ],
      ),
    );
  }
}

Bạn khởi tạo tiện ích Authentication và bọc nó trong tiện ích Consumer . Tiện ích Người tiêu dùng theo cách thông thường mà gói trình provider có thể được sử dụng để xây dựng lại một phần của cây khi trạng thái ứng dụng thay đổi. Tiện ích Authentication là giao diện người dùng xác thực mà bây giờ bạn sẽ kiểm tra.

Kiểm tra quy trình xác thực

cdf2d25e436bd48d.png

Đây là phần bắt đầu của quy trình xác thực, nơi người dùng có thể nhấn vào nút RSVP, để bắt đầu biểu mẫu email.

2a2cd6d69d172369.png

Sau khi nhập email, hệ thống xác nhận nếu người dùng đã được đăng ký, trong trường hợp người dùng được nhắc nhập mật khẩu, hoặc nếu người dùng chưa đăng ký, thì họ sẽ chuyển qua biểu mẫu đăng ký.

e5e65065dba36b54.png

Đảm bảo thử nhập mật khẩu ngắn (ít hơn sáu ký tự) để kiểm tra quy trình xử lý lỗi. Nếu người dùng đã đăng ký, họ sẽ thấy mật khẩu để thay thế.

fbb3ea35fb4f67a.png

Trên trang này, hãy nhớ nhập sai mật khẩu để kiểm tra việc xử lý lỗi trên trang này. Cuối cùng, khi người dùng đã đăng nhập, bạn sẽ thấy trải nghiệm đã đăng nhập cung cấp cho người dùng khả năng đăng xuất lại.

4ed811a25b0cf816.png

Và cùng với đó, bạn đã triển khai một quy trình xác thực. Chúc mừng!

6. Viết tin nhắn lên Cloud Firestore

Biết rằng người dùng đang đến là điều tuyệt vời, nhưng hãy cung cấp cho khách một việc khác để làm trong ứng dụng. Điều gì sẽ xảy ra nếu họ có thể để lại lời nhắn trong một cuốn sổ lưu bút? Họ có thể chia sẻ lý do tại sao họ hào hứng đến hoặc người mà họ hy vọng sẽ gặp.

Để lưu trữ các tin nhắn trò chuyện mà người dùng viết trong ứng dụng, bạn sẽ sử dụng Cloud Firestore .

Mô hình dữ liệu

Cloud Firestore là một cơ sở dữ liệu NoSQL và dữ liệu được lưu trữ trong cơ sở dữ liệu được chia thành các bộ sưu tập, tài liệu, trường và bộ sưu tập con. Bạn sẽ lưu trữ từng tin nhắn của cuộc trò chuyện dưới dạng tài liệu trong một bộ sưu tập cấp cao nhất được gọi là guestbook .

7c20dc8424bb1d84.png

Thêm tin nhắn vào Firestore

Trong phần này, bạn sẽ thêm chức năng để người dùng viết thư mới vào cơ sở dữ liệu. Đầu tiên, bạn thêm các phần tử giao diện người dùng (trường biểu mẫu và nút gửi), sau đó bạn thêm mã nối các phần tử này với cơ sở dữ liệu.

Đầu tiên, thêm nhập cho gói cloud_firestoredart:async .

lib / 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';

Để xây dựng các phần tử giao diện người dùng của trường thông báo và nút gửi, hãy thêm một GuestBook tiện ích trạng thái mới ở cuối lib/main.dart .

lib / 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'),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Có một số điểm quan tâm ở đây. Đầu tiên, bạn đang khởi tạo một Biểu mẫu để bạn có thể xác thực thông báo thực sự có một số nội dung và hiển thị cho người dùng thông báo lỗi nếu không có bất kỳ nội dung nào. Cách xác thực biểu mẫu liên quan đến việc truy cập trạng thái biểu mẫu đằng sau biểu mẫu và để làm điều này, bạn sử dụng GlobalKey . Để biết thêm thông tin về Phím và cách sử dụng chúng, vui lòng xem tập 101 của Flutter Widgets "Khi nào sử dụng phím" .

Cũng lưu ý cách bố trí các widget, bạn có một Row , với TextFormField và một StyledButton , bản thân nó chứa một Row . Cũng lưu ý rằng TextFormField được bao bọc trong một tiện ích Expanded , điều này buộc TextFormField phải chiếm thêm bất kỳ khoảng trống nào trong hàng. Để hiểu rõ hơn tại sao điều này là cần thiết, vui lòng đọc qua Tìm hiểu các ràng buộc .

Bây giờ bạn đã có một tiện ích cho phép người dùng nhập một số văn bản để thêm vào Sổ khách, bạn cần đưa nó lên màn hình. Để làm như vậy, hãy chỉnh sửa phần nội dung của HomePage để thêm hai dòng sau vào cuối phần con của 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)),

Mặc dù điều này là đủ để hiển thị Widget, nhưng nó không đủ để làm bất cứ điều gì hữu ích. Bạn sẽ sớm cập nhật mã này để làm cho nó hoạt động.

Bản xem trước ứng dụng

Người dùng nhấp vào nút GỬI sẽ kích hoạt đoạn mã bên dưới. Nó thêm nội dung của trường nhập tin nhắn vào bộ sưu tập guestbook của cơ sở dữ liệu. Cụ thể, phương thức addMessageToGuestBook thêm nội dung thư vào tài liệu mới (có ID được tạo tự động) vào bộ sưu tập guestbook .

Lưu ý rằng FirebaseAuth.instance.currentUser.uid là tham chiếu đến ID duy nhất được tạo tự động mà Xác thực Firebase cung cấp cho tất cả người dùng đã đăng nhập.

Thực hiện một thay đổi khác đối với tệp lib/main.dart . Thêm phương thức addMessageToGuestBook . Bạn sẽ kết nối giao diện người dùng và khả năng này với nhau trong bước tiếp theo.

lib / 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
}

Nối giao diện người dùng vào cơ sở dữ liệu

Bạn có giao diện người dùng để người dùng có thể nhập văn bản họ muốn thêm vào Sổ khách và bạn có mã để thêm mục nhập vào Cloud Firestore. Bây giờ tất cả những gì bạn cần làm là nối hai dây lại với nhau. Trong lib/main.dart hãy thực hiện thay đổi sau đối với tiện ích HomePage .

lib / 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.
        ],
      ),
    );
  }
}

Bạn đã thay thế hai dòng bạn đã thêm lại ở đầu bước này bằng cách triển khai đầy đủ. Bạn lại đang sử dụng Consumer<ApplicationState> để cung cấp trạng thái ứng dụng cho phần cây mà bạn đang hiển thị. Điều này cho phép bạn phản ứng với một người nào đó nhập một tin nhắn trong giao diện người dùng và xuất bản nó vào cơ sở dữ liệu. Trong phần tiếp theo, bạn sẽ kiểm tra xem các thông báo thêm vào có được xuất bản vào cơ sở dữ liệu hay không.

Kiểm tra gửi tin nhắn

  1. Đảm bảo rằng bạn đã đăng nhập vào ứng dụng.
  2. Nhập một thông báo chẳng hạn như "Chào bạn!", Rồi bấm GỬI .

Hành động này ghi thông báo vào cơ sở dữ liệu Cloud Firestore của bạn. Tuy nhiên, bạn sẽ không thấy thông báo trong ứng dụng Flutter thực của mình vì bạn vẫn cần triển khai việc truy xuất dữ liệu. Bạn sẽ làm điều đó trong bước tiếp theo.

Nhưng bạn có thể thấy thông báo mới được thêm trong bảng điều khiển Firebase.

Trong bảng điều khiển Firebase, trong bảng điều khiển Cơ sở dữ liệu , bạn sẽ thấy bộ sưu tập guestbook với thông điệp mới được thêm vào của bạn. Nếu bạn tiếp tục gửi tin nhắn, bộ sưu tập lưu bút của bạn sẽ chứa nhiều tài liệu, như sau:

Bảng điều khiển Firebase

713870af0b3b63c.png

7. Đọc tin nhắn

Thật tuyệt khi khách có thể viết tin nhắn vào cơ sở dữ liệu nhưng họ chưa thể nhìn thấy chúng trong ứng dụng. Hãy khắc phục điều đó!

Đồng bộ hóa tin nhắn

Để hiển thị thông báo, bạn sẽ cần thêm trình nghe kích hoạt khi dữ liệu thay đổi, sau đó tạo phần tử giao diện người dùng hiển thị thông báo mới. Bạn sẽ thêm mã vào trạng thái ứng dụng để lắng nghe các tin nhắn mới được thêm vào từ ứng dụng.

Ngay phía trên tiện ích GuestBook là lớp giá trị sau. Lớp này hiển thị chế độ xem có cấu trúc về dữ liệu bạn đang lưu trữ trong Cloud Firestore.

lib / main.dart

class GuestBookMessage {
  GuestBookMessage({required this.name, required this.message});
  final String name;
  final String message;
}

Trong phần ApplicationState nơi bạn xác định trạng thái và getters, hãy thêm các dòng mới sau:

lib / 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.

Và cuối cùng, trong phần khởi tạo của ApplicationState , hãy thêm phần sau để đăng ký truy vấn qua bộ sưu tập tài liệu khi người dùng đăng nhập và hủy đăng ký khi họ đăng xuất.

lib / 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();
    });
  }

Phần này rất quan trọng, vì đây là nơi bạn tạo một truy vấn về bộ sưu tập guestbook và xử lý việc đăng ký và hủy đăng ký bộ sưu tập này. Bạn nghe luồng, nơi bạn tạo lại bộ nhớ cache cục bộ của các thư trong bộ sưu tập guestbook và cũng lưu trữ tham chiếu đến đăng ký này để bạn có thể hủy đăng ký sau đó. Có rất nhiều điều đang diễn ra ở đây, và bạn nên dành chút thời gian trong trình gỡ lỗi để kiểm tra điều gì xảy ra khi nào để có được một mô hình tinh thần rõ ràng hơn.

Để biết thêm thông tin, hãy xem tài liệu Cloud Firestore .

Trong tiện ích GuestBook , bạn cần kết nối trạng thái thay đổi này với giao diện người dùng. Bạn sửa đổi tiện ích bằng cách thêm danh sách thư như một phần cấu hình của tiện ích.

lib / 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();
}

Tiếp theo, chúng tôi hiển thị cấu hình mới này trong _GuestBookState bằng cách sửa đổi phương thức build như sau.

lib / 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.
    );
  }
}

Bạn bao bọc nội dung trước đó của phương pháp xây dựng bằng tiện ích con Column , sau đó ở phần cuối của phần con của Column , bạn thêm một bộ sưu tập để tạo Paragraph mới cho mỗi thư trong danh sách thư.

Cuối cùng, bây giờ bạn cần cập nhật phần nội dung của HomePage để tạo chính xác GuestBook với tham số messages mới.

lib / 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
        ),
      ],
    ],
  ),
),

Kiểm tra đồng bộ hóa tin nhắn

Cloud Firestore đồng bộ hóa dữ liệu tự động và tức thì với các khách hàng đã đăng ký cơ sở dữ liệu.

  1. Các thông báo bạn đã tạo trước đó trong cơ sở dữ liệu sẽ được hiển thị trong ứng dụng. Hãy viết tin nhắn mới; chúng sẽ xuất hiện ngay lập tức.
  2. Nếu bạn mở không gian làm việc của mình trong nhiều cửa sổ hoặc tab, tin nhắn sẽ đồng bộ hóa trong thời gian thực giữa các tab.
  3. (Tùy chọn) Bạn có thể thử xóa, sửa đổi hoặc thêm thư mới theo cách thủ công trực tiếp trong phần Cơ sở dữ liệu của bảng điều khiển Firebase; bất kỳ thay đổi nào sẽ xuất hiện trong giao diện người dùng.

Xin chúc mừng! Bạn đang đọc tài liệu Cloud Firestore trong ứng dụng của mình!

Đánh giá ứng dụng p

8. Thiết lập các quy tắc bảo mật cơ bản

Ban đầu, bạn thiết lập Cloud Firestore để sử dụng chế độ thử nghiệm, nghĩa là cơ sở dữ liệu của bạn đang mở để đọc và ghi. Tuy nhiên, bạn chỉ nên sử dụng chế độ thử nghiệm trong giai đoạn phát triển rất sớm. Cách tốt nhất, bạn nên thiết lập các quy tắc bảo mật cho cơ sở dữ liệu của mình khi bạn phát triển ứng dụng của mình. Bảo mật phải là yếu tố không thể thiếu trong cấu trúc và hành vi ứng dụng của bạn.

Quy tắc bảo mật cho phép bạn kiểm soát quyền truy cập vào tài liệu và bộ sưu tập trong cơ sở dữ liệu của bạn. Cú pháp quy tắc linh hoạt cho phép bạn tạo quy tắc phù hợp với bất kỳ thứ gì từ tất cả các lần ghi vào toàn bộ cơ sở dữ liệu đến các hoạt động trên một tài liệu cụ thể.

Bạn có thể viết các quy tắc bảo mật cho Cloud Firestore trong bảng điều khiển Firebase:

  1. Trong phần Phát triển của bảng điều khiển Firebase, nhấp vào Cơ sở dữ liệu , sau đó chọn tab Quy tắc (hoặc nhấp vào đây để chuyển trực tiếp đến tab Quy tắc ).
  2. Bạn sẽ thấy các quy tắc bảo mật mặc định sau đây, cùng với cảnh báo về việc các quy tắc được công khai.

7767a2d2e64e7275.png

Xác định bộ sưu tập

Đầu tiên, xác định các bộ sưu tập mà ứng dụng ghi dữ liệu.

Trong match /databases/{database}/documents , hãy xác định bộ sưu tập mà bạn muốn bảo mật:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /guestbook/{entry} {
     // You'll add rules here in the next step.
  }
}

Thêm quy tắc bảo mật

Vì bạn đã sử dụng UID Xác thực làm trường trong mỗi tài liệu sổ lưu bút, bạn có thể lấy UID Xác thực và xác minh rằng bất kỳ ai cố gắng ghi vào tài liệu đều có UID Xác thực phù hợp.

Thêm các quy tắc đọc và ghi vào bộ quy tắc của bạn như được hiển thị bên dưới:

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

Giờ đây, đối với sổ lưu bút, chỉ những người dùng đã đăng nhập mới có thể đọc tin nhắn (bất kỳ tin nhắn nào!), Nhưng chỉ tác giả của tin nhắn mới có thể chỉnh sửa tin nhắn.

Thêm quy tắc xác thực

Thêm xác thực dữ liệu để đảm bảo rằng tất cả các trường mong đợi đều có trong tài liệu:

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. Bước bổ sung: Thực hành những gì bạn đã học

Ghi lại trạng thái RSVP của người tham dự

Hiện tại, ứng dụng của bạn chỉ cho phép mọi người bắt đầu trò chuyện nếu họ quan tâm đến sự kiện. Ngoài ra, cách duy nhất để bạn biết liệu ai đó có đến hay không là nếu họ đăng nó trong cuộc trò chuyện. Hãy tổ chức và cho mọi người biết có bao nhiêu người sẽ đến.

Bạn sẽ thêm một vài khả năng mới vào trạng thái ứng dụng. Đầu tiên là khả năng người dùng đã đăng nhập đề cử xem họ có tham dự hay không. Khả năng thứ hai là phản ánh số lượng người thực sự đang tham dự.

Trong lib/main.dart , hãy thêm phần sau vào phần trình truy cập để cho phép mã giao diện người dùng tương tác với trạng thái này:

lib / 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});
  }
}

Cập nhật phương thức init của ApplicationState như sau:

lib / 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();
    });
  }

Ở trên thêm một truy vấn luôn được đăng ký để tìm số lượng người tham dự và truy vấn thứ hai chỉ hoạt động khi người dùng đăng nhập để tìm hiểu xem người dùng có tham dự hay không. Tiếp theo, thêm bảng liệt kê sau sau khai báo GuestBookMessage :

lib / main.dart

enum Attending { yes, no, unknown }

Bây giờ bạn sẽ xác định một widget mới hoạt động giống như các nút radio cũ. Nó bắt đầu ở trạng thái không xác định, không có cũng không được chọn, nhưng khi người dùng chọn xem họ có tham dự hay không, thì bạn sẽ hiển thị tùy chọn đó được đánh dấu bằng một nút điền và tùy chọn khác rút lui với kết xuất phẳng.

lib / 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'),
              ),
            ],
          ),
        );
    }
  }
}

Tiếp theo, bạn cần cập nhật phương pháp xây dựng của HomePage để tận dụng YesNoSelection , cho phép người dùng đã đăng nhập đề cử xem họ có tham dự hay không. Bạn cũng sẽ hiển thị số lượng người tham dự sự kiện này.

lib / 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,
        ),
      ],
    ],
  ),
),

Thêm quy tắc

Vì bạn đã thiết lập một số quy tắc nên dữ liệu mới mà bạn đang thêm bằng các nút sẽ bị từ chối. Bạn sẽ cần cập nhật các quy tắc để cho phép thêm vào bộ sưu tập attendees .

Đối với bộ sưu tập attendees , vì bạn đã sử dụng UID xác thực làm tên tài liệu, bạn có thể lấy nó và xác minh rằng uid của người gửi giống với tài liệu họ đang viết. Bạn sẽ cho phép mọi người đọc danh sách người tham dự (vì không có dữ liệu riêng tư ở đó), nhưng chỉ người tạo mới có thể cập nhật danh sách đó.

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // ... //
    match /attendees/{userId} {
      allow read: if true;
      allow write: if request.auth.uid == userId;
    }
  }
}

Thêm quy tắc xác thực

Thêm xác thực dữ liệu để đảm bảo rằng tất cả các trường mong đợi đều có trong tài liệu:

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;

    }
  }
}

(Tùy chọn) Bây giờ bạn có thể xem kết quả của việc nhấp vào các nút. Truy cập trang tổng quan Cloud Firestore của bạn trong bảng điều khiển Firebase.

Bản xem trước ứng dụng

10. Xin chúc mừng!

Bạn đã sử dụng Firebase để xây dựng một ứng dụng web tương tác, thời gian thực!

Những gì chúng tôi đã đề cập

  • Xác thực Firebase
  • Cloud Firestore
  • Quy tắc bảo mật của Firebase

Bước tiếp theo

Tìm hiểu thêm

Nó diễn ra như thế nào?

Chúng tôi rất thích những phản hồi của bạn! Vui lòng điền vào một (rất) mẫu ngắn ở đây .