ทำความรู้จัก Firebase สำหรับ Flutter

จัดทุกอย่างให้เป็นระเบียบอยู่เสมอด้วยคอลเล็กชัน บันทึกและจัดหมวดหมู่เนื้อหาตามค่ากำหนดของคุณ

1. ก่อนที่คุณจะเริ่ม

ใน Codelab นี้ คุณจะได้เรียนรู้พื้นฐานบางอย่างของ Firebase เพื่อสร้างแอป Flutter สำหรับอุปกรณ์เคลื่อนที่สำหรับ Android และ iOS

ข้อกำหนดเบื้องต้น

Codelab นี้ถือว่าคุณคุ้นเคยกับ Flutter และคุณได้ติดตั้ง Flutter SDK และ ตัวแก้ไข แล้ว

สิ่งที่คุณจะสร้าง

ใน Codelab นี้ คุณจะสร้าง RSVP กิจกรรมและแอปแชทสมุดเยี่ยมบน Android, iOS, เว็บ และ macOS โดยใช้ Flutter คุณจะตรวจสอบสิทธิ์ผู้ใช้ด้วยการตรวจสอบสิทธิ์ Firebase และซิงค์ข้อมูลโดยใช้ Cloud Firestore

สิ่งที่คุณต้องการ

คุณสามารถเรียกใช้ Codelab นี้โดยใช้อุปกรณ์ใดๆ ต่อไปนี้:

  • อุปกรณ์จริง (Android หรือ iOS) ที่เชื่อมต่อกับคอมพิวเตอร์ของคุณและตั้งค่าเป็นโหมดนักพัฒนาซอฟต์แวร์
  • เครื่องจำลอง iOS (ต้อง ติดตั้งเครื่องมือ Xcode )
  • โปรแกรมจำลอง Android (ต้องมีการตั้งค่าใน Android Studio )

นอกเหนือจากข้างต้น คุณจะต้อง:

  • เบราว์เซอร์ที่คุณเลือก เช่น Chrome
  • IDE หรือโปรแกรมแก้ไขข้อความที่คุณเลือก เช่น Android Studio หรือ VS Code ที่กำหนดค่าด้วยปลั๊กอิน Dart และ Flutter
  • Flutter เวอร์ชันล่าสุด stable (หรือ beta หากคุณสนุกกับการใช้ชีวิตแบบเอดจ์)
  • บัญชี Google เช่น บัญชี gmail สำหรับสร้างและจัดการโปรเจ็กต์ Firebase
  • เครื่องมือบรรทัดคำสั่ง firebase ลงชื่อเข้าใช้บัญชี gmail ของคุณ
  • โค้ดตัวอย่างของ Codelab ดูขั้นตอนถัดไปสำหรับวิธีรับรหัส

2. รับโค้ดตัวอย่าง

เริ่มต้นด้วยการดาวน์โหลดเวอร์ชันเริ่มต้นของโครงการของเราจาก GitHub

โคลนที่ เก็บ GitHub จากบรรทัดคำสั่ง:

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

หรือหากคุณติดตั้งเครื่องมือ cli ของ GitHub :

gh repo clone flutter/codelabs flutter-codelabs

โค้ดตัวอย่างควรถูกโคลนลงในไดเร็กทอรี flutter-codelabs ซึ่งมีโค้ดสำหรับคอลเล็กชันของ codelabs รหัสสำหรับ codelab นี้อยู่ใน flutter-codelabs/firebase-get-to-know-flutter

โครงสร้างไดเร็กทอรีภายใต้ flutter-codelabs/firebase-get-to-know-flutter คือชุดของสแนปชอตที่คุณควรอยู่ในตอนท้ายของแต่ละขั้นตอนที่มีชื่อ นี่คือขั้นตอนที่ 2 ดังนั้นการค้นหาไฟล์ที่ตรงกันจึงง่ายเหมือน:

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

หากคุณต้องการข้ามไปข้างหน้า หรือดูว่าบางสิ่งควรเป็นอย่างไรหลังจากขั้นตอนหนึ่ง ให้ดูในไดเร็กทอรีที่ตั้งชื่อตามขั้นตอนที่คุณสนใจ

นำเข้าแอพเริ่มต้น

เปิดหรือนำเข้า flutter-codelabs/firebase-get-to-know-flutter/step_02 ลงใน IDE ที่คุณต้องการ ไดเร็กทอรีนี้มีโค้ดเริ่มต้นสำหรับ codelab ซึ่งประกอบด้วยแอปพบปะ Flutter ที่ยังไม่ได้ใช้งาน

ค้นหาไฟล์ที่จะใช้งาน

รหัสในแอพนี้กระจายไปทั่วหลายไดเร็กทอรี การแบ่งฟังก์ชันนี้ออกแบบมาเพื่อให้ทำงานได้ง่ายขึ้น โดยการจัดกลุ่มโค้ดตามฟังก์ชัน

ค้นหาไฟล์ต่อไปนี้ในโครงการ:

  • lib/main.dart : ไฟล์นี้มีจุดเข้าใช้งานหลักและวิดเจ็ตแอปพลิเคชัน
  • lib/src/widgets.dart : ไฟล์นี้มีวิดเจ็ตจำนวนหนึ่งที่ช่วยกำหนดสไตล์ของแอปพลิเคชันให้เป็นมาตรฐาน สิ่งเหล่านี้ใช้เพื่อเขียนหน้าจอของแอพเริ่มต้น
  • lib/src/authentication.dart : ไฟล์นี้มีการใช้งาน FirebaseUI Auth บางส่วนพร้อมชุดวิดเจ็ตเพื่อสร้างประสบการณ์ผู้ใช้ในการเข้าสู่ระบบสำหรับการตรวจสอบสิทธิ์ตามอีเมล Firebase วิดเจ็ตเหล่านี้สำหรับขั้นตอนการตรวจสอบสิทธิ์ยังไม่ได้ใช้ในแอพเริ่มต้น แต่คุณจะเชื่อมต่อในเร็วๆ นี้

คุณจะเพิ่มไฟล์เพิ่มเติมตามความจำเป็นเพื่อสร้างส่วนที่เหลือของแอปพลิเคชัน

การตรวจสอบไฟล์ lib/main.dart

แอปนี้ใช้ประโยชน์จากแพ็คเกจ google_fonts เพื่อให้เราสร้าง Roboto เป็นแบบอักษรเริ่มต้นทั่วทั้งแอป แบบฝึกหัดสำหรับผู้อ่านที่มีแรงบันดาลใจคือการสำรวจ fonts.google.com และใช้แบบอักษรที่คุณพบในส่วนต่างๆ ของแอป

คุณกำลังใช้วิดเจ็ตตัวช่วยจาก lib/src/widgets.dart ในรูปแบบของ Header , Paragraph และ IconAndDetail วิดเจ็ตเหล่านี้ลดความยุ่งเหยิงในเลย์เอาต์ของหน้าที่อธิบายไว้ใน HomePage โดยกำจัดโค้ดที่ซ้ำกัน สิ่งนี้มีประโยชน์เพิ่มเติมในการทำให้มีรูปลักษณ์ที่สม่ำเสมอ

นี่คือลักษณะที่แอปของคุณปรากฏบน Android, iOS, เว็บ และ macOS:

ตัวอย่างแอป

3. สร้างและตั้งค่าโปรเจ็กต์ Firebase

การแสดงข้อมูลกิจกรรมเป็นสิ่งที่ดีสำหรับแขกของคุณ แต่การแสดงกิจกรรมเพียงอย่างเดียวไม่เป็นประโยชน์สำหรับทุกคน มาเพิ่มฟังก์ชันการทำงานแบบไดนามิกให้กับแอปนี้กันเถอะ สำหรับสิ่งนี้ คุณจะต้องเชื่อมต่อ Firebase กับแอปของคุณ ในการเริ่มต้นใช้งาน Firebase คุณจะต้องสร้างและตั้งค่าโปรเจ็กต์ Firebase

สร้างโปรเจ็กต์ Firebase

  1. ลงชื่อเข้าใช้ Firebase
  2. ในคอนโซล Firebase คลิก เพิ่มโปรเจ็ กต์ (หรือ สร้างโปรเจ็ กต์ ) และตั้งชื่อโปรเจ็ กต์ Firebase Firebase-Flutter-Codelab

4395e4e67c08043a.png

  1. คลิกผ่านตัวเลือกการสร้างโครงการ ยอมรับข้อกำหนดของ Firebase หากได้รับแจ้ง ข้ามการตั้งค่า Google Analytics เนื่องจากคุณจะไม่ได้ใช้ Analytics สำหรับแอปนี้

b7138cde5f2c7b61.png

หากต้องการเรียนรู้เพิ่มเติมเกี่ยวกับโปรเจ็กต์ Firebase โปรดดู ทำความเข้าใจโปรเจ็กต์ Firebase

แอปที่คุณกำลังสร้างใช้ผลิตภัณฑ์ Firebase หลายอย่างที่พร้อมใช้งานสำหรับเว็บแอป:

  • การ ตรวจสอบสิทธิ์ Firebase เพื่อให้ผู้ใช้ลงชื่อเข้าใช้แอปของคุณได้
  • Cloud Firestore เพื่อบันทึกข้อมูลที่มีโครงสร้างบนคลาวด์และรับการแจ้งเตือนทันทีเมื่อข้อมูลเปลี่ยนแปลง
  • กฎความปลอดภัยของ Firebase เพื่อรักษาความปลอดภัยฐานข้อมูลของคุณ

ผลิตภัณฑ์เหล่านี้บางส่วนต้องมีการกำหนดค่าพิเศษหรือต้องเปิดใช้งานโดยใช้คอนโซล Firebase

เปิดใช้งานการลงชื่อเข้าใช้อีเมล สำหรับการตรวจสอบสิทธิ์ Firebase

ในการอนุญาตให้ผู้ใช้ลงชื่อเข้าใช้เว็บแอป คุณจะต้องใช้วิธีลงชื่อเข้าใช้ อีเมล/รหัสผ่าน สำหรับ Codelab นี้:

  1. ในคอนโซล Firebase ให้ขยายเมนู บิล ด์ในแผงด้านซ้าย
  2. คลิก Authentication จากนั้นคลิกปุ่ม Get Started จากนั้นไปที่แท็บ Sign-in method (หรือ คลิกที่นี่ เพื่อไปยังแท็บ Sign-in method โดยตรง)
  3. คลิก อีเมล/รหัสผ่าน ในรายการ ผู้ให้บริการลงชื่อเข้าใช้ ตั้งค่าสวิตช์ เปิดใช้งาน ไปที่ตำแหน่งเปิด จากนั้นคลิก บันทึก 58e3e3e23c2f16a4.png

เปิดใช้งาน Cloud Firestore

เว็บแอปใช้ Cloud Firestore เพื่อบันทึกข้อความแชทและรับข้อความแชทใหม่

เปิดใช้งาน Cloud Firestore:

  1. ในส่วน บิล ด์ของคอนโซล Firebase ให้คลิก Cloud Firestore
  2. คลิก สร้างฐานข้อมูล 99e8429832d23fa3.png
  1. เลือกตัวเลือก เริ่มในโหมดทดสอบ อ่านข้อจำกัดความรับผิดชอบเกี่ยวกับกฎความปลอดภัย โหมดทดสอบทำให้มั่นใจได้ว่าคุณสามารถเขียนไปยังฐานข้อมูลได้อย่างอิสระในระหว่างการพัฒนา คลิก ถัดไป 6be00e26c72ea032.png
  1. เลือกตำแหน่งสำหรับฐานข้อมูลของคุณ (คุณสามารถใช้ค่าเริ่มต้นได้) โปรดทราบว่าสถานที่นี้ไม่สามารถเปลี่ยนแปลงได้ในภายหลัง 278656eefcfb0216.png
  2. คลิก เปิดใช้งาน

4. การกำหนดค่า Firebase

ในการใช้ Firebase กับ Flutter คุณต้องทำตามขั้นตอนเพื่อกำหนดค่าโปรเจ็กต์ Flutter เพื่อใช้ไลบรารี FlutterFire อย่างถูกต้อง:

  • เพิ่มการพึ่งพา FlutterFire ให้กับโปรเจ็กต์ของคุณ
  • ลงทะเบียนแพลตฟอร์มที่ต้องการในโครงการ Firebase
  • ดาวน์โหลดไฟล์การกำหนดค่าเฉพาะแพลตฟอร์ม และเพิ่มลงในโค้ด

ในไดเร็กทอรีระดับบนสุดของแอพ Flutter ของคุณ มีไดเร็กทอรีย่อยที่เรียกว่า android , ios , macos และ web ไดเร็กทอรีเหล่านี้เก็บไฟล์การกำหนดค่าเฉพาะแพลตฟอร์มสำหรับ iOS และ Android ตามลำดับ

กำหนดค่าการพึ่งพา

คุณต้องเพิ่มไลบรารี FlutterFire สำหรับผลิตภัณฑ์ Firebase สองรายการที่คุณใช้ในแอปนี้ - Firebase Auth และ Cloud Firestore รันคำสั่งสามคำสั่งต่อไปนี้เพื่อเพิ่ม depencies

$ flutter pub add firebase_core

firebase_core เป็นรหัสทั่วไปที่จำเป็นสำหรับปลั๊กอิน Firebase Flutter ทั้งหมด

$ flutter pub add firebase_auth

firebase_auth เปิดใช้งานการผสานรวมกับความสามารถในการตรวจสอบสิทธิ์ของ Firebase

$ flutter pub add cloud_firestore

cloud_firestore ช่วยให้สามารถเข้าถึงการจัดเก็บข้อมูล Cloud Firestore

$ flutter pub add provider

แพ็คเกจ firebase_ui_auth จัดเตรียมชุดวิดเจ็ตและยูทิลิตีเฉพาะสำหรับการเพิ่มความเร็วของนักพัฒนาด้วยโฟลว์การพิสูจน์ตัวตน

$ flutter pub add firebase_ui_auth

ในขณะที่คุณได้เพิ่มแพ็คเกจที่จำเป็นแล้ว คุณยังต้องกำหนดค่าโปรเจ็กต์ iOS, Android, macOS และ Web runner เพื่อใช้ Firebase อย่างเหมาะสม คุณกำลังใช้แพ็คเกจ provider ที่จะเปิดใช้งานการแยกตรรกะทางธุรกิจออกจากตรรกะการแสดงผล

ติดตั้ง flutterfire

FlutterFire CLI ขึ้นอยู่กับ Firebase CLI พื้นฐาน หากคุณยังไม่ได้ดำเนินการ ตรวจสอบให้แน่ใจว่าได้ติดตั้ง Firebase CLI บนเครื่องของคุณแล้ว

ถัดไป ติดตั้ง FlutterFire CLI โดยรันคำสั่งต่อไปนี้:

$ dart pub global activate flutterfire_cli

เมื่อติดตั้งแล้ว คำสั่ง flutterfire จะพร้อมใช้งานทั่วโลก

การกำหนดค่าแอพของคุณ

CLI จะดึงข้อมูลจากโปรเจ็กต์ Firebase และแอปพลิเคชันโปรเจ็กต์ที่เลือกเพื่อสร้างการกำหนดค่าทั้งหมดสำหรับแพลตฟอร์มเฉพาะ

ในรูทของแอปพลิเคชันของคุณ ให้รันคำสั่ง configuration:

$ flutterfire configure

คำสั่งการกำหนดค่าจะแนะนำคุณผ่านกระบวนการต่างๆ:

  1. การเลือกโปรเจ็กต์ Firebase (ตามไฟล์ .firebaserc หรือจากคอนโซล Firebase)
  2. แจ้งว่าคุณต้องการกำหนดค่าสำหรับแพลตฟอร์มใด (เช่น Android, iOS, macOS และเว็บ)
  3. ระบุว่าควรใช้แอปพลิเคชัน Firebase สำหรับแพลตฟอร์มที่เลือกเพื่อแยกการกำหนดค่า โดยค่าเริ่มต้น CLI จะพยายามจับคู่แอป Firebase โดยอัตโนมัติตามการกำหนดค่าโปรเจ็กต์ปัจจุบันของคุณ
  4. สร้างไฟล์ firebase_options.dart ในโครงการของคุณ

กำหนดค่า macOS

Flutter บน macOS สร้างแอปพลิเคชันแซนด์บ็อกซ์อย่างสมบูรณ์ เนื่องจากแอปพลิเคชันนี้กำลังผสานรวมโดยใช้เครือข่ายเพื่อสื่อสารกับเซิร์ฟเวอร์ Firebase คุณจะต้องกำหนดค่าแอปพลิเคชันของคุณด้วยสิทธิ์ไคลเอ็นต์เครือข่าย

macos/Runner/DebugProfile.entitlements

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>com.apple.security.app-sandbox</key>
	<true/>
	<key>com.apple.security.cs.allow-jit</key>
	<true/>
	<key>com.apple.security.network.server</key>
	<true/>
  <!-- Add the following two lines -->
	<key>com.apple.security.network.client</key>
	<true/>
</dict>
</plist>

macos/Runner/Release.entitlements

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>com.apple.security.app-sandbox</key>
	<true/>
  <!-- Add the following two lines -->
	<key>com.apple.security.network.client</key>
	<true/>
</dict>
</plist>

ดู การให้สิทธิ์และแซนด์บ็อกซ์ของแอป สำหรับรายละเอียดเพิ่มเติม

5. เพิ่มการลงชื่อเข้าใช้ของผู้ใช้ (RSVP)

เมื่อคุณได้เพิ่ม Firebase ลงในแอปแล้ว คุณสามารถตั้งค่าปุ่ม RSVP ที่ลงทะเบียนผู้คนโดยใช้ Firebase Authentication สำหรับ Android native, iOS native และ Web มีแพ็คเกจ FirebaseUI Auth ที่สร้างไว้ล่วงหน้า แต่สำหรับ Flutter คุณจะต้องสร้างความสามารถนี้

โปรเจ็กต์ที่คุณเรียกค้นในขั้นตอนที่ 2 ได้รวมชุดวิดเจ็ตที่ใช้อินเทอร์เฟซผู้ใช้สำหรับโฟลว์การรับรองความถูกต้องส่วนใหญ่ คุณจะใช้ตรรกะทางธุรกิจเพื่อรวมการตรวจสอบสิทธิ์ Firebase เข้ากับแอปพลิเคชัน

ตรรกะทางธุรกิจกับผู้ให้บริการ

คุณจะใช้แพ็คเกจ provider เพื่อสร้างออบเจ็กต์สถานะแอปพลิเคชันแบบรวมศูนย์พร้อมใช้งานทั่วทั้งแผนผังวิดเจ็ต Flutter ของแอปพลิเคชัน ในการเริ่มต้น แก้ไขการนำเข้าที่ด้านบนของ lib/main.dart :

lib/main.dart

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

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

บรรทัด import แนะนำ Firebase Core และ Auth ดึงแพ็คเกจ provider ที่คุณใช้เพื่อทำให้อ็อบเจ็กต์สถานะแอปพลิเคชันพร้อมใช้งานผ่านแผนผังวิดเจ็ต และรวมวิดเจ็ตการพิสูจน์ตัวตนจาก firebase_ui_auth

ApplicationState ออบเจ็กต์สถานะแอปพลิ ApplicationState นี้มีหน้าที่หลักอย่างหนึ่งสำหรับขั้นตอนนี้ ซึ่งก็คือการแจ้งเตือนแผนผังวิดเจ็ตว่ามีการอัพเดตเป็นสถานะที่พิสูจน์ตัวตนแล้ว เพิ่มคลาสต่อไปนี้ต่อท้าย lib/main.dart :

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

เรากำลังใช้ผู้ให้บริการที่นี่เพื่อสื่อสารกับแอปที่ระบุสถานะการเข้าสู่ระบบของผู้ใช้ ไม่มีอะไรเพิ่มเติม ในการเข้าสู่ระบบผู้ใช้ เราจะใช้ UI ที่จัดทำโดย firebase_ui_auth ซึ่งเป็นวิธีที่ยอดเยี่ยมในการบูตหน้าจอเข้าสู่ระบบสำหรับแอปพลิเคชันของคุณอย่างรวดเร็ว

การรวมขั้นตอนการตรวจสอบสิทธิ์

เมื่อคุณเริ่มต้นสถานะแอปพลิเคชันแล้ว ก็ถึงเวลาที่จะเชื่อมโยงสถานะแอปพลิเคชันเข้ากับการเริ่มต้นแอป และเพิ่มโฟลว์การตรวจสอบสิทธิ์ลงใน HomePage อัปเดตจุดเริ่มต้นหลักเพื่อรวมสถานะแอปพลิเคชันผ่านแพ็คเกจ provider :

lib/main.dart

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

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

การปรับเปลี่ยนฟังก์ชัน main ทำให้แพ็กเกจผู้ให้บริการรับผิดชอบในการสร้างอินสแตนซ์อ็อบเจ็กต์สถานะแอปพลิเคชันโดยใช้วิดเจ็ต ChangeNotifierProvider คุณกำลังใช้คลาสตัวให้บริการเฉพาะนี้เนื่องจากอ็อบเจ็กต์สถานะแอปพลิเคชันขยาย ChangeNotifier และทำให้แพ็คเกจ provider ทราบเมื่อจะแสดงวิดเจ็ตที่ขึ้นต่อกันอีกครั้ง

เนื่องจากเราใช้ FirebaseUI สำหรับ Flutter เราจะอัปเดตแอปเพื่อจัดการการนำทางไปยังหน้าจอต่างๆ ที่ FirebaseUI จัดเตรียมไว้ให้เรา ในการดำเนินการนี้ เราเพิ่มคุณสมบัติ initialRoute และเพิ่มหน้าจอที่เราต้องการซึ่งเราสามารถกำหนดเส้นทางไปยังภายใต้คุณสมบัติ routes การเปลี่ยนแปลงควรมีลักษณะดังนี้:

lib/main.dart

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      //Start adding here
      initialRoute: '/home',
      routes: {
        '/home': (context) {
          return const HomePage();
        },
        '/sign-in': ((context) {
          return SignInScreen(
            actions: [
              ForgotPasswordAction(((context, email) {
                Navigator.of(context)
                    .pushNamed('/forgot-password', arguments: {'email': email});
              })),
              AuthStateChangeAction(((context, state) {
                if (state is SignedIn || state is UserCreated) {
                  var user = (state is SignedIn)
                      ? state.user
                      : (state as UserCreated).credential.user;
                  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);
                  }
                  Navigator.of(context).pushReplacementNamed('/home');
                }
              })),
            ],
          );
        }),
        '/forgot-password': ((context) {
          final arguments = ModalRoute.of(context)?.settings.arguments
              as Map<String, dynamic>?;

          return ForgotPasswordScreen(
            email: arguments?['email'] as String,
            headerMaxExtent: 200,
          );
        }),
        '/profile': ((context) {
          return ProfileScreen(
            providers: [],
            actions: [
              SignedOutAction(
                ((context) {
                  Navigator.of(context).pushReplacementNamed('/home');
                }),
              ),
            ],
          );
        })
      },
      // end adding here
      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,
      ),
    );
  }
}

แต่ละหน้าจอมีการดำเนินการประเภทต่างๆ ที่เกี่ยวข้องกันตามสถานะใหม่ของขั้นตอนการตรวจสอบสิทธิ์ หลังจากเปลี่ยนสถานะการตรวจสอบสิทธิ์ส่วนใหญ่แล้ว เราสามารถกำหนดเส้นทางกลับไปที่หน้าจอที่ต้องการได้ ไม่ว่าจะเป็นหน้าจอหลักหรือหน้าจออื่น เช่น โปรไฟล์ สุดท้าย รวมสถานะแอปพลิเคชันกับ AuthFunc โดยอัปเดตวิธีการสร้างของ HomePage :

lib/main.dart

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

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

คุณสร้างอินสแตนซ์วิดเจ็ต AuthFunc และรวมไว้ในวิดเจ็ต Consumer วิดเจ็ต Consumer เป็นวิธีปกติที่แพ็คเกจ provider สามารถใช้เพื่อสร้างส่วนหนึ่งของโครงสร้างใหม่เมื่อสถานะแอปพลิเคชันเปลี่ยนแปลง วิดเจ็ต AuthFunc เป็นวิดเจ็ตเสริมที่คุณจะทำการทดสอบ

การทดสอบขั้นตอนการตรวจสอบสิทธิ์

cdf2d25e436bd48d.png

นี่คือจุดเริ่มต้นของขั้นตอนการตรวจสอบสิทธิ์ ซึ่งผู้ใช้สามารถแตะที่ปุ่ม RSVP เพื่อเริ่มต้น SignInScreen

2a2cd6d69d172369.png

เมื่อป้อนอีเมล ระบบจะยืนยันว่าผู้ใช้ลงทะเบียนแล้วหรือไม่ ซึ่งในกรณีนี้ ผู้ใช้จะได้รับแจ้งให้ป้อนรหัสผ่าน หรือหากผู้ใช้ไม่ได้ลงทะเบียน ผู้ใช้จะผ่านแบบฟอร์มการลงทะเบียน

e5e65065dba36b54.png

อย่าลืมลองป้อนรหัสผ่านแบบสั้น (น้อยกว่าหกอักขระ) เพื่อตรวจสอบขั้นตอนการจัดการข้อผิดพลาด หากผู้ใช้ลงทะเบียนแล้ว จะเห็นรหัสผ่านแทน

ในหน้านี้ ตรวจสอบให้แน่ใจว่าได้ป้อนรหัสผ่านที่ไม่ถูกต้อง เพื่อตรวจสอบการจัดการข้อผิดพลาดในหน้านี้ สุดท้าย เมื่อผู้ใช้เข้าสู่ระบบแล้ว คุณจะเห็นประสบการณ์การเข้าสู่ระบบที่ให้ผู้ใช้สามารถออกจากระบบได้อีกครั้ง

4ed811a25b0cf816.png

และด้วยเหตุนี้ คุณได้ใช้โฟลว์การตรวจสอบสิทธิ์ ยินดีด้วย!

6. เขียนข้อความไปยัง Cloud Firestore

การรู้ว่าผู้ใช้กำลังมานั้นยอดเยี่ยม แต่ให้แขกคนอื่นทำอย่างอื่นในแอพ จะเกิดอะไรขึ้นหากพวกเขาสามารถฝากข้อความไว้ในสมุดเยี่ยมได้? พวกเขาสามารถแบ่งปันได้ว่าทำไมพวกเขาถึงตื่นเต้นที่จะมาหรือใครที่พวกเขาหวังว่าจะได้พบ

ในการจัดเก็บข้อความแชทที่ผู้ใช้เขียนในแอป คุณจะต้องใช้ Cloud Firestore

แบบจำลองข้อมูล

Cloud Firestore เป็นฐานข้อมูล NoSQL และข้อมูลที่จัดเก็บไว้ในฐานข้อมูลจะถูกแบ่งออกเป็นคอลเล็กชัน เอกสาร ฟิลด์ และคอลเล็กชันย่อย คุณจะเก็บแต่ละข้อความของการแชทเป็นเอกสารในคอลเลกชั่นระดับบนสุดที่เรียกว่า guestbook

7c20dc8424bb1d84.png

เพิ่มข้อความไปยัง Firestore

ในส่วนนี้ คุณจะเพิ่มฟังก์ชันให้ผู้ใช้เขียนข้อความใหม่ลงในฐานข้อมูล ขั้นแรก คุณเพิ่มองค์ประกอบ UI (ฟิลด์แบบฟอร์มและปุ่มส่ง) จากนั้นคุณเพิ่มโค้ดที่เชื่อมโยงองค์ประกอบเหล่านี้เข้ากับฐานข้อมูล

ขั้นแรก เพิ่มการนำเข้าสำหรับแพ็คเกจ cloud_firestore และ dart: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';

ในการสร้างองค์ประกอบ UI ของฟิลด์ข้อความและปุ่มส่ง ให้เพิ่มวิดเจ็ตแบบเก็บสถานะใหม่ GuestBook ที่ด้านล่างของ lib/main.dart

lib/main.dart

class GuestBook extends StatefulWidget {
  const GuestBook({required this.addMessage, super.key});
  final FutureOr<void> Function(String message) addMessage;

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

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

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

มีจุดที่น่าสนใจสองสามจุดที่นี่ ขั้นแรก คุณกำลังสร้างอินสแตนซ์ของฟอร์ม เพื่อให้คุณสามารถตรวจสอบว่าข้อความนั้นมีเนื้อหาอยู่จริง และแสดงข้อความแสดงข้อผิดพลาดให้ผู้ใช้เห็นหากไม่มี วิธีตรวจสอบความถูกต้องของแบบฟอร์มเกี่ยวข้องกับการเข้าถึงสถานะของแบบฟอร์มที่อยู่เบื้องหลังแบบฟอร์ม และสำหรับสิ่งนี้ คุณใช้ GlobalKey สำหรับข้อมูลเพิ่มเติมเกี่ยวกับคีย์และวิธีใช้ โปรดดูที่ Flutter Widgets 101 ตอน "เมื่อใดควรใช้คีย์"

นอกจากนี้ โปรดสังเกตวิธีการจัดวางวิดเจ็ต คุณมี Row ที่มี TextFormField และ StyledButton ซึ่งมี Row อยู่ด้วย นอกจากนี้ โปรดทราบว่า TextFormField ถูกรวมไว้ในวิดเจ็ต Expanded ซึ่งจะบังคับให้ TextFormField ใช้พื้นที่เพิ่มเติมในแถว เพื่อให้เข้าใจมากขึ้นว่าทำไมจึงต้องทำเช่นนี้ โปรดอ่าน ทำความเข้าใจข้อจำกัด

เมื่อคุณมีวิดเจ็ตที่ช่วยให้ผู้ใช้สามารถป้อนข้อความเพื่อเพิ่มลงในสมุดเยี่ยมได้แล้ว คุณจะต้องแสดงมันบนหน้าจอ ในการดำเนินการดังกล่าว ให้แก้ไขเนื้อหาของ HomePage เพื่อเพิ่มสองบรรทัดต่อไปนี้ที่ด้านล่างของรายการย่อยของ ListView :

const Header("What we'll be doing"),
const Paragraph(
  'Join us for a day full of Firebase Workshops and Pizza!',
),
// Add the following two lines.
const Header('Discussion'),
GuestBook(addMessage: (message) => print(message)),

แม้ว่าจะเพียงพอที่จะแสดงวิดเจ็ต แต่ก็ไม่เพียงพอที่จะทำอะไรที่เป็นประโยชน์ คุณจะอัปเดตรหัสนี้ในไม่ช้าเพื่อให้ใช้งานได้

ตัวอย่างแอป

ผู้ใช้ที่คลิกปุ่ม ส่ง จะเรียกข้อมูลโค้ดด้านล่าง จะเพิ่มเนื้อหาของฟิลด์ป้อนข้อความไปยังคอลเลกชัน guestbook ของฐานข้อมูล โดยเฉพาะอย่างยิ่ง เมธอด addMessageToGuestBook จะเพิ่มเนื้อหาข้อความลงในเอกสารใหม่ (ด้วย ID ที่สร้างขึ้นโดยอัตโนมัติ) ไปยังคอลเลกชั่น guestbook

โปรดทราบว่า FirebaseAuth.instance.currentUser.uid เป็นข้อมูลอ้างอิงถึง ID ที่ไม่ซ้ำที่สร้างขึ้นโดยอัตโนมัติซึ่ง Firebase Authentication มอบให้สำหรับผู้ใช้ที่เข้าสู่ระบบทั้งหมด

ทำการเปลี่ยนแปลงอื่นในไฟล์ lib/main.dart เพิ่มเมธอด addMessageToGuestBook คุณจะเชื่อมต่อส่วนต่อประสานผู้ใช้และความสามารถนี้เข้าด้วยกันในขั้นตอนต่อไป

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

การเดินสาย UI เข้าสู่ฐานข้อมูล

คุณมี UI ที่ผู้ใช้สามารถป้อนข้อความที่ต้องการเพิ่มในสมุดเยี่ยมชม และคุณมีรหัสสำหรับเพิ่มรายการใน Cloud Firestore ตอนนี้สิ่งที่คุณต้องทำคือเชื่อมต่อทั้งสองเข้าด้วยกัน ใน lib/main.dart ทำการเปลี่ยนแปลงต่อไปนี้ในวิดเจ็ต 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, _) => AuthFunc(
                loggedIn: appState.loggedIn,
                signOut: () {
                  FirebaseAuth.instance.signOut();
                }),
          ),
          const Divider(
            height: 8,
            thickness: 1,
            indent: 8,
            endIndent: 8,
            color: Colors.grey,
          ),
          const Header("What we'll be doing"),
          const Paragraph(
            'Join us for a day full of Firebase Workshops and Pizza!',
          ),
          // Modify from here
          Consumer<ApplicationState>(
            builder: (context, appState, _) => Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                if (appState.loggedIn) ...[
                  const Header('Discussion'),
                  GuestBook(
                    addMessage: (message) =>
                        appState.addMessageToGuestBook(message),
                  ),
                ],
              ],
            ),
          ),
          // To here.
        ],
      ),
    );
  }
}

คุณได้แทนที่สองบรรทัดที่คุณเพิ่มกลับเข้าไปเมื่อเริ่มต้นขั้นตอนนี้ด้วยการใช้งานแบบเต็ม คุณกำลังใช้ Consumer<ApplicationState> อีกครั้งเพื่อให้สถานะแอปพลิเคชันพร้อมใช้งานในส่วนของทรีที่คุณกำลังแสดงผล ซึ่งจะทำให้คุณสามารถตอบสนองต่อผู้ที่ป้อนข้อความใน UI และเผยแพร่ลงในฐานข้อมูล ในส่วนถัดไป คุณจะทดสอบว่าข้อความที่เพิ่มนั้นเผยแพร่ในฐานข้อมูลหรือไม่

ทดสอบการส่งข้อความ

  1. ตรวจสอบให้แน่ใจว่าคุณลงชื่อเข้าใช้แอพแล้ว
  2. ป้อนข้อความเช่น "เฮ้!" แล้วคลิก ส่ง

การดำเนินการนี้จะเขียนข้อความไปยังฐานข้อมูล Cloud Firestore อย่างไรก็ตาม คุณจะยังไม่เห็นข้อความในแอป Flutter จริงของคุณ เนื่องจากคุณยังต้องใช้การเรียกข้อมูล คุณจะทำอย่างนั้นในขั้นตอนต่อไป

แต่คุณสามารถดูข้อความที่เพิ่มใหม่ได้ในคอนโซล Firebase

ในคอนโซล Firebase ใน แดชบอร์ด ฐานข้อมูล คุณควรเห็นคอลเล็กชัน guestbook พร้อมข้อความที่เพิ่มใหม่ หากคุณยังคงส่งข้อความ คอลเลกชั่นสมุดเยี่ยมชมของคุณจะมีเอกสารมากมาย เช่น:

คอนโซล Firebase

713870af0b3b63c.png

7. อ่านข้อความ

เป็นเรื่องดีที่แขกสามารถเขียนข้อความไปยังฐานข้อมูลได้ แต่ยังไม่เห็นข้อความดังกล่าวในแอป มาแก้ไขกันเถอะ!

ประสานข้อความ

ในการแสดงข้อความ คุณจะต้องเพิ่ม Listener ที่จะทริกเกอร์เมื่อข้อมูลเปลี่ยนแปลง จากนั้นจึงสร้างองค์ประกอบ UI ที่แสดงข้อความใหม่ คุณจะเพิ่มโค้ดในสถานะแอปพลิเคชันที่รับฟังข้อความที่เพิ่มใหม่จากแอป

เหนือวิดเจ็ต GuestBook คลาสค่าต่อไปนี้ คลาสนี้แสดงมุมมองที่มีโครงสร้างของข้อมูลที่คุณกำลังจัดเก็บไว้ใน Cloud Firestore

lib/main.dart

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

ในส่วนของ ApplicationState ที่คุณกำหนด state และ getters ให้เพิ่มบรรทัดใหม่ต่อไปนี้:

lib/main.dart

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

  // Add from here
  StreamSubscription<QuerySnapshot>? _guestBookSubscription;
  List<GuestBookMessage> _guestBookMessages = [];
  List<GuestBookMessage> get guestBookMessages => _guestBookMessages;
  // to here.

และสุดท้าย ในส่วนการเริ่มต้นของ ApplicationState ให้เพิ่มสิ่งต่อไปนี้เพื่อสมัครรับการสืบค้นผ่านการรวบรวมเอกสารเมื่อผู้ใช้เข้าสู่ระบบ และยกเลิกการสมัครเมื่อออกจากระบบ

lib/main.dart

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

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

ส่วนนี้มีความสำคัญ เนื่องจากที่นี่เป็นที่ที่คุณสร้างแบบสอบถามเหนือคอลเลกชัน guestbook และจัดการการสมัครและยกเลิกการสมัครคอลเลกชันนี้ คุณฟังสตรีม ซึ่งคุณสร้างแคชของข้อความในคอลเลกชั่น guestbook ขึ้นมาใหม่ และยังจัดเก็บข้อมูลอ้างอิงถึงการสมัครรับข้อมูลนี้เพื่อให้คุณสามารถยกเลิกการสมัครได้ในภายหลัง มีหลายสิ่งหลายอย่างเกิดขึ้นที่นี่ และควรค่าแก่การใช้เวลาในการตรวจแก้จุดบกพร่องเพื่อตรวจสอบสิ่งที่เกิดขึ้นเมื่อได้รับแบบจำลองทางจิตที่ชัดเจนขึ้น

สำหรับข้อมูลเพิ่มเติม โปรดดู เอกสาร Cloud Firestore

ในวิดเจ็ต GuestBook คุณต้องเชื่อมต่อสถานะการเปลี่ยนแปลงนี้กับอินเทอร์เฟซผู้ใช้ คุณแก้ไขวิดเจ็ตโดยเพิ่มรายการข้อความเป็นส่วนหนึ่งของการกำหนดค่า

lib/main.dart

class GuestBook extends StatefulWidget {
  // Modify the following line
  const GuestBook({super.key, required this.addMessage, required this.messages,});
  final FutureOr<void> Function(String message) addMessage;
  final List<GuestBookMessage> messages; // new

  @override
  _GuestBookState createState() => _GuestBookState();
}

ต่อไป เราเปิดเผยการกำหนดค่าใหม่นี้ใน _GuestBookState โดยแก้ไขวิธีการ build ดังนี้

lib/main.dart

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

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

คุณรวมเนื้อหาก่อนหน้าของวิธีการสร้างด้วยวิดเจ็ต Column จากนั้นที่ส่วนท้ายของ Column ย่อย คุณจะเพิ่ม คอลเล็กชัน เพื่อสร้าง Paragraph ใหม่สำหรับแต่ละข้อความในรายการข้อความ

สุดท้ายนี้ คุณต้องอัปเดตเนื้อหาของ HomePage เพื่อสร้าง GuestBook อย่างถูกต้องด้วยพารามิเตอร์ messages ใหม่

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

ทดสอบการซิงโครไนซ์ข้อความ

Cloud Firestore จะซิงโครไนซ์ข้อมูลกับไคลเอนต์ที่สมัครรับข้อมูลฐานข้อมูลโดยอัตโนมัติและทันที

  1. ข้อความที่คุณสร้างไว้ก่อนหน้านี้ในฐานข้อมูลควรแสดงในแอพ รู้สึกอิสระที่จะเขียนข้อความใหม่ ควรปรากฏขึ้นทันที
  2. หากคุณเปิดพื้นที่ทำงานในหลายหน้าต่างหรือหลายแท็บ ข้อความจะซิงค์ตามเวลาจริงในแท็บต่างๆ
  3. (ไม่บังคับ) คุณสามารถลองลบ แก้ไข หรือเพิ่มข้อความใหม่ด้วยตนเองได้โดยตรงในส่วน ฐานข้อมูล ของคอนโซล Firebase การเปลี่ยนแปลงใดๆ ควรปรากฏใน UI

ยินดีด้วย! คุณกำลังอ่านเอกสาร Cloud Firestore ในแอปของคุณ!

แอพพ รีวิว

8. ตั้งกฎความปลอดภัยพื้นฐาน

ในตอนแรก คุณตั้งค่า Cloud Firestore ให้ใช้โหมดทดสอบ ซึ่งหมายความว่าฐานข้อมูลของคุณเปิดสำหรับการอ่านและเขียน อย่างไรก็ตาม คุณควรใช้โหมดทดสอบในช่วงเริ่มต้นของการพัฒนาเท่านั้น ตามแนวทางปฏิบัติที่ดีที่สุด คุณควรตั้งกฎความปลอดภัยสำหรับฐานข้อมูลของคุณในขณะที่คุณพัฒนาแอป ความปลอดภัยควรเป็นส่วนสำคัญในโครงสร้างและการทำงานของแอปของคุณ

กฎความปลอดภัยช่วยให้คุณควบคุมการเข้าถึงเอกสารและคอลเลกชันในฐานข้อมูลของคุณ ไวยากรณ์กฎที่ยืดหยุ่นช่วยให้คุณสร้างกฎที่ตรงกับอะไรก็ได้ตั้งแต่การเขียนทั้งหมดไปยังฐานข้อมูลทั้งหมดไปจนถึงการดำเนินการในเอกสารเฉพาะ

คุณสามารถเขียนกฎความปลอดภัยสำหรับ Cloud Firestore ในคอนโซล Firebase:

  1. ในส่วน การพัฒนา ของคอนโซล Firebase คลิก ฐานข้อมูล จากนั้นเลือกแท็บ กฎ (หรือ คลิกที่นี่ เพื่อไปที่แท็บ กฎ โดยตรง)
  2. คุณควรเห็นกฎความปลอดภัยเริ่มต้นต่อไปนี้ พร้อมกับคำเตือนเกี่ยวกับกฎที่เป็นสาธารณะ

7767a2d2e64e7275.png

ระบุคอลเล็กชัน

ขั้นแรก ระบุคอลเล็กชันที่แอปเขียนข้อมูล

ใน match /databases/{database}/documents ให้ระบุคอลเล็กชันที่คุณต้องการรักษาความปลอดภัย:

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

เพิ่มกฎความปลอดภัย

เนื่องจากคุณใช้ UID การตรวจสอบสิทธิ์เป็นฟิลด์ในเอกสารสมุดเยี่ยมแต่ละฉบับ คุณสามารถรับ UID การตรวจสอบสิทธิ์และตรวจสอบว่าใครก็ตามที่พยายามเขียนลงในเอกสารนั้นมี UID การตรวจสอบสิทธิ์ที่ตรงกัน

เพิ่มกฎการอ่านและเขียนในชุดกฎของคุณดังที่แสดงด้านล่าง:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /guestbook/{entry} {
      allow read: if request.auth.uid != null;
      allow write:
        if request.auth.uid == request.resource.data.userId;
    }
  }
}

ตอนนี้ สำหรับสมุดเยี่ยม มีเพียงผู้ใช้ที่ลงชื่อเข้าใช้เท่านั้นที่สามารถอ่านข้อความ (ข้อความใดก็ได้!) แต่มีเพียงผู้เขียนข้อความเท่านั้นที่สามารถแก้ไขข้อความได้

เพิ่มกฎการตรวจสอบ

เพิ่มการตรวจสอบข้อมูลเพื่อให้แน่ใจว่าฟิลด์ที่คาดหวังทั้งหมดมีอยู่ในเอกสาร:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /guestbook/{entry} {
      allow read: if request.auth.uid != null;
      allow write:
      if request.auth.uid == request.resource.data.userId
          && "name" in request.resource.data
          && "text" in request.resource.data
          && "timestamp" in request.resource.data;
    }
  }
}

9. ขั้นตอนโบนัส: ฝึกฝนสิ่งที่คุณได้เรียนรู้

บันทึกสถานะ RSVP ของผู้เข้าร่วม

ตอนนี้ แอปของคุณอนุญาตให้ผู้คนเริ่มแชทได้หากพวกเขาสนใจในกิจกรรม วิธีเดียวที่คุณจะรู้ว่ามีคนมาหรือไม่คือถ้าพวกเขาโพสต์ในแชท มาจัดกันและให้คนรู้ว่ามีคนมากี่คน

คุณจะเพิ่มความสามารถใหม่สองสามอย่างให้กับสถานะแอปพลิเคชัน ประการแรกคือความสามารถสำหรับผู้ใช้ที่เข้าสู่ระบบเพื่อเสนอชื่อว่าพวกเขาเข้าร่วมหรือไม่ ความสามารถที่สองเป็นตัวนับจำนวนคนที่เข้าร่วมจริงๆ

ใน lib/main.dart ให้เพิ่มสิ่งต่อไปนี้ในส่วน accessors เพื่อเปิดใช้งานรหัส UI เพื่อโต้ตอบกับสถานะนี้:

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

อัปเดตวิธีการ init ของ ApplicationState ดังนี้:

lib/main.dart

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

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

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

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

ด้านบนเพิ่มแบบสอบถามที่สมัครรับข้อมูลเสมอเพื่อค้นหาจำนวนผู้เข้าร่วม และแบบสอบถามที่สองที่ทำงานอยู่เฉพาะในขณะที่ผู้ใช้เข้าสู่ระบบเพื่อดูว่าผู้ใช้เข้าร่วมหรือไม่ ถัดไป เพิ่มการแจงนับต่อไปนี้หลังการประกาศ GuestBookMessage :

lib/main.dart

enum Attending { yes, no, unknown }

ตอนนี้คุณกำลังจะกำหนดวิดเจ็ตใหม่ที่ทำหน้าที่เหมือนปุ่มตัวเลือกแบบเก่า มันเริ่มต้นในสถานะไม่แน่นอน โดยไม่ได้เลือกทั้งใช่และไม่ใช่ แต่เมื่อผู้ใช้เลือกว่าจะเข้าร่วมหรือไม่ จากนั้นคุณจะแสดงตัวเลือกนั้นที่ไฮไลต์ด้วยปุ่มเติม และตัวเลือกอื่นถอยกลับด้วยการแสดงผลแบบเรียบ

lib/main.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: [
              ElevatedButton(
                style: ElevatedButton.styleFrom(elevation: 0),
                onPressed: () => onSelection(Attending.yes),
                child: const Text('YES'),
              ),
              const SizedBox(width: 8),
              TextButton(
                onPressed: () => onSelection(Attending.no),
                child: const Text('NO'),
              ),
            ],
          ),
        );
      case Attending.no:
        return Padding(
          padding: const EdgeInsets.all(8.0),
          child: Row(
            children: [
              TextButton(
                onPressed: () => onSelection(Attending.yes),
                child: const Text('YES'),
              ),
              const SizedBox(width: 8),
              ElevatedButton(
                style: ElevatedButton.styleFrom(elevation: 0),
                onPressed: () => onSelection(Attending.no),
                child: const Text('NO'),
              ),
            ],
          ),
        );
      default:
        return Padding(
          padding: const EdgeInsets.all(8.0),
          child: Row(
            children: [
              StyledButton(
                onPressed: () => onSelection(Attending.yes),
                child: const Text('YES'),
              ),
              const SizedBox(width: 8),
              StyledButton(
                onPressed: () => onSelection(Attending.no),
                child: const Text('NO'),
              ),
            ],
          ),
        );
    }
  }
}

ถัดไป คุณต้องอัปเดตวิธีการสร้างของ HomePage เพื่อใช้ประโยชน์จาก YesNoSelection ทำให้ผู้ใช้ที่เข้าสู่ระบบสามารถเสนอชื่อได้หากพวกเขาเข้าร่วม คุณยังแสดงจำนวนผู้เข้าร่วมสำหรับกิจกรรมนี้

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.loggedIn) ...[
        // Add from here
        YesNoSelection(
          state: appState.attending,
          onSelection: (attending) => appState.attending = attending,
        ),
        // To here.
        const Header('Discussion'),
        GuestBook(
          addMessage: (message) =>
              appState.addMessageToGuestBook(message),
          messages: appState.guestBookMessages,
        ),
      ],
    ],
  ),
),

เพิ่มกฎ

เนื่องจากคุณได้ตั้งค่ากฎบางอย่างไว้แล้ว ข้อมูลใหม่ที่คุณเพิ่มด้วยปุ่มจะถูกปฏิเสธ คุณจะต้องอัปเดตกฎเพื่อให้สามารถเพิ่มไปยังคอลเลกชัน attendees ได้

สำหรับคอลเล็กชัน attendees เนื่องจากคุณใช้ Authentication UID เป็นชื่อเอกสาร คุณจึงสามารถคว้ามาและตรวจสอบว่า uid ของผู้ส่งตรงกับเอกสารที่พวกเขาเขียน คุณจะอนุญาตให้ทุกคนอ่านรายชื่อผู้เข้าร่วมได้ (เนื่องจากไม่มีข้อมูลส่วนตัวอยู่ที่นั่น) แต่เฉพาะผู้สร้างเท่านั้นที่สามารถอัปเดตได้

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

เพิ่มกฎการตรวจสอบ

เพิ่มการตรวจสอบข้อมูลเพื่อให้แน่ใจว่าฟิลด์ที่คาดหวังทั้งหมดมีอยู่ในเอกสาร:

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

    }
  }
}

(ไม่บังคับ) คุณสามารถดูผลลัพธ์ของการคลิกปุ่มได้แล้ว ไปที่แดชบอร์ด Cloud Firestore ในคอนโซล Firebase

ตัวอย่างแอป

10. ขอแสดงความยินดี!

คุณได้ใช้ Firebase เพื่อสร้างเว็บแอปพลิเคชันแบบเรียลไทม์เชิงโต้ตอบ!

สิ่งที่เราได้กล่าวถึง

  • การตรวจสอบสิทธิ์ Firebase
  • Cloud Firestore
  • กฎความปลอดภัยของ Firebase

ขั้นตอนถัดไป

  • ต้องการเรียนรู้เพิ่มเติมเกี่ยวกับผลิตภัณฑ์ Firebase อื่นๆ หรือไม่ บางทีคุณอาจต้องการเก็บไฟล์ภาพที่ผู้ใช้อัปโหลด? หรือส่งการแจ้งเตือนไปยังผู้ใช้ของคุณ? ตรวจสอบ เอกสาร Firebase ต้องการเรียนรู้เพิ่มเติมเกี่ยวกับปลั๊กอิน Flutter สำหรับ Firebase หรือไม่ ตรวจสอบ FlutterFire สำหรับข้อมูลเพิ่มเติม
  • ต้องการเรียนรู้เพิ่มเติมเกี่ยวกับ Cloud Firestore หรือไม่ บางทีคุณอาจต้องการเรียนรู้เกี่ยวกับคอลเลกชันย่อยและธุรกรรม? ไปที่ เว็บโค้ดแล็บของ Cloud Firestore เพื่อดูโค้ดแล็บที่เจาะลึกยิ่งขึ้นบน Cloud Firestore หรือดู ซีรีส์ YouTube นี้เพื่อทำความรู้จักกับ Cloud Firestore !

เรียนรู้เพิ่มเติม

มันไปได้อย่างไร?

เราชอบความคิดเห็นของคุณ! กรุณากรอกแบบฟอร์มสั้นๆ (มาก) ที่นี่