เกี่ยวกับ Codelab นี้
1 ก่อนเริ่มต้น
ใน Codelab นี้ คุณจะได้เรียนรู้พื้นฐานบางอย่างของ Firebase เพื่อสร้างแอป Flutter บนอุปกรณ์เคลื่อนที่สำหรับ Android และ iOS
ข้อกำหนดเบื้องต้น
- ความคุ้นเคยกับ Flutter
- Flutter SDK
- โปรแกรมแก้ไขข้อความที่คุณเลือก
สิ่งที่คุณจะได้เรียนรู้
- วิธีสร้างแอปแชทสำหรับตอบกลับกิจกรรมและสมุดเยี่ยมบน Android, iOS, เว็บ และ macOS ด้วย Flutter
- วิธีตรวจสอบสิทธิ์ผู้ใช้ด้วยการตรวจสอบสิทธิ์ Firebase และซิงค์ข้อมูลกับ Firestore
|
|
|
|
สิ่งที่ต้องมี
อุปกรณ์ใดก็ได้ต่อไปนี้
- อุปกรณ์ Android หรือ iOS จริงที่เชื่อมต่อกับคอมพิวเตอร์และตั้งค่าเป็นโหมดนักพัฒนาแอป
- โปรแกรมจำลอง iOS (ต้องใช้เครื่องมือ Xcode)
- โปรแกรมจำลอง Android (ต้องตั้งค่าใน Android Studio)
นอกจากนี้ คุณจะต้องมีสิ่งต่อไปนี้ด้วย
- เบราว์เซอร์ที่คุณเลือก เช่น Google Chrome
- IDE หรือโปรแกรมแก้ไขข้อความที่คุณเลือกซึ่งกำหนดค่าด้วยปลั๊กอิน Dart และ Flutter เช่น Android Studio หรือ Visual Studio Code
- Flutter เวอร์ชัน
stableล่าสุดหรือbetaหากคุณชอบใช้เวอร์ชันที่ใหม่ที่สุด - บัญชี Google สำหรับการสร้างและการจัดการโปรเจ็กต์ Firebase
FirebaseCLI เข้าสู่ระบบบัญชี Google ของคุณแล้ว
2 รับโค้ดตัวอย่าง
ดาวน์โหลดโปรเจ็กต์เวอร์ชันเริ่มต้นจาก GitHub โดยทำดังนี้
- จากบรรทัดคำสั่ง ให้โคลนที่เก็บ GitHub ในไดเรกทอรี
flutter-codelabs
git clone https://github.com/flutter/codelabs.git flutter-codelabs
ไดเรกทอรี flutter-codelabs มีโค้ดสำหรับชุดของโค้ดแล็บ โค้ดสำหรับโค้ดแล็บนี้อยู่ในไดเรกทอรี flutter-codelabs/firebase-get-to-know-flutter ไดเรกทอรีประกอบด้วยชุดสแนปชอตที่แสดงให้เห็นว่าโปรเจ็กต์ควรมีลักษณะอย่างไรเมื่อสิ้นสุดแต่ละขั้นตอน เช่น คุณอยู่ในขั้นตอนที่ 2
- ค้นหาไฟล์ที่ตรงกันสำหรับขั้นตอนที่ 2
cd flutter-codelabs/firebase-get-to-know-flutter/step_02
หากต้องการข้ามไปข้างหน้าหรือดูว่าสิ่งต่างๆ ควรมีลักษณะอย่างไรหลังจากทำตามขั้นตอนหนึ่งๆ ให้ไปที่ไดเรกทอรีที่มีชื่อตามขั้นตอนที่คุณสนใจ
นำเข้าแอปเริ่มต้น
- เปิดหรือนำเข้าไดเรกทอรี
flutter-codelabs/firebase-get-to-know-flutter/step_02ใน IDE ที่ต้องการ ไดเรกทอรีนี้มีโค้ดเริ่มต้นสำหรับโค้ดแล็บ ซึ่งประกอบด้วยแอป Flutter Meetup ที่ยังไม่พร้อมใช้งาน
ค้นหาไฟล์ที่ต้องดำเนินการ
โค้ดในแอปนี้กระจายอยู่หลายไดเรกทอรี การแยกฟังก์ชันการทำงานนี้ช่วยให้การทำงานง่ายขึ้นเนื่องจากจะจัดกลุ่มโค้ดตามฟังก์ชันการทำงาน
- ค้นหาไฟล์ต่อไปนี้
lib/main.dart: ไฟล์นี้มีจุดแรกเข้าหลักและวิดเจ็ตแอปlib/home_page.dart: ไฟล์นี้มีวิดเจ็ตหน้าแรกlib/src/widgets.dart: ไฟล์นี้มีวิดเจ็ตจำนวนหนึ่งที่จะช่วยกำหนดรูปแบบของแอปให้เป็นมาตรฐาน วิดเจ็ตเหล่านี้จะประกอบกันเป็นหน้าจอของแอปเริ่มต้นlib/src/authentication.dart: ไฟล์นี้มีการติดตั้งใช้งานบางส่วนของการตรวจสอบสิทธิ์พร้อมชุดวิดเจ็ตเพื่อสร้างประสบการณ์การใช้งานการเข้าสู่ระบบสำหรับการตรวจสอบสิทธิ์ 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 โดยใช้บัญชี Google
- คลิกปุ่มเพื่อสร้างโปรเจ็กต์ใหม่ แล้วป้อนชื่อโปรเจ็กต์ (เช่น
Firebase-Flutter-Codelab)
- คลิกต่อไป
- หากได้รับแจ้ง ให้อ่านและยอมรับข้อกำหนดของ Firebase แล้วคลิกต่อไป
- (ไม่บังคับ) เปิดใช้ความช่วยเหลือจาก AI ในคอนโซล Firebase (เรียกว่า "Gemini ใน Firebase")
- สำหรับ Codelab นี้ คุณไม่จำเป็นต้องใช้ Google Analytics ดังนั้นให้ปิดตัวเลือก Google Analytics
- คลิกสร้างโปรเจ็กต์ รอให้ระบบจัดสรรโปรเจ็กต์ แล้วคลิกดำเนินการต่อ
ดูข้อมูลเพิ่มเติมเกี่ยวกับโปรเจ็กต์ Firebase ได้ที่ทําความเข้าใจโปรเจ็กต์ Firebase
ตั้งค่าผลิตภัณฑ์ Firebase
แอปใช้ผลิตภัณฑ์ Firebase ต่อไปนี้ ซึ่งพร้อมใช้งานสำหรับเว็บแอป
- การตรวจสอบสิทธิ์: ช่วยให้ผู้ใช้ลงชื่อเข้าใช้แอปของคุณได้
- Firestore: บันทึกข้อมูลที่มีโครงสร้างไว้ในระบบคลาวด์และรับการแจ้งเตือนทันทีเมื่อข้อมูลมีการเปลี่ยนแปลง
- กฎการรักษาความปลอดภัยของ Firebase: รักษาความปลอดภัยให้ฐานข้อมูล
ผลิตภัณฑ์บางอย่างเหล่านี้ต้องมีการกำหนดค่าพิเศษหรือคุณต้องเปิดใช้ในคอนโซล Firebase
เปิดใช้การตรวจสอบสิทธิ์การลงชื่อเข้าใช้ด้วยอีเมล
- ในแผงภาพรวมของโปรเจ็กต์ของคอนโซล Firebase ให้ขยายเมนูสร้าง
- คลิกการตรวจสอบสิทธิ์ > เริ่มต้นใช้งาน > วิธีการลงชื่อเข้าใช้ > อีเมล/รหัสผ่าน > เปิดใช้ > บันทึก

ตั้งค่า Firestore
เว็บแอปใช้ Firestore เพื่อบันทึกข้อความแชทและรับข้อความแชทใหม่
วิธีตั้งค่า Firestore ในโปรเจ็กต์ Firebase มีดังนี้
- ในแผงด้านซ้ายของคอนโซล Firebase ให้ขยายสร้าง แล้วเลือกฐานข้อมูล Firestore
- คลิกสร้างฐานข้อมูล
- ตั้งค่ารหัสฐานข้อมูลเป็น
(default)ไว้ดังเดิม - เลือกตำแหน่งสำหรับฐานข้อมูล แล้วคลิกถัดไป
สำหรับแอปจริง คุณควรเลือกตำแหน่งที่อยู่ใกล้กับผู้ใช้ - คลิกเริ่มในโหมดทดสอบ อ่านข้อจำกัดความรับผิดเกี่ยวกับกฎความปลอดภัย
ในภายหลังใน Codelab นี้ คุณจะเพิ่มกฎความปลอดภัยเพื่อรักษาความปลอดภัยของข้อมูล อย่าเผยแพร่หรือเปิดเผยแอปต่อสาธารณะโดยไม่ได้เพิ่มกฎความปลอดภัยสำหรับฐานข้อมูล - คลิกสร้าง
4 กำหนดค่า Firebase
หากต้องการใช้ Firebase กับ Flutter คุณต้องทํางานต่อไปนี้ให้เสร็จสมบูรณ์เพื่อกําหนดค่าโปรเจ็กต์ Flutter ให้ใช้ไลบรารี FlutterFire อย่างถูกต้อง
- เพิ่มทรัพยากร Dependency ของ
FlutterFireลงในโปรเจ็กต์ - ลงทะเบียนแพลตฟอร์มที่เลือกในโปรเจ็กต์ Firebase
- ดาวน์โหลดไฟล์การกำหนดค่าเฉพาะแพลตฟอร์ม แล้วเพิ่มลงในโค้ด
ในไดเรกทอรีระดับบนสุดของแอป Flutter จะมีไดเรกทอรีย่อย android, ios, macos และ web ซึ่งมีไฟล์กำหนดค่าเฉพาะแพลตฟอร์มสำหรับ iOS และ Android ตามลำดับ
กำหนดค่าทรัพยากร Dependency
คุณต้องเพิ่มไลบรารี FlutterFire สำหรับผลิตภัณฑ์ Firebase 2 รายการที่คุณใช้ในแอปนี้ ได้แก่ Authentication และ Firestore
- จากบรรทัดคำสั่ง ให้เพิ่มทรัพยากร Dependency ต่อไปนี้
$ flutter pub add firebase_core firebase_auth cloud_firestore provider firebase_ui_auth
หากต้องการใช้ Firebase ในแอป Flutter คุณจะต้องรวมแพ็กเกจเฉพาะทาง 2-3 แพ็กเกจเข้าด้วยกัน ดังนี้
firebase_corepackage: นี่คือจุดเริ่มต้นที่สำคัญ คุณต้องมีแพ็กเกจนี้ เนื่องจากเครื่องมืออื่นๆ ทั้งหมดของ Firebase สำหรับ Flutter ขึ้นอยู่กับแพ็กเกจนี้- แพ็กเกจ
firebase_auth: แพ็กเกจนี้ใช้สำหรับจัดการบัญชีผู้ใช้ ซึ่งช่วยให้คุณเพิ่มฟีเจอร์ต่างๆ เช่น การลงชื่อสมัครใช้ การเข้าสู่ระบบ และการออกจากระบบได้ - แพ็กเกจ
cloud_firestore: ใช้แพ็กเกจนี้เพื่อเชื่อมต่อแอปกับฐานข้อมูล Firestore ซึ่งจะช่วยให้คุณบันทึกและเข้าถึงข้อมูลของแอปได้ firebase_ui_authpackage: แพ็กเกจนี้ช่วยให้ตั้งค่าการตรวจสอบสิทธิ์ได้เร็วขึ้นมาก โดยมีวิดเจ็ตที่พร้อมใช้งาน (เช่น หน้าจอเข้าสู่ระบบที่สร้างไว้ล่วงหน้า) เพื่อให้คุณไม่ต้องสร้างทุกอย่างตั้งแต่ต้น- แพ็กเกจ
provider: เป็นตัวเลือกยอดนิยมสำหรับการจัดการสถานะ ซึ่งช่วยให้แอปติดตามข้อมูล (เช่น ผู้ที่เข้าสู่ระบบ) และทำให้ข้อมูลดังกล่าวพร้อมใช้งานในหน้าจอต่างๆ ทั้งหมดที่ต้องการ
คุณได้เพิ่มแพ็กเกจที่จำเป็นแล้ว แต่ยังต้องกำหนดค่าโปรเจ็กต์ Runner ของ iOS, Android, macOS และเว็บเพื่อให้ใช้ Firebase ได้อย่างเหมาะสม นอกจากนี้ คุณยังใช้แพ็กเกจ provider ที่ช่วยให้แยกตรรกะทางธุรกิจออกจากตรรกะการแสดงผลได้ด้วย
ติดตั้ง FlutterFire CLI
FlutterFire CLI ขึ้นอยู่กับ Firebase CLI ที่อยู่เบื้องหลัง
- ติดตั้ง Firebase CLI ในเครื่องหากยังไม่ได้ดำเนินการ
- ติดตั้ง FlutterFire CLI โดยทำดังนี้
$ dart pub global activate flutterfire_cli
เมื่อติดตั้งแล้ว คำสั่ง flutterfire จะพร้อมใช้งานทั่วโลก
กำหนดค่าแอป
CLI จะดึงข้อมูลจากโปรเจ็กต์ Firebase และแอปของโปรเจ็กต์ที่เลือกเพื่อสร้างการกำหนดค่าทั้งหมดสำหรับแพลตฟอร์มที่เฉพาะเจาะจง
ในรูทของแอป ให้เรียกใช้คำสั่ง configure
$ 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>
ดูข้อมูลเพิ่มเติมได้ที่การรองรับ Flutter บนเดสก์ท็อป
5 เพิ่มฟังก์ชันการตอบกลับ
ตอนนี้คุณได้เพิ่ม Firebase ลงในแอปแล้ว คุณสามารถสร้างปุ่มตอบรับคำเชิญที่ลงทะเบียนผู้ใช้ด้วยการตรวจสอบสิทธิ์ สำหรับ Android เนทีฟ, iOS เนทีฟ และเว็บ จะมีFirebaseUI Authแพ็กเกจที่สร้างไว้ล่วงหน้า แต่คุณต้องสร้างความสามารถนี้สำหรับ Flutter
โปรเจ็กต์ที่คุณดึงข้อมูลก่อนหน้านี้มีวิดเจ็ตชุดหนึ่งที่ใช้ส่วนติดต่อผู้ใช้สำหรับขั้นตอนการตรวจสอบสิทธิ์ส่วนใหญ่ คุณใช้ตรรกะทางธุรกิจเพื่อผสานรวมการตรวจสอบสิทธิ์กับแอป
เพิ่มตรรกะทางธุรกิจด้วยแพ็กเกจ Provider
ใช้providerแพ็กเกจเพื่อให้ออบเจ็กต์สถานะแอปแบบรวมศูนย์พร้อมใช้งานตลอดทั้งโครงสร้างวิดเจ็ต Flutter ของแอป
- สร้างไฟล์ใหม่ชื่อ
app_state.dartโดยมีเนื้อหาดังนี้
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();
});
}
}
คำสั่ง import จะแนะนำ Firebase Core และ Auth ดึงแพ็กเกจ provider ที่ทำให้ออบเจ็กต์สถานะของแอปพร้อมใช้งานตลอดทั้งโครงสร้างวิดเจ็ต และรวมวิดเจ็ตการตรวจสอบสิทธิ์จากแพ็กเกจ firebase_ui_auth
ออบเจ็กต์สถานะแอปพลิเคชัน ApplicationState นี้มีหน้าที่หลักอย่างหนึ่งสำหรับขั้นตอนนี้ นั่นคือการแจ้งเตือนแผนผังวิดเจ็ตว่ามีการอัปเดตสถานะที่ผ่านการตรวจสอบสิทธิ์
คุณใช้ผู้ให้บริการเพื่อสื่อสารสถานะการเข้าสู่ระบบของผู้ใช้กับแอปเท่านั้น หากต้องการให้ผู้ใช้ลงชื่อเข้าใช้ คุณต้องใช้ UI ที่จัดทำโดยแพ็กเกจ firebase_ui_auth ซึ่งเป็นวิธีที่ยอดเยี่ยมในการเริ่มต้นใช้งานหน้าจอเข้าสู่ระบบในแอปอย่างรวดเร็ว
ผสานรวมขั้นตอนการตรวจสอบสิทธิ์
- แก้ไขการนำเข้าที่ด้านบนของไฟล์
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';
- เชื่อมต่อสถานะแอปกับการเริ่มต้นแอป แล้วเพิ่มขั้นตอนการตรวจสอบสิทธิ์ไปยัง
HomePage
lib/main.dart
void main() {
// Modify from here...
WidgetsFlutterBinding.ensureInitialized();
runApp(ChangeNotifierProvider(
create: (context) => ApplicationState(),
builder: ((context, child) => const App()),
));
// ...to here.
}
การแก้ไขฟังก์ชัน main() ทำให้แพ็กเกจผู้ให้บริการมีหน้าที่รับผิดชอบในการสร้างออบเจ็กต์สถานะแอปด้วยวิดเจ็ต ChangeNotifierProvider คุณใช้คลาส provider นี้เนื่องจากออบเจ็กต์สถานะแอปขยายคลาส ChangeNotifier ซึ่งช่วยให้แพ็กเกจ provider ทราบเวลาที่จะแสดงวิดเจ็ตที่ขึ้นต่อกันอีกครั้ง
- อัปเดตแอปเพื่อจัดการการไปยังหน้าจอต่างๆ ที่
FirebaseUIมอบให้คุณโดยสร้างการกำหนดค่า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
);
}
}
แต่ละหน้าจอจะมีประเภทการดำเนินการที่แตกต่างกันซึ่งเชื่อมโยงอยู่ โดยอิงตามสถานะใหม่ของขั้นตอนการตรวจสอบสิทธิ์ หลังจากการเปลี่ยนแปลงสถานะส่วนใหญ่ในการตรวจสอบสิทธิ์ คุณจะเปลี่ยนเส้นทางกลับไปยังหน้าจอที่ต้องการได้ ไม่ว่าจะเป็นหน้าจอหลักหรือหน้าจออื่น เช่น โปรไฟล์
- ในเมธอดบิลด์ของคลาส
HomePageให้ผสานรวมสถานะแอปกับวิดเจ็ต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!',
),
],
),
);
}
}
คุณสร้างอินสแตนซ์ของวิดเจ็ต AuthFunc และห่อไว้ในวิดเจ็ต Consumer วิดเจ็ต Consumer เป็นวิธีปกติที่ใช้provider package เพื่อสร้างส่วนหนึ่งของแผนผังใหม่เมื่อสถานะแอปเปลี่ยนแปลง วิดเจ็ต AuthFunc คือวิดเจ็ตเสริมที่คุณทดสอบ
ทดสอบขั้นตอนการตรวจสอบสิทธิ์

- ในแอป ให้แตะปุ่มตอบกลับเพื่อเริ่ม
SignInScreen

- ป้อนอีเมล หากคุณลงทะเบียนแล้ว ระบบจะแจ้งให้คุณป้อนรหัสผ่าน มิฉะนั้น ระบบจะแจ้งให้คุณกรอกแบบฟอร์มการลงทะเบียนให้เสร็จสมบูรณ์

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

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

เพิ่มข้อความลงใน Firestore
ในส่วนนี้ คุณจะเพิ่มฟังก์ชันการทำงานเพื่อให้ผู้ใช้เขียนข้อความลงในฐานข้อมูลได้ ก่อนอื่น ให้เพิ่มช่องแบบฟอร์มและปุ่มส่ง จากนั้นเพิ่มโค้ดที่เชื่อมต่อองค์ประกอบเหล่านี้กับฐานข้อมูล
- สร้างไฟล์ใหม่ชื่อ
guest_book.dartเพิ่มวิดเจ็ตแบบมีสถานะGuestBookเพื่อสร้างองค์ประกอบ UI ของช่องข้อความและปุ่มส่ง
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'),
],
),
),
],
),
),
);
}
}
มี 2 ประเด็นที่น่าสนใจในที่นี้ ก่อนอื่นให้สร้างอินสแตนซ์ของแบบฟอร์มเพื่อให้คุณตรวจสอบได้ว่าข้อความมีเนื้อหาจริงหรือไม่ และแสดงข้อความแสดงข้อผิดพลาดแก่ผู้ใช้หากไม่มีเนื้อหา หากต้องการตรวจสอบแบบฟอร์ม ให้เข้าถึงสถานะแบบฟอร์มที่อยู่เบื้องหลังแบบฟอร์มด้วย GlobalKey ดูข้อมูลเพิ่มเติมเกี่ยวกับคีย์และวิธีใช้งานได้ที่กรณีที่ควรใช้คีย์
นอกจากนี้ โปรดสังเกตวิธีจัดวางวิดเจ็ต คุณมี Row ที่มี TextFormField และ StyledButton ซึ่งมี Row นอกจากนี้ โปรดสังเกตว่า TextFormField อยู่ในวิดเจ็ต Expanded ซึ่งบังคับให้ TextFormField เติมพื้นที่ว่างเพิ่มเติมในแถว ดูข้อมูลเพิ่มเติมเกี่ยวกับสาเหตุที่ต้องมีข้อกำหนดนี้ได้ที่ทำความเข้าใจข้อจำกัด
ตอนนี้คุณมีวิดเจ็ตที่ช่วยให้ผู้ใช้ป้อนข้อความเพื่อเพิ่มลงในสมุดเยี่ยมแล้ว คุณจึงต้องนำวิดเจ็ตนี้ไปไว้บนหน้าจอ
- แก้ไขเนื้อหาของ
HomePageเพื่อเพิ่ม 2 บรรทัดต่อไปนี้ที่ส่วนท้ายของบุตรหลานของ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 วิธีการจะเพิ่มเนื้อหาข้อความลงในเอกสารใหม่ที่มีรหัสที่สร้างขึ้นโดยอัตโนมัติในคอลเล็กชัน guestbook
โปรดทราบว่า FirebaseAuth.instance.currentUser.uid คือการอ้างอิงถึงรหัสที่ไม่ซ้ำกันที่สร้างขึ้นโดยอัตโนมัติซึ่งการตรวจสอบสิทธิ์จะให้แก่ผู้ใช้ที่เข้าสู่ระบบทั้งหมด
- ในไฟล์
lib/app_state.dartให้เพิ่มเมธอดaddMessageToGuestBookคุณจะเชื่อมต่อความสามารถนี้กับอินเทอร์เฟซผู้ใช้ในขั้นตอนถัดไป
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.
}
เชื่อมต่อ UI และฐานข้อมูล
คุณมี UI ที่ผู้ใช้สามารถป้อนข้อความที่ต้องการเพิ่มในสมุดเยี่ยม และมีโค้ดเพื่อเพิ่มรายการลงใน Firestore ตอนนี้สิ่งที่คุณต้องทำก็คือเชื่อมต่อทั้ง 2 อย่าง
- ในไฟล์
lib/home_page.dartให้ทำการเปลี่ยนแปลงต่อไปนี้กับวิดเจ็ต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.
],
),
);
}
}
คุณได้แทนที่ 2 บรรทัดที่เพิ่มไว้ตอนต้นของขั้นตอนนี้ด้วยการติดตั้งใช้งานแบบเต็ม คุณใช้ Consumer<ApplicationState> อีกครั้งเพื่อให้สถานะแอปพร้อมใช้งานสำหรับส่วนของทรีที่คุณแสดง ซึ่งจะช่วยให้คุณตอบกลับผู้ที่ป้อนข้อความใน UI และเผยแพร่ข้อความนั้นในฐานข้อมูลได้ ในส่วนถัดไป คุณจะทดสอบว่าข้อความที่เพิ่มได้รับการเผยแพร่ในฐานข้อมูลหรือไม่
ทดสอบการส่งข้อความ
- ลงชื่อเข้าใช้แอปหากจำเป็น
- ป้อนข้อความ เช่น
Hey there!แล้วคลิกส่ง
การดำเนินการนี้จะเขียนข้อความลงในฐานข้อมูล Firestore อย่างไรก็ตาม คุณจะไม่เห็นข้อความในแอป Flutter จริงเนื่องจากคุณยังต้องติดตั้งใช้งานการดึงข้อมูล ซึ่งคุณจะทำในขั้นตอนถัดไป อย่างไรก็ตาม ในแดชบอร์ดฐานข้อมูลของคอนโซล Firebase คุณจะเห็นข้อความที่เพิ่มในคอลเล็กชัน guestbook หากส่งข้อความเพิ่มเติม คุณจะเพิ่มเอกสารลงในคอลเล็กชัน guestbook ได้มากขึ้น ดูข้อมูลโค้ดต่อไปนี้เป็นตัวอย่าง

7 อ่านข้อความ
แขกรับเชิญเขียนข้อความลงในฐานข้อมูลได้ แต่ยังดูข้อความในแอปไม่ได้ ถึงเวลาแก้ไขแล้ว
ซิงค์ข้อความ
หากต้องการแสดงข้อความ คุณต้องเพิ่ม Listener ที่ทริกเกอร์เมื่อข้อมูลเปลี่ยนแปลง แล้วสร้างองค์ประกอบ UI ที่แสดงข้อความใหม่ คุณเพิ่มโค้ดลงในสถานะของแอปที่รอรับข้อความที่เพิ่มใหม่จากแอป
- สร้างไฟล์
guest_book_message.dartใหม่ แล้วเพิ่มคลาสต่อไปนี้เพื่อแสดงมุมมองที่มีโครงสร้างของข้อมูลที่คุณจัดเก็บไว้ใน Firestore
lib/guest_book_message.dart
class GuestBookMessage {
GuestBookMessage({required this.name, required this.message});
final String name;
final String message;
}
- ในไฟล์
lib/app_state.dartให้เพิ่มการนำเข้าต่อไปนี้
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
- ในส่วนของ
ApplicationStateที่คุณกำหนดสถานะและตัวรับ ให้เพิ่มบรรทัดต่อไปนี้
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.
- ในส่วนการเริ่มต้นของ
ApplicationStateให้เพิ่มบรรทัดต่อไปนี้เพื่อติดตามการค้นหาในคอลเล็กชันเอกสารเมื่อผู้ใช้เข้าสู่ระบบ และยกเลิกการติดตามเมื่อผู้ใช้ออกจากระบบ
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();
});
}
ส่วนนี้มีความสำคัญเนื่องจากเป็นที่ที่คุณสร้างคําค้นหาในคอลเล็กชัน guestbook และจัดการการติดตามและการเลิกติดตามคอลเล็กชันนี้ คุณฟังสตรีม ซึ่งคุณจะสร้างแคชในเครื่องของข้อความในguestbookคอลเล็กชันใหม่ และจัดเก็บข้อมูลอ้างอิงถึงการสมัครใช้บริการนี้เพื่อให้คุณยกเลิกการสมัครใช้บริการได้ในภายหลัง มีหลายอย่างเกิดขึ้นที่นี่ ดังนั้นคุณควรสำรวจในดีบักเกอร์เพื่อตรวจสอบสิ่งที่เกิดขึ้นเพื่อให้ได้โมเดลในใจที่ชัดเจนยิ่งขึ้น ดูข้อมูลเพิ่มเติมได้ที่รับข้อมูลอัปเดตแบบเรียลไทม์ด้วย Firestore
- ในไฟล์
lib/guest_book.dartให้เพิ่มการนำเข้าต่อไปนี้
import 'guest_book_message.dart';
- ในวิดเจ็ต
GuestBookให้เพิ่มรายการข้อความเป็นส่วนหนึ่งของการกำหนดค่าเพื่อเชื่อมต่อสถานะที่เปลี่ยนแปลงนี้กับอินเทอร์เฟซผู้ใช้
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();
}
- ใน
_GuestBookStateให้แก้ไขเมธอดbuildดังนี้เพื่อแสดงการกำหนดค่านี้
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.
);
}
}
คุณจะห่อหุ้มเนื้อหาเดิมของเมธอด build() ด้วยวิดเจ็ต Column จากนั้นเพิ่มคอลเล็กชันสำหรับที่ท้ายของรายการย่อยของ Column เพื่อสร้าง Paragraph ใหม่สำหรับแต่ละข้อความในรายการข้อความ
- อัปเดตเนื้อหาของ
HomePageเพื่อสร้างGuestBookอย่างถูกต้องด้วยพารามิเตอร์messagesใหม่
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
),
],
],
),
),
ทดสอบการซิงค์ข้อความ
Firestore จะซิงค์ข้อมูลกับไคลเอ็นต์ที่สมัครใช้บริการฐานข้อมูลโดยอัตโนมัติและทันที
ทดสอบการซิงค์ข้อความ
- ในแอป ให้ค้นหาข้อความที่คุณสร้างไว้ก่อนหน้านี้ในฐานข้อมูล
- เขียนข้อความใหม่ โดยจะปรากฏขึ้นทันที
- เปิดพื้นที่ทํางานในหลายหน้าต่างหรือแท็บ ระบบจะซิงค์ข้อความแบบเรียลไทม์ในหน้าต่างและแท็บต่างๆ
- ไม่บังคับ: ในเมนูฐานข้อมูลของคอนโซล Firebase ให้ลบ แก้ไข หรือเพิ่มข้อความใหม่ด้วยตนเอง การเปลี่ยนแปลงทั้งหมดจะปรากฏใน UI
ยินดีด้วย คุณอ่านเอกสาร Firestore ในแอปได้แล้ว
ตัวอย่างแอป
|
|
|
|
8 ตั้งค่ากฎความปลอดภัยพื้นฐาน
คุณตั้งค่า 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 ขั้นตอนโบนัส: ฝึกฝนสิ่งที่ได้เรียนรู้
บันทึกสถานะการตอบกลับของผู้เข้าร่วม
ตอนนี้แอปของคุณอนุญาตให้ผู้ใช้แชทได้เฉพาะเมื่อสนใจกิจกรรมเท่านั้น นอกจากนี้ วิธีเดียวที่คุณจะรู้ว่ามีใครกำลังจะมาคือเมื่อบุคคลนั้นบอกในแชท
ในขั้นตอนนี้ คุณจะต้องจัดระเบียบและแจ้งให้ผู้เข้าร่วมทราบจำนวนผู้ที่เข้าร่วม คุณเพิ่มความสามารถ 2 อย่างลงในสถานะแอป อย่างแรกคือความสามารถของผู้ใช้ที่เข้าสู่ระบบในการเสนอชื่อว่าตนเองจะเข้าร่วมหรือไม่ ส่วนที่ 2 คือตัวนับจำนวนผู้เข้าร่วม
- ในไฟล์
lib/app_state.dartให้เพิ่มบรรทัดต่อไปนี้ลงในส่วนตัวช่วยเข้าถึงของApplicationStateเพื่อให้โค้ด UI โต้ตอบกับสถานะนี้ได้
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});
}
}
- อัปเดตวิธีการ
init()ของApplicationStateดังนี้
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();
});
}
โค้ดนี้จะเพิ่มการค้นหาที่สมัครรับข้อมูลเสมอเพื่อระบุจำนวนผู้เข้าร่วม และการค้นหาที่ 2 ซึ่งจะใช้งานได้เฉพาะในขณะที่ผู้ใช้เข้าสู่ระบบเพื่อพิจารณาว่าผู้ใช้เข้าร่วมหรือไม่
- เพิ่มการแจงนับต่อไปนี้ที่ด้านบนของไฟล์
lib/app_state.dart
lib/app_state.dart
enum Attending { yes, no, unknown }
- สร้างไฟล์ใหม่
yes_no_selection.dartแล้วกำหนดวิดเจ็ตใหม่ที่ทำหน้าที่เหมือนปุ่มตัวเลือก
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'),
),
],
),
);
}
}
}
โดยจะเริ่มต้นในสถานะที่ยังไม่แน่นอนซึ่งไม่ได้เลือกทั้งใช่และไม่ใช่ เมื่อผู้ใช้เลือกแล้วว่าจะเข้าร่วมหรือไม่ คุณจะแสดงตัวเลือกที่เลือกโดยไฮไลต์ด้วยปุ่มที่เติมสี และตัวเลือกอื่นๆ จะแสดงเป็นปุ่มแบน
- อัปเดตวิธีการของ
HomePagebuild()เพื่อใช้ประโยชน์จากYesNoSelection, เปิดให้ผู้ใช้ที่เข้าสู่ระบบเสนอชื่อว่าตนจะเข้าร่วมหรือไม่ และแสดงจำนวนผู้เข้าร่วมกิจกรรม
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,
),
],
],
),
),
เพิ่มกฎ
คุณตั้งค่ากฎบางอย่างไว้แล้ว ดังนั้นระบบจะปฏิเสธข้อมูลที่คุณเพิ่มด้วยปุ่ม คุณต้องอัปเดตกฎเพื่ออนุญาตให้เพิ่มรายการลงในคอลเล็กชัน attendees
- ใน
attendeesคอลเล็กชัน ให้คัดลอก UID การตรวจสอบสิทธิ์ที่คุณใช้เป็นชื่อเอกสาร แล้วตรวจสอบว่าuidของผู้ส่งตรงกับเอกสารที่ผู้ส่งกำลังเขียน
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// ... //
match /attendees/{userId} {
allow read: if true;
allow write: if request.auth.uid == userId;
}
}
}
ซึ่งจะช่วยให้ทุกคนอ่านรายชื่อผู้เข้าร่วมได้เนื่องจากไม่มีข้อมูลส่วนตัว แต่มีเพียงผู้สร้างเท่านั้นที่อัปเดตได้
- เพิ่มการตรวจสอบข้อมูลเพื่อให้แน่ใจว่าช่องที่คาดไว้ทั้งหมดอยู่ในเอกสาร
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// ... //
match /attendees/{userId} {
allow read: if true;
allow write: if request.auth.uid == userId
&& "attending" in request.resource.data;
}
}
}
- ไม่บังคับ: ในแอป ให้คลิกปุ่มเพื่อดูผลลัพธ์ในแดชบอร์ด Firestore ในคอนโซล Firebase
ตัวอย่างแอป
|
|
|
|















