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
- ลงชื่อเข้าใช้ Firebase
- ในคอนโซล Firebase คลิก เพิ่มโปรเจ็ กต์ (หรือ สร้างโปรเจ็ กต์ ) และตั้งชื่อโปรเจ็ กต์ Firebase Firebase-Flutter-Codelab
- คลิกผ่านตัวเลือกการสร้างโครงการ ยอมรับข้อกำหนดของ Firebase หากได้รับแจ้ง ข้ามการตั้งค่า Google Analytics เนื่องจากคุณจะไม่ได้ใช้ Analytics สำหรับแอปนี้
หากต้องการเรียนรู้เพิ่มเติมเกี่ยวกับโปรเจ็กต์ Firebase โปรดดู ทำความเข้าใจโปรเจ็กต์ Firebase
แอปที่คุณกำลังสร้างใช้ผลิตภัณฑ์ Firebase หลายอย่างที่พร้อมใช้งานสำหรับเว็บแอป:
- การ ตรวจสอบสิทธิ์ Firebase เพื่อให้ผู้ใช้ลงชื่อเข้าใช้แอปของคุณได้
- Cloud Firestore เพื่อบันทึกข้อมูลที่มีโครงสร้างบนคลาวด์และรับการแจ้งเตือนทันทีเมื่อข้อมูลเปลี่ยนแปลง
- กฎความปลอดภัยของ Firebase เพื่อรักษาความปลอดภัยฐานข้อมูลของคุณ
ผลิตภัณฑ์เหล่านี้บางส่วนต้องมีการกำหนดค่าพิเศษหรือต้องเปิดใช้งานโดยใช้คอนโซล Firebase
เปิดใช้งานการลงชื่อเข้าใช้อีเมล สำหรับการตรวจสอบสิทธิ์ Firebase
ในการอนุญาตให้ผู้ใช้ลงชื่อเข้าใช้เว็บแอป คุณจะต้องใช้วิธีลงชื่อเข้าใช้ อีเมล/รหัสผ่าน สำหรับ Codelab นี้:
- ในคอนโซล Firebase ให้ขยายเมนู บิล ด์ในแผงด้านซ้าย
- คลิก Authentication จากนั้นคลิกปุ่ม Get Started จากนั้นไปที่แท็บ Sign-in method (หรือ คลิกที่นี่ เพื่อไปยังแท็บ Sign-in method โดยตรง)
- คลิก อีเมล/รหัสผ่าน ในรายการ ผู้ให้บริการลงชื่อเข้าใช้ ตั้งค่าสวิตช์ Enable ไปที่ตำแหน่งเปิด จากนั้นคลิก บันทึก
เปิดใช้งาน Cloud Firestore
เว็บแอปใช้ Cloud Firestore เพื่อบันทึกข้อความแชทและรับข้อความแชทใหม่
เปิดใช้งาน Cloud Firestore:
- ในส่วน บิล ด์ของคอนโซล Firebase ให้คลิก Cloud Firestore
- คลิก สร้างฐานข้อมูล
- เลือกตัวเลือก เริ่มในโหมดทดสอบ อ่านข้อจำกัดความรับผิดชอบเกี่ยวกับกฎความปลอดภัย โหมดทดสอบช่วยให้แน่ใจว่าคุณสามารถเขียนฐานข้อมูลได้อย่างอิสระในระหว่างการพัฒนา คลิก ถัดไป
- เลือกตำแหน่งสำหรับฐานข้อมูลของคุณ (คุณสามารถใช้ค่าเริ่มต้นได้) โปรดทราบว่าสถานที่นี้ไม่สามารถเปลี่ยนแปลงได้ในภายหลัง
- คลิก เปิดใช้งาน
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 Resolving dependencies... + firebase_core 1.10.5 + firebase_core_platform_interface 4.2.2 + firebase_core_web 1.5.2 + flutter_web_plugins 0.0.0 from sdk flutter + js 0.6.3 test_api 0.4.3 (0.4.8 available) Changed 5 dependencies!
firebase_core
เป็นรหัสทั่วไปที่จำเป็นสำหรับปลั๊กอิน Firebase Flutter ทั้งหมด
$ flutter pub add firebase_auth Resolving dependencies... + firebase_auth 3.3.3 + firebase_auth_platform_interface 6.1.8 + firebase_auth_web 3.3.4 + intl 0.17.0 test_api 0.4.3 (0.4.8 available) Changed 4 dependencies!
firebase_auth
เปิดใช้งานการผสานรวมกับความสามารถในการตรวจสอบสิทธิ์ของ Firebase
$ flutter pub add cloud_firestore Resolving dependencies... + cloud_firestore 3.1.4 + cloud_firestore_platform_interface 5.4.9 + cloud_firestore_web 2.6.4 test_api 0.4.3 (0.4.8 available) Changed 3 dependencies!
cloud_firestore
ช่วยให้เข้าถึงการจัดเก็บข้อมูล Cloud Firestore
$ flutter pub add provider Resolving dependencies... + nested 1.0.0 + provider 6.0.1 test_api 0.4.3 (0.4.8 available) Changed 2 dependencies!
ในขณะที่คุณได้เพิ่มแพ็คเกจที่จำเป็นแล้ว คุณยังต้องกำหนดค่าโปรเจ็กต์ iOS, Android, macOS และ Web runner เพื่อใช้ Firebase อย่างเหมาะสม คุณกำลังใช้แพ็คเกจ provider
ที่จะเปิดใช้งานการแยกตรรกะทางธุรกิจออกจากตรรกะการแสดงผล
ติดตั้ง flutterfire
FlutterFire CLI ขึ้นอยู่กับ Firebase CLI พื้นฐาน หากคุณยังไม่ได้ดำเนินการ ตรวจสอบให้แน่ใจว่าได้ติดตั้ง Firebase CLI บนเครื่องของคุณแล้ว
ถัดไป ติดตั้ง FlutterFire CLI โดยรันคำสั่งต่อไปนี้:
$ dart pub global activate flutterfire_cli
เมื่อติดตั้งแล้ว คำสั่ง flutterfire
จะพร้อมใช้งานทั่วโลก
การกำหนดค่าแอพของคุณ
CLI จะดึงข้อมูลจากโปรเจ็กต์ Firebase และแอปพลิเคชันโปรเจ็กต์ที่เลือกเพื่อสร้างการกำหนดค่าทั้งหมดสำหรับแพลตฟอร์มเฉพาะ
ในรูทของแอปพลิเคชันของคุณ ให้รันคำสั่ง configuration:
$ flutterfire configure
คำสั่งการกำหนดค่าจะแนะนำคุณตลอดกระบวนการต่างๆ:
- การเลือกโปรเจ็กต์ Firebase (ตามไฟล์ .firebaserc หรือจากคอนโซล Firebase)
- แจ้งว่าคุณต้องการกำหนดค่าสำหรับแพลตฟอร์มใด (เช่น Android, iOS, macOS และเว็บ)
- ระบุว่าควรใช้แอปพลิเคชัน Firebase สำหรับแพลตฟอร์มที่เลือกเพื่อแยกการกำหนดค่า โดยค่าเริ่มต้น CLI จะพยายามจับคู่แอป Firebase โดยอัตโนมัติตามการกำหนดค่าโปรเจ็กต์ปัจจุบันของคุณ
- สร้างไฟล์ firebase_options.dart ในโครงการของคุณ
กำหนดค่า macOS
Flutter บน macOS สร้างแอปพลิเคชันแซนด์บ็อกซ์อย่างสมบูรณ์ เนื่องจากแอปพลิเคชันนี้กำลังผสานรวมโดยใช้เครือข่ายเพื่อสื่อสารกับเซิร์ฟเวอร์ Firebase คุณจะต้องกำหนดค่าแอปพลิเคชันของคุณด้วยสิทธิ์ไคลเอ็นต์เครือข่าย
macos/Runner/DebugProfile.entitlements
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<!-- Add the following two lines -->
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>
macos/Runner/Release.entitlements
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<!-- Add the following two lines -->
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>
ดู การให้สิทธิ์และแซนด์บ็อกซ์ของแอป สำหรับรายละเอียดเพิ่มเติม
5. เพิ่มการลงชื่อเข้าใช้ของผู้ใช้ (RSVP)
เมื่อคุณได้เพิ่ม Firebase ลงในแอปแล้ว คุณสามารถตั้งค่าปุ่ม RSVP ที่ลงทะเบียนผู้คนโดยใช้ Firebase Authentication สำหรับเนทีฟ Android, เนทีฟ iOS และเว็บ มีแพ็คเกจ FirebaseUI Auth ที่สร้างไว้ล่วงหน้า แต่สำหรับ Flutter คุณจะต้องสร้างความสามารถนี้
โปรเจ็กต์ที่คุณเรียกค้นในขั้นตอนที่ 2 ได้รวมชุดวิดเจ็ตที่ใช้อินเทอร์เฟซผู้ใช้สำหรับโฟลว์การพิสูจน์ตัวตนส่วนใหญ่ คุณจะใช้ตรรกะทางธุรกิจเพื่อรวมการตรวจสอบสิทธิ์ Firebase เข้ากับแอปพลิเคชัน
ตรรกะทางธุรกิจกับผู้ให้บริการ
คุณจะใช้แพ็คเกจ provider
เพื่อสร้างออบเจ็กต์สถานะแอปพลิเคชันแบบรวมศูนย์พร้อมใช้งานทั่วทั้งแผนผังวิดเจ็ต Flutter ของแอปพลิเคชัน ในการเริ่มต้น แก้ไขการนำเข้าที่ด้านบนของ 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';
บรรทัด import
แนะนำ Firebase Core และ Auth ดึงแพ็คเกจ provider
ที่คุณใช้เพื่อทำให้อ็อบเจ็กต์สถานะแอปพลิเคชันพร้อมใช้งานผ่านแผนผังวิดเจ็ต และรวมวิดเจ็ตการพิสูจน์ตัวตนจาก lib/src
ออบเจ็กต์สถานะของแอปพลิเคชันนี้ ApplicationState
มีความรับผิดชอบหลักสองประการสำหรับขั้นตอนนี้ แต่จะได้รับความรับผิดชอบเพิ่มเติมเมื่อคุณเพิ่มความสามารถเพิ่มเติมให้กับแอปพลิเคชันในขั้นตอนต่อไป ความรับผิดชอบแรกคือการเริ่มต้นไลบรารี Firebase ด้วยการเรียก Firebase.initializeApp()
จากนั้นจะมีการจัดการขั้นตอนการอนุญาต เพิ่มคลาสต่อไปนี้ต่อท้าย 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();
}
}
เป็นที่น่าสังเกตประเด็นสำคัญสองสามข้อในชั้นเรียนนี้ ผู้ใช้เริ่มต้นโดยไม่ได้รับการตรวจสอบสิทธิ์ แอปจะแสดงแบบฟอร์มขอที่อยู่อีเมลของผู้ใช้ ขึ้นอยู่กับว่าที่อยู่อีเมลนั้นอยู่ในไฟล์หรือไม่ แอปจะถามผู้ใช้ที่ลงทะเบียนหรือขอรหัสผ่าน จากนั้นถือว่าทุกอย่างเรียบร้อยดี ผู้ใช้ ได้รับการรับรองความถูกต้อง
ต้องสังเกตว่านี่ไม่ใช่การนำขั้นตอนการตรวจสอบสิทธิ์ FirebaseUI ไปใช้งานโดยสมบูรณ์ เนื่องจากไม่ได้จัดการกรณีของผู้ใช้ที่มีบัญชีที่มีอยู่ซึ่งมีปัญหาในการเข้าสู่ระบบ การใช้ความสามารถเพิ่มเติมนี้จะเหลือเป็นแบบฝึกหัดสำหรับ ผู้อ่านที่มีแรงบันดาลใจ
การรวมขั้นตอนการตรวจสอบสิทธิ์
เมื่อคุณเริ่มต้นสถานะแอปพลิเคชันแล้ว ก็ถึงเวลาที่จะเชื่อมโยงสถานะแอปพลิเคชันเข้ากับการเริ่มต้นแอป และเพิ่มโฟลว์การตรวจสอบสิทธิ์ใน HomePage
อัปเดตจุดเริ่มต้นหลักเพื่อรวมสถานะแอปพลิเคชันผ่านแพ็คเกจ provider
:
lib/main.dart
void main() {
// Modify from here
runApp(
ChangeNotifierProvider(
create: (context) => ApplicationState(),
builder: (context, _) => App(),
),
);
// to here.
}
การปรับเปลี่ยนฟังก์ชัน main
ทำให้แพ็คเกจผู้ให้บริการรับผิดชอบในการสร้างอินสแตนซ์อ็อบเจ็กต์สถานะแอปพลิเคชันโดยใช้วิดเจ็ต ChangeNotifierProvider
คุณกำลังใช้คลาสตัวให้บริการเฉพาะนี้เนื่องจากอ็อบเจ็กต์สถานะแอปพลิเคชันขยาย ChangeNotifier
และทำให้แพ็คเกจ provider
ทราบเมื่อจะแสดงวิดเจ็ตที่ขึ้นต่อกันอีกครั้ง สุดท้าย รวมสถานะแอปพลิเคชันกับการ Authentication
โดยอัปเดตวิธีการ build
ของ 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!',
),
],
),
);
}
}
คุณสร้างอินสแตนซ์วิดเจ็ตการ Authentication
และรวมไว้ในวิดเจ็ต Consumer
วิดเจ็ต Consumer เป็นวิธีปกติที่แพ็คเกจ provider
สามารถใช้เพื่อสร้างส่วนหนึ่งของโครงสร้างใหม่เมื่อสถานะของแอปพลิเคชันเปลี่ยนแปลง วิดเจ็ตการ Authentication
คือ UI การตรวจสอบสิทธิ์ที่คุณจะทำการทดสอบ
การทดสอบขั้นตอนการตรวจสอบสิทธิ์
นี่คือจุดเริ่มต้นของขั้นตอนการตรวจสอบสิทธิ์ ซึ่งผู้ใช้สามารถแตะที่ปุ่ม RSVP เพื่อเริ่มแบบฟอร์มอีเมล
เมื่อป้อนอีเมล ระบบจะยืนยันว่าผู้ใช้ลงทะเบียนแล้วหรือไม่ ซึ่งในกรณีนี้ ผู้ใช้จะได้รับแจ้งให้ป้อนรหัสผ่าน หรือหากผู้ใช้ไม่ได้ลงทะเบียน ผู้ใช้จะผ่านแบบฟอร์มการลงทะเบียน
อย่าลืมลองป้อนรหัสผ่านแบบสั้น (น้อยกว่าหกอักขระ) เพื่อตรวจสอบขั้นตอนการจัดการข้อผิดพลาด หากผู้ใช้ลงทะเบียนแล้ว จะเห็นรหัสผ่านแทน
ในหน้านี้ ตรวจสอบให้แน่ใจว่าได้ป้อนรหัสผ่านที่ไม่ถูกต้อง เพื่อตรวจสอบการจัดการข้อผิดพลาดในหน้านี้ สุดท้าย เมื่อผู้ใช้เข้าสู่ระบบแล้ว คุณจะเห็นประสบการณ์การเข้าสู่ระบบที่ให้ผู้ใช้สามารถออกจากระบบได้อีกครั้ง
และด้วยเหตุนี้ คุณได้ใช้โฟลว์การตรวจสอบสิทธิ์ ยินดีด้วย!
6. เขียนข้อความไปยัง Cloud Firestore
การรู้ว่าผู้ใช้กำลังมานั้นยอดเยี่ยม แต่ให้แขกคนอื่นทำอย่างอื่นในแอป จะเกิดอะไรขึ้นหากพวกเขาสามารถฝากข้อความไว้ในสมุดเยี่ยมได้? พวกเขาสามารถแบ่งปันได้ว่าทำไมพวกเขาถึงตื่นเต้นที่จะมาหรือใครที่พวกเขาหวังว่าจะได้พบ
ในการจัดเก็บข้อความแชทที่ผู้ใช้เขียนในแอป คุณจะต้องใช้ Cloud Firestore
แบบจำลองข้อมูล
Cloud Firestore เป็นฐานข้อมูล NoSQL และข้อมูลที่จัดเก็บไว้ในฐานข้อมูลจะถูกแบ่งออกเป็นคอลเล็กชัน เอกสาร ฟิลด์ และคอลเล็กชันย่อย คุณจะเก็บแต่ละข้อความของการแชทเป็นเอกสารในคอลเลกชั่นระดับบนสุดที่เรียกว่า guestbook
เพิ่มข้อความไปที่ 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});
final FutureOr<void> Function(String message) addMessage;
@override
_GuestBookState createState() => _GuestBookState();
}
class _GuestBookState extends State<GuestBook> {
final _formKey = GlobalKey<FormState>(debugLabel: '_GuestBookState');
final _controller = TextEditingController();
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Form(
key: _formKey,
child: Row(
children: [
Expanded(
child: TextFormField(
controller: _controller,
decoration: const InputDecoration(
hintText: 'Leave a message',
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Enter your message to continue';
}
return null;
},
),
),
const SizedBox(width: 8),
StyledButton(
onPressed: () async {
if (_formKey.currentState!.validate()) {
await widget.addMessage(_controller.text);
_controller.clear();
}
},
child: Row(
children: const [
Icon(Icons.send),
SizedBox(width: 4),
Text('SEND'),
],
),
),
],
),
),
);
}
}
มีจุดที่น่าสนใจสองสามจุดที่นี่ ขั้นแรก คุณกำลังสร้างอินสแตนซ์ของฟอร์ม เพื่อให้คุณสามารถตรวจสอบว่าข้อความมีเนื้อหาจริงหรือไม่ และแสดงข้อความแสดงข้อผิดพลาดให้ผู้ใช้เห็นหากไม่มี วิธีตรวจสอบความถูกต้องของแบบฟอร์มเกี่ยวข้องกับการเข้าถึงสถานะของแบบฟอร์มที่อยู่เบื้องหลังแบบฟอร์ม และสำหรับสิ่งนี้ คุณใช้ GlobalKey
สำหรับข้อมูลเพิ่มเติมเกี่ยวกับคีย์และวิธีใช้ โปรดดูที่ Flutter Widgets 101 ตอน "เมื่อใดควรใช้คีย์"
นอกจากนี้ โปรดสังเกตวิธีการจัดวางวิดเจ็ต คุณมี Row
ที่มี TextFormField
และ StyledButton
ซึ่งมี Row
อยู่ด้วย นอกจากนี้ โปรดทราบว่า TextFormField
ถูกรวมไว้ในวิดเจ็ต Expanded
ซึ่งจะบังคับให้ TextFormField
ใช้พื้นที่เพิ่มเติมในแถว เพื่อให้เข้าใจมากขึ้นว่าทำไมจึงต้องทำเช่นนี้ โปรดอ่าน ทำความเข้าใจข้อจำกัด
เมื่อคุณมีวิดเจ็ตที่ช่วยให้ผู้ใช้สามารถป้อนข้อความเพื่อเพิ่มลงในสมุดเยี่ยมได้แล้ว คุณจะต้องแสดงมันบนหน้าจอ ในการดำเนินการดังกล่าว ให้แก้ไขเนื้อหาของ HomePage
เพื่อเพิ่มสองบรรทัดต่อไปนี้ที่ด้านล่างของรายการย่อยของ ListView
:
const Header("What we'll be doing"),
const Paragraph(
'Join us for a day full of Firebase Workshops and Pizza!',
),
// Add the following two lines.
const Header('Discussion'),
GuestBook(addMessage: (message) => print(message)),
แม้ว่าจะเพียงพอที่จะแสดงวิดเจ็ต แต่ก็ไม่เพียงพอที่จะทำอะไรที่เป็นประโยชน์ คุณจะอัปเดตรหัสนี้ในไม่ช้าเพื่อให้ใช้งานได้
ตัวอย่างแอป
ผู้ใช้ที่คลิกปุ่ม ส่ง จะเรียกข้อมูลโค้ดด้านล่าง จะเพิ่มเนื้อหาของฟิลด์ป้อนข้อความไปยังคอลเลกชัน guestbook
ของฐานข้อมูล โดยเฉพาะอย่างยิ่ง เมธอด addMessageToGuestBook
จะเพิ่มเนื้อหาข้อความลงในเอกสารใหม่ (ด้วย 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 (_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
}
การเดินสาย 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, _) => Authentication(
email: appState.email,
loginState: appState.loginState,
startLoginFlow: appState.startLoginFlow,
verifyEmail: appState.verifyEmail,
signInWithEmailAndPassword: appState.signInWithEmailAndPassword,
cancelRegistration: appState.cancelRegistration,
registerAccount: appState.registerAccount,
signOut: appState.signOut,
),
),
const Divider(
height: 8,
thickness: 1,
indent: 8,
endIndent: 8,
color: Colors.grey,
),
const Header("What we'll be doing"),
const Paragraph(
'Join us for a day full of Firebase Workshops and Pizza!',
),
// Modify from here
Consumer<ApplicationState>(
builder: (context, appState, _) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (appState.loginState == ApplicationLoginState.loggedIn) ...[
const Header('Discussion'),
GuestBook(
addMessage: (message) =>
appState.addMessageToGuestBook(message),
),
],
],
),
),
// To here.
],
),
);
}
}
คุณได้แทนที่สองบรรทัดที่คุณเพิ่มกลับเมื่อเริ่มต้นขั้นตอนนี้ด้วยการใช้งานแบบเต็ม คุณกำลังใช้ Consumer<ApplicationState>
อีกครั้งเพื่อให้สถานะแอปพลิเคชันพร้อมใช้งานในส่วนของทรีที่คุณกำลังแสดงผล ซึ่งจะทำให้คุณสามารถตอบสนองต่อผู้ที่ป้อนข้อความใน UI และเผยแพร่ลงในฐานข้อมูล ในส่วนถัดไป คุณจะทดสอบว่าข้อความที่เพิ่มนั้นเผยแพร่ในฐานข้อมูลหรือไม่
ทดสอบการส่งข้อความ
- ตรวจสอบให้แน่ใจว่าคุณลงชื่อเข้าใช้แอพแล้ว
- ป้อนข้อความเช่น "เฮ้!" แล้วคลิก ส่ง
การดำเนินการนี้จะเขียนข้อความไปยังฐานข้อมูล Cloud Firestore อย่างไรก็ตาม คุณจะยังไม่เห็นข้อความในแอป Flutter จริงของคุณ เนื่องจากคุณยังต้องใช้การเรียกข้อมูล คุณจะทำอย่างนั้นในขั้นตอนต่อไป
แต่คุณสามารถดูข้อความที่เพิ่มใหม่ได้ในคอนโซล Firebase
ในคอนโซล Firebase ใน แดชบอร์ด ฐานข้อมูล คุณควรเห็นคอลเล็กชัน guestbook
พร้อมข้อความที่เพิ่มใหม่ หากคุณยังคงส่งข้อความ คอลเลกชั่นสมุดเยี่ยมชมของคุณจะมีเอกสารจำนวนมากดังนี้:
คอนโซล Firebase
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
ApplicationLoginState _loginState = ApplicationLoginState.loggedOut;
ApplicationLoginState get loginState => _loginState;
String? _email;
String? get email => _email;
// Add from here
StreamSubscription<QuerySnapshot>? _guestBookSubscription;
List<GuestBookMessage> _guestBookMessages = [];
List<GuestBookMessage> get guestBookMessages => _guestBookMessages;
// to here.
และสุดท้าย ในส่วนการเริ่มต้นของ ApplicationState
ให้เพิ่มสิ่งต่อไปนี้เพื่อสมัครรับการสืบค้นผ่านการรวบรวมเอกสารเมื่อผู้ใช้เข้าสู่ระบบ และยกเลิกการสมัครเมื่อออกจากระบบ
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();
});
}
ส่วนนี้มีความสำคัญ เนื่องจากที่นี่เป็นที่ที่คุณสร้างแบบสอบถามเหนือคอลเลกชัน guestbook
และจัดการการสมัครและยกเลิกการสมัครคอลเลกชันนี้ คุณฟังสตรีม ซึ่งคุณสร้างแคชของข้อความในคอลเลกชั่น guestbook
ขึ้นมาใหม่ และยังเก็บข้อมูลอ้างอิงถึงการสมัครรับข้อมูลนี้เพื่อให้คุณสามารถยกเลิกการสมัครได้ในภายหลัง มีหลายสิ่งหลายอย่างเกิดขึ้นที่นี่ และควรค่าแก่การใช้เวลาในการตรวจแก้จุดบกพร่องเพื่อตรวจสอบสิ่งที่เกิดขึ้นเมื่อได้รับแบบจำลองทางจิตที่ชัดเจนขึ้น
สำหรับข้อมูลเพิ่มเติม โปรดดู เอกสารประกอบของ Cloud Firestore
ในวิดเจ็ต GuestBook
คุณต้องเชื่อมต่อสถานะการเปลี่ยนแปลงนี้กับอินเทอร์เฟซผู้ใช้ คุณแก้ไขวิดเจ็ตโดยเพิ่มรายการข้อความเป็นส่วนหนึ่งของการกำหนดค่า
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();
}
ต่อไป เราเปิดเผยการกำหนดค่าใหม่นี้ใน _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 จะซิงโครไนซ์ข้อมูลกับไคลเอนต์ที่สมัครรับข้อมูลฐานข้อมูลโดยอัตโนมัติและทันที
- ข้อความที่คุณสร้างไว้ก่อนหน้านี้ในฐานข้อมูลควรแสดงในแอพ รู้สึกอิสระที่จะเขียนข้อความใหม่ ควรปรากฏขึ้นทันที
- หากคุณเปิดพื้นที่ทำงานในหลายหน้าต่างหรือหลายแท็บ ข้อความจะซิงค์ตามเวลาจริงในแท็บต่างๆ
- (ไม่บังคับ) คุณสามารถลองลบ แก้ไข หรือเพิ่มข้อความใหม่ด้วยตนเองได้โดยตรงในส่วน ฐานข้อมูล ของคอนโซล Firebase การเปลี่ยนแปลงใดๆ ควรปรากฏใน UI
ยินดีด้วย! คุณกำลังอ่านเอกสาร Cloud Firestore ในแอปของคุณ!
แอพพ รีวิว
8. ตั้งกฎความปลอดภัยพื้นฐาน
ในตอนแรก คุณตั้งค่า Cloud Firestore ให้ใช้โหมดทดสอบ ซึ่งหมายความว่าฐานข้อมูลของคุณเปิดสำหรับการอ่านและเขียน อย่างไรก็ตาม คุณควรใช้โหมดทดสอบในช่วงเริ่มต้นของการพัฒนาเท่านั้น ตามแนวทางปฏิบัติที่ดีที่สุด คุณควรตั้งกฎความปลอดภัยสำหรับฐานข้อมูลของคุณในขณะที่คุณพัฒนาแอป ความปลอดภัยควรเป็นส่วนสำคัญในโครงสร้างและการทำงานของแอปของคุณ
กฎความปลอดภัยช่วยให้คุณควบคุมการเข้าถึงเอกสารและคอลเลกชันในฐานข้อมูลของคุณ ไวยากรณ์กฎที่ยืดหยุ่นช่วยให้คุณสร้างกฎที่ตรงกับอะไรก็ได้ตั้งแต่การเขียนทั้งหมดไปยังฐานข้อมูลทั้งหมด ไปจนถึงการดำเนินการในเอกสารเฉพาะ
คุณสามารถเขียนกฎความปลอดภัยสำหรับ Cloud Firestore ในคอนโซล Firebase:
- ในส่วน การพัฒนา ของคอนโซล Firebase คลิก ฐานข้อมูล จากนั้นเลือกแท็บ กฎ (หรือ คลิกที่นี่ เพื่อไปที่แท็บ กฎ โดยตรง)
- คุณควรเห็นกฎความปลอดภัยเริ่มต้นต่อไปนี้ พร้อมกับคำเตือนเกี่ยวกับกฎที่เป็นสาธารณะ
ระบุคอลเล็กชัน
ขั้นแรก ระบุคอลเล็กชันที่แอปเขียนข้อมูล
ใน match /databases/{database}/documents
ให้ระบุคอลเล็กชันที่คุณต้องการรักษาความปลอดภัย:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /guestbook/{entry} {
// You'll add rules here in the next step.
}
}
เพิ่มกฎความปลอดภัย
เนื่องจากคุณใช้ UID การตรวจสอบสิทธิ์เป็นฟิลด์ในเอกสารสมุดเยี่ยมแต่ละฉบับ คุณจึงสามารถรับ UID การตรวจสอบสิทธิ์และตรวจสอบว่าใครก็ตามที่พยายามเขียนลงในเอกสารนั้นมี UID การตรวจสอบสิทธิ์ที่ตรงกัน
เพิ่มกฎการอ่านและเขียนในชุดกฎของคุณดังที่แสดงด้านล่าง:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /guestbook/{entry} {
allow read: if request.auth.uid != null;
allow write:
if request.auth.uid == request.resource.data.userId;
}
}
}
ตอนนี้ สำหรับสมุดเยี่ยม มีเพียงผู้ใช้ที่ลงชื่อเข้าใช้เท่านั้นที่สามารถอ่านข้อความ (ข้อความใดก็ได้!) แต่มีเพียงผู้เขียนข้อความเท่านั้นที่สามารถแก้ไขข้อความได้
เพิ่มกฎการตรวจสอบ
เพิ่มการตรวจสอบข้อมูลเพื่อให้แน่ใจว่าฟิลด์ที่คาดหวังทั้งหมดมีอยู่ในเอกสาร:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /guestbook/{entry} {
allow read: if request.auth.uid != null;
allow write:
if request.auth.uid == request.resource.data.userId
&& "name" in request.resource.data
&& "text" in request.resource.data
&& "timestamp" in request.resource.data;
}
}
}
9. ขั้นตอนโบนัส: ฝึกฝนสิ่งที่คุณได้เรียนรู้
บันทึกสถานะ RSVP ของผู้เข้าร่วม
ตอนนี้ แอปของคุณอนุญาตให้ผู้คนเริ่มแชทได้หากพวกเขาสนใจในกิจกรรม วิธีเดียวที่คุณจะรู้ว่ามีคนมาหรือไม่คือถ้าพวกเขาโพสต์ในแชท มาจัดกันและให้คนรู้ว่ามีคนมากี่คน
คุณจะเพิ่มความสามารถใหม่สองสามอย่างให้กับสถานะแอปพลิเคชัน ประการแรกคือความสามารถสำหรับผู้ใช้ที่เข้าสู่ระบบเพื่อเสนอชื่อว่าพวกเขาเข้าร่วมหรือไม่ ความสามารถที่สองเป็นตัวนับจำนวนคนที่เข้าร่วมจริงๆ
ใน lib/main.dart
ให้เพิ่มสิ่งต่อไปนี้ในส่วน 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,
);
// 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({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.loginState == ApplicationLoginState.loggedIn) ...[
// Add from here
YesNoSelection(
state: appState.attending,
onSelection: (attending) => appState.attending = attending,
),
// To here.
const Header('Discussion'),
GuestBook(
addMessage: (message) =>
appState.addMessageToGuestBook(message),
messages: appState.guestBookMessages,
),
],
],
),
),
เพิ่มกฎ
เนื่องจากคุณได้ตั้งค่ากฎบางอย่างไว้แล้ว ข้อมูลใหม่ที่คุณเพิ่มด้วยปุ่มจะถูกปฏิเสธ คุณจะต้องอัปเดตกฎเพื่อให้สามารถเพิ่มไปยังคอลเลกชัน attendees
ได้
สำหรับคอลเลกชั่น attendees
เนื่องจากคุณใช้ 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 !
เรียนรู้เพิ่มเติม
- ไซต์ Firebase: firebase.google.com
- เว็บไซต์ Flutter: flutter.dev
- วิดเจ็ต FlutterFire Firebase Flutter: firebase.flutter.dev
- ช่อง YouTube Firebase
- ช่อง YouTube Flutter
มันไปได้อย่างไร?
เราชอบความคิดเห็นของคุณ! กรุณากรอกแบบฟอร์มสั้นๆ (มาก) ที่นี่