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
Firebase
CLI เข้าสู่ระบบบัญชี 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_core
แพ็กเกจคือโค้ดทั่วไปที่จำเป็นสำหรับปลั๊กอิน Firebase Flutter ทั้งหมด
$ flutter pub add firebase_auth
firebase_auth
แพ็กเกจช่วยให้ผสานรวมกับการตรวจสอบสิทธิ์ได้
$ flutter pub add cloud_firestore
แพ็กเกจ cloud_firestore
ช่วยให้เข้าถึงที่เก็บข้อมูล Firestore ได้
$ flutter pub add provider
firebase_ui_auth
แพ็กเกจมีชุดวิดเจ็ตและยูทิลิตีเพื่อเพิ่มความเร็วของนักพัฒนาแอปด้วยขั้นตอนการตรวจสอบสิทธิ์
$ flutter pub add firebase_ui_auth
คุณได้เพิ่มแพ็กเกจที่จำเป็นแล้ว แต่ยังต้องกำหนดค่าโปรเจ็กต์ 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
จากนั้นเพิ่ม collection for ที่ท้ายของรายการย่อยของ 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'),
),
],
),
);
}
}
}
โดยจะเริ่มต้นในสถานะที่ยังไม่แน่นอนซึ่งไม่ได้เลือกทั้งใช่และไม่ใช่ เมื่อผู้ใช้เลือกแล้วว่าจะเข้าร่วมหรือไม่ คุณจะแสดงตัวเลือกที่เลือกโดยไฮไลต์ด้วยปุ่มที่เติมสี และตัวเลือกอื่นๆ จะแสดงเป็นปุ่มแบน
- อัปเดตวิธีการของ
HomePage
build()
เพื่อใช้ประโยชน์จาก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
ตัวอย่างแอป
10. ยินดีด้วย
คุณใช้ Firebase เพื่อสร้างเว็บแอปแบบเรียลไทม์ที่มีการโต้ตอบได้