Tìm hiểu về Firebase cho Flutter

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

Trong lớp học lập trình này, bạn 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ạn sẽ học được gì

  • Cách xây dựng ứng dụng trò chuyện RSVP và sổ lưu bút trên Android, iOS, Web và macOS bằng Flutter.
  • Cách xác thực người dùng bằng Xác thực Firebase và đồng bộ hóa dữ liệu với Firestore.

Màn hình chính của ứng dụng trên Android

Màn hình chính của ứng dụng trên iOS

Những gì bạn cần

Bất kỳ thiết bị nào sau đây:

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

Bạn cũng cần những điều sau đây:

  • Trình duyệt bạn chọn, chẳng hạn như Google Chrome.
  • IDE hoặc trình soạn thảo văn bản mà bạn chọn được định cấu hình bằng plugin Dart và Flutter, chẳng hạn như Android Studio hoặc Visual Studio Code .
  • Phiên bản stable mới nhất của Flutter hoặc beta nếu bạn thích sống ở rìa.
  • Tài khoản Google để tạo và quản lý dự án Firebase của bạn.
  • Firebase CLI đã đăng nhập vào Tài khoản Google của bạn.

2. Lấy mã mẫu

Tải xuống phiên bản ban đầu của dự án của bạn từ GitHub:

  1. Từ dòng lệnh, sao chép kho lưu trữ GitHub trong thư mục flutter-codelabs :
git clone https://github.com/flutter/codelabs.git flutter-codelabs

Thư mục flutter-codelabs chứa mã cho một tập hợp các lớp học lập trình. Mã cho lớp học lập trình này nằm trong thư flutter-codelabs/firebase-get-to-know-flutter . Thư mục chứa một loạt ảnh chụp nhanh cho biết dự án của bạn sẽ trông như thế nào ở cuối mỗi bước. Ví dụ: bạn đang ở bước thứ hai.

  1. Tìm các tệp phù hợp cho bước thứ hai:
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 nội dung nào đó 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 mà bạn quan tâm.

Nhập ứng dụng khởi đầu

  • Mở hoặc nhập thư flutter-codelabs/firebase-get-to-know-flutter/step_02 trong IDE ưa thích của bạn. Thư mục này chứa mã khởi động cho lớp học lập trình, bao gồm một ứng dụng gặp mặt Flutter chưa hoạt động.

Xác định vị trí các tập tin cần làm việc

Mã trong ứng dụng này được trải rộng trên nhiều thư mục. Việc phân chia chức năng này giúp công việc dễ dàng hơn vì nó nhóm mã theo chức năng.

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

Bạn 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 để đặt Roboto làm phông chữ mặc định trong toàn bộ ứng dụng. Bạn có thể khám phá phông chữ.google.com và sử dụng các phông chữ mà bạn khám phá ở đó trong các phần khác nhau của ứng dụng.

Bạn sử dụng các tiện ích trợ giúp từ tệp lib/src/widgets.dart ở dạng Header , ParagraphIconAndDetail . Những tiện ích này loại bỏ mã trùng lặp để giảm sự lộn xộn trong bố cục trang được mô tả trong HomePage . Điều này cũng cho phép một cái nhìn và cảm nhận nhất quán.

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

Màn hình chính của ứng dụng trên Android

Màn hình chính của ứng dụng trên iOS

Màn hình chính của ứng dụng trên web

Màn hình chính của ứng dụng trên macOS

3. Tạo và định cấu hình dự án Firebase

Việc hiển thị thông tin sự kiện rất tốt cho khách của bạn, nhưng nó không hữu ích lắm cho bất kỳ ai. Bạn cần thêm một số chức năng động vào ứng dụng. Để làm như vậy, bạn 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à định cấu hình dự án Firebase.

Tạo dự án Firebase

  1. Đăng nhập vào Firebase .
  2. Trong bảng điều khiển, nhấp vào Thêm dự án hoặc Tạo dự án .
  3. Trong trường Tên dự án , nhập Firebase-Flutter-Codelab rồi nhấp vào Tiếp tục .

4395e4e67c08043a.png

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

b7138cde5f2c7b61.png

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

Ứng dụng sử dụng các sản phẩm Firebase sau, có sẵn cho ứng dụng web:

  • Xác thực: Cho phép người dùng đăng nhập vào ứng dụng của bạn.
  • 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ấu hình đặc biệt hoặc bạn cần kích hoạt chúng trong bảng điều khiển Firebase.

Bật xác thực đăng nhập email

  1. Trong ngăn Tổng quan dự án của bảng điều khiển Firebase, hãy mở rộng menu Xây dựng .
  2. Nhấp vào Xác thực > Bắt đầu > Phương thức đăng nhập > Email/Mật khẩu > Bật > Lưu .

58e3e3e23c2f16a4.png

Kích hoạt Firestore

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

Kích hoạt Firestore:

  • Trong menu Xây dựng , nhấp vào Cơ sở dữ liệu Firestore > Tạo cơ sở dữ liệu .

99e8429832d23fa3.png

  1. Chọn Bắt đầu ở chế độ thử nghiệm rồi đọ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.

6be00e26c72ea032.png

  1. Bấm vào Tiếp theo rồi chọn vị trí cho cơ sở dữ liệu của bạn. Bạn có thể sử dụng mặc định. Bạn không thể thay đổi vị trí sau này.

278656eefcfb0216.png

  1. Nhấp vào Bật .

4. Định cấu hình Firebase

Để sử dụng Firebase với Flutter, bạn cần hoàn thành các tác vụ sau để định cấu hình dự án Flutter nhằm sử dụng chính xác các thư viện FlutterFire :

  1. Thêm phần phụ thuộc FlutterFire vào dự án của bạn.
  2. Đăng ký nền tảng mong muốn trên dự án Firebase.
  3. Tải xuống tệp cấu hình dành riêng cho nền tảng rồi 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 android , ios , macosweb tương ứng chứa các tệp cấu hình dành riêng cho nền tảng cho iOS và Android.

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

Bạn cần thêm thư viện FlutterFire cho hai sản phẩm Firebase mà bạn sử dụng trong ứng dụng này: Xác thực và Firestore.

  • Từ dòng lệnh, thêm các phụ thuộc sau:
$ flutter pub add firebase_core

Gói firebase_core là mã chung bắt buộc cho tất cả plugin Firebase Flutter.

$ flutter pub add firebase_auth

Gói firebase_auth cho phép tích hợp với Xác thực.

$ flutter pub add cloud_firestore

Gói cloud_firestore cho phép truy cập vào bộ lưu trữ dữ liệu Firestore.

$ flutter pub add provider

Gói firebase_ui_auth cung cấp một bộ tiện ích và tiện ích để tăng tốc độ của nhà phát triển với các luồng xác thực.

$ flutter pub add firebase_ui_auth

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

Cài đặt FlutterFire CLI

FlutterFire CLI phụ thuộc vào Firebase CLI cơ bản.

  1. Nếu bạn chưa làm như vậy, hãy cài đặt Firebase CLI trên máy của bạn.
  2. Cài đặt FlutterFire CLI:
$ dart pub global activate flutterfire_cli

Sau khi cài đặt, lệnh flutterfire 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 configure :

$ flutterfire configure

Lệnh cấu hình sẽ hướng dẫn bạn qua các quy trình sau:

  1. Chọn dự án Firebase dựa trên tệp .firebaserc hoặc từ Bảng điều khiển Firebase.
  2. Xác định nền tảng cho cấu hình như Android, iOS, macOS và web.
  3. Xác định ứng dụng Firebase để trích xuất cấu hình. Theo mặc định, CLI 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 được đóng hộp cát hoàn toàn. Khi ứng dụng này tích hợp với mạng để liên lạc với máy chủ Firebase, bạn cần đị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>

Để biết thêm thông tin, hãy xem Hỗ trợ máy tính để bàn cho Flutter .

5. Thêm chức năng Trả lời

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

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

Thêm logic nghiệp vụ với gói Provider

Sử dụng gói provider để cung cấp đối tượng trạng thái ứng dụng tập trung trên toàn bộ cây tiện ích Flutter của ứng dụng:

  1. Tạo một tệp mới có tên app_state.dart với nội dung sau:

lib/app_state.dart

import 'package:firebase_auth/firebase_auth.dart'
    hide EmailAuthProvider, PhoneAuthProvider;
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';

import 'firebase_options.dart';

class ApplicationState extends ChangeNotifier {
  ApplicationState() {
    init();
  }

  bool _loggedIn = false;
  bool get loggedIn => _loggedIn;

  Future<void> init() async {
    await Firebase.initializeApp(
        options: DefaultFirebaseOptions.currentPlatform);

    FirebaseUIAuth.configureProviders([
      EmailAuthProvider(),
    ]);

    FirebaseAuth.instance.userChanges().listen((user) {
      if (user != null) {
        _loggedIn = true;
      } else {
        _loggedIn = false;
      }
      notifyListeners();
    });
  }
}

Câu lệnh import giới thiệu Firebase Core và Auth, kéo gói provider giúp cung cấp đối tượng trạng thái ứng dụng trên toàn bộ cây tiện ích và bao gồm các tiện ích xác thực từ gói firebase_ui_auth .

Đối tượng trạng thái ứng dụng ApplicationState này có một trách nhiệm chính cho bước này, đó là cảnh báo cây widget rằng đã có bản cập nhật cho trạng thái được xác thực.

Bạn chỉ sử dụng nhà cung cấp để thông báo trạng thái đăng nhập của người dùng tới ứng dụng. Để cho phép người dùng đăng nhập, bạn sử dụng giao diện người dùng do gói firebase_ui_auth cung cấp. Đây là một cách tuyệt vời để khởi động nhanh màn hình đăng nhập trong ứng dụng của bạn.

Tích hợp luồng xác thực

  1. Sửa đổi phần nhập ở đầu tệp lib/main.dart :

lib/main.dart

import 'package:firebase_ui_auth/firebase_ui_auth.dart'; // new
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';               // new
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';                 // new

import 'app_state.dart';                                 // new
import 'home_page.dart';
  1. Kết nối trạng thái ứng dụng với quá trình khởi tạo ứng dụng, sau đó thêm luồng xác thực vào HomePage :

lib/main.dart

void main() {
  // Modify from here...
  WidgetsFlutterBinding.ensureInitialized();

  runApp(ChangeNotifierProvider(
    create: (context) => ApplicationState(),
    builder: ((context, child) => const App()),
  ));
  // ...to here.
}

Việc sửa đổi hàm main() khiến gói 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 ChangeNotifierProvider . Bạn sử dụng lớp provider cụ thể này vì đối tượng trạng thái ứng dụng mở rộng lớp ChangeNotifier , cho phép gói provider biết thời điểm hiển thị lại các tiện ích phụ thuộc.

  1. Cập nhật ứng dụng của bạn để xử lý việc điều hướng đến các màn hình khác nhau mà FirebaseUI cung cấp cho bạn bằng cách tạo cấu hình GoRouter :

lib/main.dart

// Add GoRouter configuration outside the App class
final _router = GoRouter(
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => const HomePage(),
      routes: [
        GoRoute(
          path: 'sign-in',
          builder: (context, state) {
            return SignInScreen(
              actions: [
                ForgotPasswordAction(((context, email) {
                  final uri = Uri(
                    path: '/sign-in/forgot-password',
                    queryParameters: <String, String?>{
                      'email': email,
                    },
                  );
                  context.push(uri.toString());
                })),
                AuthStateChangeAction(((context, state) {
                  final user = switch (state) {
                    SignedIn state => state.user,
                    UserCreated state => state.credential.user,
                    _ => null
                  };
                  if (user == null) {
                    return;
                  }
                  if (state is UserCreated) {
                    user.updateDisplayName(user.email!.split('@')[0]);
                  }
                  if (!user.emailVerified) {
                    user.sendEmailVerification();
                    const snackBar = SnackBar(
                        content: Text(
                            'Please check your email to verify your email address'));
                    ScaffoldMessenger.of(context).showSnackBar(snackBar);
                  }
                  context.pushReplacement('/');
                })),
              ],
            );
          },
          routes: [
            GoRoute(
              path: 'forgot-password',
              builder: (context, state) {
                final arguments = state.uri.queryParameters;
                return ForgotPasswordScreen(
                  email: arguments['email'],
                  headerMaxExtent: 200,
                );
              },
            ),
          ],
        ),
        GoRoute(
          path: 'profile',
          builder: (context, state) {
            return ProfileScreen(
              providers: const [],
              actions: [
                SignedOutAction((context) {
                  context.pushReplacement('/');
                }),
              ],
            );
          },
        ),
      ],
    ),
  ],
);
// end of GoRouter configuration

// Change MaterialApp to MaterialApp.router and add the routerConfig
class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: 'Firebase Meetup',
      theme: ThemeData(
        buttonTheme: Theme.of(context).buttonTheme.copyWith(
              highlightColor: Colors.deepPurple,
            ),
        primarySwatch: Colors.deepPurple,
        textTheme: GoogleFonts.robotoTextTheme(
          Theme.of(context).textTheme,
        ),
        visualDensity: VisualDensity.adaptivePlatformDensity,
        useMaterial3: true,
      ),
      routerConfig: _router, // new
    );
  }
}

Mỗi màn hình có một loại hành động khác nhau được liên kết với nó dựa trên trạng thái mới của luồng xác thực. Sau hầu hết các thay đổi trạng thái trong xác thực, bạn có thể định tuyến lại màn hình ưa thích, cho dù đó là màn hình chính hay màn hình khác, chẳng hạn như hồ sơ.

  1. Trong phương thức xây dựng của lớp HomePage , hãy tích hợp trạng thái ứng dụng với tiện ích AuthFunc :

lib/home_page.dart

import 'package:firebase_auth/firebase_auth.dart' // new
    hide EmailAuthProvider, PhoneAuthProvider;    // new
import 'package:flutter/material.dart';           // new
import 'package:provider/provider.dart';          // new

import 'app_state.dart';                          // new
import 'src/authentication.dart';                 // new
import 'src/widgets.dart';

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Firebase Meetup'),
      ),
      body: ListView(
        children: <Widget>[
          Image.asset('assets/codelab.png'),
          const SizedBox(height: 8),
          const IconAndDetail(Icons.calendar_today, 'October 30'),
          const IconAndDetail(Icons.location_city, 'San Francisco'),
          // Add from here
          Consumer<ApplicationState>(
            builder: (context, appState, _) => AuthFunc(
                loggedIn: appState.loggedIn,
                signOut: () {
                  FirebaseAuth.instance.signOut();
                }),
          ),
          // to here
          const Divider(
            height: 8,
            thickness: 1,
            indent: 8,
            endIndent: 8,
            color: Colors.grey,
          ),
          const Header("What we'll be doing"),
          const Paragraph(
            'Join us for a day full of Firebase Workshops and Pizza!',
          ),
        ],
      ),
    );
  }
}

Bạn khởi tạo tiện ích AuthFunc và gói nó trong tiện ích Consumer . Tiện ích Người tiêu dùng là cách thông thường mà gói 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 AuthFunc là tiện ích bổ sung mà bạn thử nghiệm.

Kiểm tra luồng xác thực

cdf2d25e436bd48d.png

  1. Trong ứng dụng, hãy nhấn vào nút Trả lời để bắt đầu SignInScreen .

2a2cd6d69d172369.png

  1. Nhập địa chỉ email. Nếu bạn đã đăng ký, hệ thống sẽ nhắc bạn nhập mật khẩu. Nếu không, hệ thống sẽ nhắc bạn điền vào mẫu đăng ký.

e5e65065dba36b54.png

  1. Nhập mật khẩu ít hơn sáu ký tự để kiểm tra quy trình xử lý lỗi. Nếu bạn đã đăng ký, bạn sẽ thấy mật khẩu thay thế.
  2. Nhập sai mật khẩu để kiểm tra quy trình xử lý lỗi.
  3. Nhập đúng mật khẩu. Bạn thấy trải nghiệm đăng nhập, cung cấp cho người dùng khả năng đăng xuất.

4ed811a25b0cf816.png

6. Viết tin nhắn tới Firestore

Thật tuyệt khi biết rằng người dùng sẽ đến nhưng bạn cần cung cấp cho khách những việc khác để làm trong ứng dụng. Điều gì sẽ xảy ra nếu họ có thể để lại tin nhắn trong sổ lưu bút? Họ có thể chia sẻ lý do tại sao họ lại hào hứng đến đây hoặc họ muốn gặp ai.

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

Mô hình dữ liệu

Firestore là 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 lưu trữ từng tin nhắn trong cuộc trò chuyện dưới dạng tài liệu trong bộ sưu tập guestbook , đây là bộ sưu tập cấp cao nhất.

7c20dc8424bb1d84.png

Thêm tin nhắn vào Firestore

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

  1. Tạo một tệp mới có tên guest_book.dart , thêm tiện ích trạng thái GuestBook để xây dựng các thành phần giao diện người dùng của trường thông báo và nút gửi:

lib/guest_book.dart

import 'dart:async';

import 'package:flutter/material.dart';

import 'src/widgets.dart';

class GuestBook extends StatefulWidget {
  const GuestBook({required this.addMessage, super.key});

  final FutureOr<void> Function(String message) addMessage;

  @override
  State<GuestBook> createState() => _GuestBookState();
}

class _GuestBookState extends State<GuestBook> {
  final _formKey = GlobalKey<FormState>(debugLabel: '_GuestBookState');
  final _controller = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Form(
        key: _formKey,
        child: Row(
          children: [
            Expanded(
              child: TextFormField(
                controller: _controller,
                decoration: const InputDecoration(
                  hintText: 'Leave a message',
                ),
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Enter your message to continue';
                  }
                  return null;
                },
              ),
            ),
            const SizedBox(width: 8),
            StyledButton(
              onPressed: () async {
                if (_formKey.currentState!.validate()) {
                  await widget.addMessage(_controller.text);
                  _controller.clear();
                }
              },
              child: Row(
                children: const [
                  Icon(Icons.send),
                  SizedBox(width: 4),
                  Text('SEND'),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Có một vài điểm đáng quan tâm ở đây. Trước tiên, bạn khởi tạo biểu mẫu để có thể xác thực rằng thư thực sự chứa nội dung và hiển thị cho người dùng thông báo lỗi nếu không có. Để xác thực biểu mẫu, bạn truy cập trạng thái biểu mẫu phía sau biểu mẫu bằng GlobalKey . Để biết thêm thông tin về Khóa và cách sử dụng chúng, hãy xem Khi nào nên sử dụng Khóa .

Cũng lưu ý cách bố trí các tiện ích, bạn có RowTextFormFieldStyledButton , chứa Row . Cũng lưu ý rằng TextFormField được gói trong một tiện ích Expanded , tiện ích này buộc TextFormField phải lấp đầy bất kỳ khoảng trống thừa nào trong hàng. Để hiểu rõ hơn lý do tại sao điều này là bắt buộc, hãy xem 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.

  1. Chỉnh sửa phần thân của HomePage để thêm hai dòng sau vào cuối phần tử 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ị tiện ích nhưng chưa đủ để làm bất cứ điều gì hữu ích. Bạn cập nhật mã này ngay để làm cho nó hoạt động.

Xem trước ứng dụng

Màn hình chính của ứng dụng trên Android có tích hợp trò chuyện

Màn hình chính của ứng dụng trên iOS có tích hợp trò chuyện

Màn hình chính của ứng dụng trên web có tích hợp trò chuyện

Màn hình chính của ứng dụng trên macOS có tích hợp trò chuyện

Khi người dùng nhấp vào GỬI , nó sẽ kích hoạt đoạn mã sau. 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 tin nhắn vào tài liệu mới có ID được tạo tự động trong 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 cung cấp cho tất cả người dùng đã đăng nhập.

  • Trong tệp lib/app_state.dart , hãy thêm phương thức addMessageToGuestBook . Bạn kết nối khả năng này với giao diện người dùng trong bước tiếp theo.

lib/app_state.dart

import 'package:cloud_firestore/cloud_firestore.dart'; // new
import 'package:firebase_auth/firebase_auth.dart'
    hide EmailAuthProvider, PhoneAuthProvider;
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';

import 'firebase_options.dart';

class ApplicationState extends ChangeNotifier {

  // Current content of ApplicationState elided ...

  // Add from here...
  Future<DocumentReference> addMessageToGuestBook(String message) {
    if (!_loggedIn) {
      throw Exception('Must be logged in');
    }

    return FirebaseFirestore.instance
        .collection('guestbook')
        .add(<String, dynamic>{
      'text': message,
      'timestamp': DateTime.now().millisecondsSinceEpoch,
      'name': FirebaseAuth.instance.currentUser!.displayName,
      'userId': FirebaseAuth.instance.currentUser!.uid,
    });
  }
  // ...to here.
}

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

Bạn có giao diện người dùng nơi 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 vào Firestore. Bây giờ tất cả những gì bạn cần làm là kết nối cả hai.

  • Trong tệp lib/home_page.dart , hãy thực hiện thay đổi sau đối với tiện ích HomePage :

lib/home_page.dart

import 'package:firebase_auth/firebase_auth.dart'
    hide EmailAuthProvider, PhoneAuthProvider;
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'app_state.dart';
import 'guest_book.dart';                         // new
import 'src/authentication.dart';
import 'src/widgets.dart';

class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Firebase Meetup'),
      ),
      body: ListView(
        children: <Widget>[
          Image.asset('assets/codelab.png'),
          const SizedBox(height: 8),
          const IconAndDetail(Icons.calendar_today, 'October 30'),
          const IconAndDetail(Icons.location_city, 'San Francisco'),
          Consumer<ApplicationState>(
            builder: (context, appState, _) => AuthFunc(
                loggedIn: appState.loggedIn,
                signOut: () {
                  FirebaseAuth.instance.signOut();
                }),
          ),
          const Divider(
            height: 8,
            thickness: 1,
            indent: 8,
            endIndent: 8,
            color: Colors.grey,
          ),
          const Header("What we'll be doing"),
          const Paragraph(
            'Join us for a day full of Firebase Workshops and Pizza!',
          ),
          // Modify from here...
          Consumer<ApplicationState>(
            builder: (context, appState, _) => Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                if (appState.loggedIn) ...[
                  const Header('Discussion'),
                  GuestBook(
                    addMessage: (message) =>
                        appState.addMessageToGuestBook(message),
                  ),
                ],
              ],
            ),
          ),
          // ...to here.
        ],
      ),
    );
  }
}

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

Kiểm tra việc gửi tin nhắn

  1. Nếu cần, hãy đăng nhập vào ứng dụng.
  2. Nhập một tin nhắn, chẳng hạn như Hey there! , sau đó nhấp vào GỬI .

Hành động này ghi thông báo vào cơ sở dữ liệu Firestore của bạn. Tuy nhiên, bạn không thấy thông báo trong ứng dụng Flutter thực tế của mình vì bạn vẫn cần triển khai truy xuất dữ liệu mà bạn sẽ thực hiện trong bước tiếp theo. Tuy nhiên, trong bảng điều khiển Cơ sở dữ liệu của bảng điều khiển Firebase, bạn có thể thấy thông báo đã thêm của mình trong bộ sưu tập guestbook . Nếu bạn gửi nhiều tin nhắn hơn, bạn sẽ thêm nhiều tài liệu hơn vào bộ sưu tập guestbook của mình. Ví dụ: xem đoạn mã sau:

713870af0b3b63c.png

7. Đọc tin nhắn

Thật thú vị 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. Đã đến lúc khắc phục điều đó!

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

Để hiển thị thông báo, bạn cần thêm trình nghe kích hoạt khi dữ liệu thay đổi, sau đó tạo thành phần giao diện người dùng hiển thị thông báo mới. Bạn 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 từ ứng dụng.

  1. Tạo một tệp mới guest_book_message.dart , thêm lớp sau để hiển thị chế độ xem có cấu trúc của dữ liệu mà bạn lưu trữ trong Firestore.

lib/guest_book_message.dart

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

  final String name;
  final String message;
}
  1. Trong tệp lib/app_state.dart , hãy thêm các mục nhập sau:

lib/app_state.dart

import 'dart:async';                                     // new

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart'
    hide EmailAuthProvider, PhoneAuthProvider;
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';

import 'firebase_options.dart';
import 'guest_book_message.dart';                        // new
  1. 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 sau:

lib/app_state.dart

  bool _loggedIn = false;
  bool get loggedIn => _loggedIn;

  // Add from here...
  StreamSubscription<QuerySnapshot>? _guestBookSubscription;
  List<GuestBookMessage> _guestBookMessages = [];
  List<GuestBookMessage> get guestBookMessages => _guestBookMessages;
  // ...to here.
  1. Trong phần khởi tạo của ApplicationState , hãy thêm các dòng sau để đăng ký truy vấn về 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/app_state.dart

  Future<void> init() async {
    await Firebase.initializeApp(
        options: DefaultFirebaseOptions.currentPlatform);

    FirebaseUIAuth.configureProviders([
      EmailAuthProvider(),
    ]);
    
    FirebaseAuth.instance.userChanges().listen((user) {
      if (user != null) {
        _loggedIn = true;
        _guestBookSubscription = FirebaseFirestore.instance
            .collection('guestbook')
            .orderBy('timestamp', descending: true)
            .snapshots()
            .listen((snapshot) {
          _guestBookMessages = [];
          for (final document in snapshot.docs) {
            _guestBookMessages.add(
              GuestBookMessage(
                name: document.data()['name'] as String,
                message: document.data()['text'] as String,
              ),
            );
          }
          notifyListeners();
        });
      } else {
        _loggedIn = false;
        _guestBookMessages = [];
        _guestBookSubscription?.cancel();
      }
      notifyListeners();
    });
  }

Phần này rất quan trọng vì đó là nơi bạn xây dựng một truy vấn đối với 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 xây dựng lại bộ đệm cục bộ của các tin nhắn 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 này. Có rất nhiều điều đang diễn ra ở đây, vì vậy bạn nên khám phá nó trong trình gỡ lỗi để kiểm tra điều gì xảy ra nhằm có được mô hình tinh thần rõ ràng hơn. Để biết thêm thông tin, hãy xem Nhận bản cập nhật theo thời gian thực với Firestore .

  1. Trong tệp lib/guest_book.dart , hãy thêm nội dung nhập sau:
import 'guest_book_message.dart';
  1. Trong tiện ích GuestBook , hãy thêm danh sách tin nhắn như một phần của cấu hình để kết nối trạng thái thay đổi này với giao diện người dùng:

lib/guest_book.dart

class GuestBook extends StatefulWidget {
  // Modify the following line:
  const GuestBook({
    super.key, 
    required this.addMessage, 
    required this.messages,
  });

  final FutureOr<void> Function(String message) addMessage;
  final List<GuestBookMessage> messages; // new

  @override
  _GuestBookState createState() => _GuestBookState();
}
  1. Trong _GuestBookState , sửa đổi phương thức build như sau để hiển thị cấu hình này:

lib/guest_book.dart

class _GuestBookState extends State<GuestBook> {
  final _formKey = GlobalKey<FormState>(debugLabel: '_GuestBookState');
  final _controller = TextEditingController();

  @override
  // Modify from here...
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        // ...to here.
        Padding(
          padding: const EdgeInsets.all(8.0),
          child: Form(
            key: _formKey,
            child: Row(
              children: [
                Expanded(
                  child: TextFormField(
                    controller: _controller,
                    decoration: const InputDecoration(
                      hintText: 'Leave a message',
                    ),
                    validator: (value) {
                      if (value == null || value.isEmpty) {
                        return 'Enter your message to continue';
                      }
                      return null;
                    },
                  ),
                ),
                const SizedBox(width: 8),
                StyledButton(
                  onPressed: () async {
                    if (_formKey.currentState!.validate()) {
                      await widget.addMessage(_controller.text);
                      _controller.clear();
                    }
                  },
                  child: Row(
                    children: const [
                      Icon(Icons.send),
                      SizedBox(width: 4),
                      Text('SEND'),
                    ],
                  ),
                ),
              ],
            ),
          ),
        ),
        // Modify from here...
        const SizedBox(height: 8),
        for (var message in widget.messages)
          Paragraph('${message.name}: ${message.message}'),
        const SizedBox(height: 8),
      ],
      // ...to here.
    );
  }
}

Bạn gói nội dung trước đó của phương thức build() bằng một tiện ích Column và sau đó bạn thêm một bộ sưu tập for ở cuối các phần tử con của Column để tạo một Paragraph mới cho mỗi thông báo trong danh sách các thông báo.

  1. Cập nhật nội dung của HomePage để xây dựng chính xác GuestBook với thông số messages mới:

lib/home_page.dart

Consumer<ApplicationState>(
  builder: (context, appState, _) => Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      if (appState.loggedIn) ...[
        const Header('Discussion'),
        GuestBook(
          addMessage: (message) =>
              appState.addMessageToGuestBook(message),
          messages: appState.guestBookMessages, // new
        ),
      ],
    ],
  ),
),

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

Firestore tự động và ngay lập tức đồng bộ hóa dữ liệu với các máy khách đã đăng ký cơ sở dữ liệu.

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

  1. Trong ứng dụng, hãy tìm các tin nhắn bạn đã tạo trước đó trong cơ sở dữ liệu.
  2. Viết tin nhắn mới. Chúng xuất hiện ngay lập tức.
  3. Mở không gian làm việc của bạn trong nhiều cửa sổ hoặc tab. Tin nhắn được đồng bộ hóa theo thời gian thực trên các cửa sổ và tab.
  4. Tùy chọn: Trong menu Cơ sở dữ liệu của bảng điều khiển Firebase, hãy xóa, sửa đổi hoặc thêm thông báo mới theo cách thủ công. Tất cả các thay đổi đều xuất hiện trong giao diện người dùng.

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

Xem trước ứng dụng

Màn hình chính của ứng dụng trên Android có tích hợp trò chuyện

Màn hình chính của ứng dụng trên iOS có tích hợp trò chuyện

Màn hình chính của ứng dụng trên web có tích hợp trò chuyện

Màn hình chính của ứng dụng trên macOS có tích hợp trò chuyện

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 Firestore để sử dụng chế độ thử nghiệm, điều đó có nghĩa là cơ sở dữ liệu của bạn được mở để đọc và ghi. Tuy nhiên, bạn chỉ nên sử dụng chế độ thử nghiệm trong giai đoạn đầu phát triển. Cách tốt nhất là 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 phát triển ứng dụng. Bảo mật là điều không thể thiếu đối với cấu trúc và hành vi của ứng dụng của bạn.

Quy tắc bảo mật của Firebase 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 mình. Cú pháp quy tắc linh hoạt cho phép bạn tạo các quy tắc khớp với mọi thứ từ tất cả các thao tác ghi vào toàn bộ cơ sở dữ liệu đến các thao tác trên một tài liệu cụ thể.

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

  1. Trong menu Phát triển của bảng điều khiển Firebase, hãy nhấp vào Cơ sở dữ liệu > Quy tắc . Bạn sẽ thấy các quy tắc bảo mật mặc định sau đây và cảnh báo về các quy tắc được công khai:

7767a2d2e64e7275.png

  1. Xác định các bộ sưu tập mà ứng dụng ghi dữ liệu vào:

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

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 nên 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.

  1. Thêm quy tắc đọc và ghi vào bộ quy tắc của bạn:
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, chỉ người dùng đã đăng nhập mới có thể đọc tin nhắn trong sổ khách nhưng chỉ tác giả của tin nhắn mới có thể chỉnh sửa tin nhắn.

  1. Thêm xác thực dữ liệu để đảm bảo rằng tất cả các trường dự kiến ​​đề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 trả lời 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 trò chuyện khi 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à khi họ nói như vậy trong cuộc trò chuyện.

Ở bước này, bạn sắp xếp và cho mọi người biết có bao nhiêu người sẽ đến. Bạn thêm một số khả năng vào trạng thái ứng dụng. Đầu tiên là khả năng người dùng đã đăng nhập có thể quyết định xem họ có tham dự hay không. Thứ hai là bộ đếm có bao nhiêu người đang tham dự.

  1. Trong tệp lib/app_state.dart , hãy thêm các dòng sau vào phần truy cập của ApplicationState để mã UI có thể tương tác với trạng thái này:

lib/app_state.dart

int _attendees = 0;
int get attendees => _attendees;

Attending _attending = Attending.unknown;
StreamSubscription<DocumentSnapshot>? _attendingSubscription;
Attending get attending => _attending;
set attending(Attending attending) {
  final userDoc = FirebaseFirestore.instance
      .collection('attendees')
      .doc(FirebaseAuth.instance.currentUser!.uid);
  if (attending == Attending.yes) {
    userDoc.set(<String, dynamic>{'attending': true});
  } else {
    userDoc.set(<String, dynamic>{'attending': false});
  }
}
  1. Cập nhật phương thức init() của ApplicationState như sau:

lib/app_state.dart

  Future<void> init() async {
    await Firebase.initializeApp(
        options: DefaultFirebaseOptions.currentPlatform);

    FirebaseUIAuth.configureProviders([
      EmailAuthProvider(),
    ]);

    // Add from here...
    FirebaseFirestore.instance
        .collection('attendees')
        .where('attending', isEqualTo: true)
        .snapshots()
        .listen((snapshot) {
      _attendees = snapshot.docs.length;
      notifyListeners();
    });
    // ...to here.

    FirebaseAuth.instance.userChanges().listen((user) {
      if (user != null) {
        _loggedIn = true;
        _emailVerified = user.emailVerified;
        _guestBookSubscription = FirebaseFirestore.instance
            .collection('guestbook')
            .orderBy('timestamp', descending: true)
            .snapshots()
            .listen((snapshot) {
          _guestBookMessages = [];
          for (final document in snapshot.docs) {
            _guestBookMessages.add(
              GuestBookMessage(
                name: document.data()['name'] as String,
                message: document.data()['text'] as String,
              ),
            );
          }
          notifyListeners();
        });
        // Add from here...
        _attendingSubscription = FirebaseFirestore.instance
            .collection('attendees')
            .doc(user.uid)
            .snapshots()
            .listen((snapshot) {
          if (snapshot.data() != null) {
            if (snapshot.data()!['attending'] as bool) {
              _attending = Attending.yes;
            } else {
              _attending = Attending.no;
            }
          } else {
            _attending = Attending.unknown;
          }
          notifyListeners();
        });
        // ...to here.
      } else {
        _loggedIn = false;
        _emailVerified = false;
        _guestBookMessages = [];
        _guestBookSubscription?.cancel();
        _attendingSubscription?.cancel(); // new
      }
      notifyListeners();
    });
  }

Mã này thêm truy vấn luôn đăng ký để xác định 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 để xác định xem người dùng có tham dự hay không.

  1. Thêm bảng liệt kê sau vào đầu tệp lib/app_state.dart .

lib/app_state.dart

enum Attending { yes, no, unknown }
  1. Tạo một tệp mới yes_no_selection.dart , xác định một tiện ích mới hoạt động giống như các nút radio:

lib/yes_no_selection.dart

import 'package:flutter/material.dart';

import 'app_state.dart';
import 'src/widgets.dart';

class YesNoSelection extends StatelessWidget {
  const YesNoSelection(
      {super.key, required this.state, required this.onSelection});
  final Attending state;
  final void Function(Attending selection) onSelection;

  @override
  Widget build(BuildContext context) {
    switch (state) {
      case Attending.yes:
        return Padding(
          padding: const EdgeInsets.all(8.0),
          child: Row(
            children: [
              FilledButton(
                onPressed: () => onSelection(Attending.yes),
                child: const Text('YES'),
              ),
              const SizedBox(width: 8),
              TextButton(
                onPressed: () => onSelection(Attending.no),
                child: const Text('NO'),
              ),
            ],
          ),
        );
      case Attending.no:
        return Padding(
          padding: const EdgeInsets.all(8.0),
          child: Row(
            children: [
              TextButton(
                onPressed: () => onSelection(Attending.yes),
                child: const Text('YES'),
              ),
              const SizedBox(width: 8),
              FilledButton(
                onPressed: () => onSelection(Attending.no),
                child: const Text('NO'),
              ),
            ],
          ),
        );
      default:
        return Padding(
          padding: const EdgeInsets.all(8.0),
          child: Row(
            children: [
              StyledButton(
                onPressed: () => onSelection(Attending.yes),
                child: const Text('YES'),
              ),
              const SizedBox(width: 8),
              StyledButton(
                onPressed: () => onSelection(Attending.no),
                child: const Text('NO'),
              ),
            ],
          ),
        );
    }
  }
}

Nó bắt đầu ở trạng thái không xác định và không chọn cũng như Không . Sau khi người dùng chọn xem họ có tham dự hay không, bạn sẽ hiển thị tùy chọn đó được đánh dấu bằng nút điền và tùy chọn còn lại sẽ lùi xuống bằng cách hiển thị phẳng.

  1. Cập nhật phương thức build() của HomePage để tận dụng YesNoSelection , cho phép người dùng đã đăng nhập chỉ định xem họ có tham dự hay không và hiển thị số lượng người tham dự sự kiện:

lib/home_page.dart

Consumer<ApplicationState>(
  builder: (context, appState, _) => Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      // Add from here...
      switch (appState.attendees) {
        1 => const Paragraph('1 person going'),
        >= 2 => Paragraph('${appState.attendees} people going'),
        _ => const Paragraph('No one going'),
      },
      // ...to here.
      if (appState.loggedIn) ...[
        // Add from here...
        YesNoSelection(
          state: appState.attending,
          onSelection: (attending) => appState.attending = attending,
        ),
        // ...to here.
        const Header('Discussion'),
        GuestBook(
          addMessage: (message) =>
              appState.addMessageToGuestBook(message),
          messages: appState.guestBookMessages,
        ),
      ],
    ],
  ),
),

Thêm quy tắc

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

  1. Trong bộ sưu tập attendees , lấy UID xác thực mà bạn đã sử dụng làm tên tài liệu 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:
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // ... //
    match /attendees/{userId} {
      allow read: if true;
      allow write: if request.auth.uid == userId;
    }
  }
}

Điều này 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ư ở đó mà chỉ người tạo mới có thể cập nhật nó.

  1. Thêm xác thực dữ liệu để đảm bảo rằng tất cả các trường dự kiến ​​đề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;

    }
  }
}
  1. Tùy chọn: Trong ứng dụng, nhấp vào nút để xem kết quả trong bảng điều khiển Firestore trong bảng điều khiển Firebase.

Xem trước ứng dụng

Màn hình chính của ứng dụng trên Android

Màn hình chính của ứng dụng trên iOS

Màn hình chính của ứng dụng trên web

Màn hình chính của ứng dụng trên macOS

10. Xin chúc mừng!

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

Tìm hiểu thêm