1. ก่อนเริ่ม
ใน Codelab นี้ คุณจะได้เรียนรู้พื้นฐานบางอย่างของ Firebase เพื่อสร้างแอปมือถือ Flutter สำหรับ Android และ iOS
ข้อกำหนดเบื้องต้น
- ทำความรู้จักกับ Flutter
- SDK ของ Flutter
- โปรแกรมแก้ไขข้อความที่คุณเลือก
สิ่งที่คุณจะได้เรียนรู้
- วิธีสร้างแอปแชท RSVP และสมุดเยี่ยมบน Android, iOS, เว็บและ macOS ด้วย Flutter
- วิธีตรวจสอบผู้ใช้ด้วย Firebase Authentication และซิงค์ข้อมูลกับ Firestore
สิ่งที่คุณต้องการ
อุปกรณ์ใด ๆ ต่อไปนี้:
- อุปกรณ์ Android หรือ iOS จริงที่เชื่อมต่อกับคอมพิวเตอร์ของคุณและตั้งค่าเป็นโหมดนักพัฒนาซอฟต์แวร์
- โปรแกรมจำลอง iOS (ต้องใช้ เครื่องมือ Xcode )
- โปรแกรมจำลอง Android (ต้องตั้งค่าใน Android Studio )
คุณต้องมีสิ่งต่อไปนี้ด้วย:
- เบราว์เซอร์ที่คุณเลือก เช่น Google Chrome
- IDE หรือโปรแกรมแก้ไขข้อความที่คุณเลือกกำหนดค่าด้วยปลั๊กอิน Dart และ Flutter เช่น Android Studio หรือ Visual Studio Code
- Flutter หรือ
beta
เวอร์ชันstable
ล่าสุดหากคุณชอบใช้ชีวิตบนขอบ - บัญชี Google สำหรับสร้างและจัดการโครงการ Firebase ของคุณ
-
Firebase
CLI ลงชื่อเข้าใช้บัญชี Google ของคุณ
2. รับรหัสตัวอย่าง
ดาวน์โหลดเวอร์ชันเริ่มต้นของโครงการของคุณจาก GitHub:
- จากบรรทัดคำสั่ง ให้โคลน ที่เก็บ GitHub ในไดเร็กทอรี
flutter-codelabs
:
git clone https://github.com/flutter/codelabs.git flutter-codelabs
ไดเร็กทอรี flutter-codelabs
มีโค้ดสำหรับชุดโค้ดแล็บ รหัสสำหรับ codelab นี้อยู่ในไดเร็กทอรี flutter-codelabs/firebase-get-to-know-flutter
ไดเร็กทอรีประกอบด้วยชุดสแนปชอตที่แสดงว่าโปรเจ็กต์ของคุณควรมีลักษณะอย่างไรเมื่อสิ้นสุดแต่ละขั้นตอน ตัวอย่างเช่น คุณอยู่ในขั้นตอนที่สอง
- ค้นหาไฟล์ที่ตรงกันสำหรับขั้นตอนที่สอง:
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/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
- ในคอนโซล ให้คลิก เพิ่มโครงการ หรือ สร้างโครงการ
- ในช่อง Project name ให้ป้อน Firebase-Flutter-Codelab แล้วคลิก Continue
- คลิกผ่านตัวเลือกการสร้างโครงการ หากได้รับแจ้ง ให้ยอมรับข้อกำหนดของ Firebase แต่ข้ามการตั้งค่า Google Analytics เนื่องจากคุณจะไม่ใช้ Google Analytics สำหรับแอปนี้
หากต้องการเรียนรู้เพิ่มเติมเกี่ยวกับโปรเจ็กต์ Firebase โปรดดู ที่ ทำความเข้าใจโปรเจ็กต์ Firebase
แอปใช้ผลิตภัณฑ์ Firebase ต่อไปนี้ซึ่งมีให้สำหรับเว็บแอป:
- การรับรองความถูกต้อง: อนุญาตให้ผู้ใช้ลงชื่อเข้าใช้แอปของคุณ
- Firestore: บันทึกข้อมูลที่มีโครงสร้างบนคลาวด์และรับการแจ้งเตือนทันทีเมื่อข้อมูลมีการเปลี่ยนแปลง
- กฎความปลอดภัยของ Firebase: รักษาความปลอดภัยฐานข้อมูลของคุณ
ผลิตภัณฑ์เหล่านี้บางรายการต้องมีการกำหนดค่าพิเศษ หรือคุณต้องเปิดใช้งานในคอนโซล Firebase
เปิดใช้งานการตรวจสอบสิทธิ์การลงชื่อเข้าใช้ด้วยอีเมล
- ในบาน หน้าต่างภาพรวมโครงการ ของคอนโซล Firebase ให้ขยายเมนู สร้าง
- คลิก การรับรองความถูกต้อง > เริ่มต้น > วิธีการลงชื่อเข้าใช้ > อีเมล/รหัสผ่าน > เปิดใช้งาน > บันทึก
เปิดใช้งาน Firestore
เว็บแอปใช้ Firestore เพื่อบันทึกข้อความแชทและรับข้อความแชทใหม่
เปิดใช้งาน Firestore:
- ในเมนู สร้าง ให้ คลิก Cloud Firestore > สร้างฐานข้อมูล
- เลือก เริ่มในโหมดทดสอบ จากนั้นอ่านข้อจำกัดความรับผิดชอบเกี่ยวกับกฎความปลอดภัย โหมดทดสอบช่วยให้แน่ใจว่าคุณสามารถเขียนลงในฐานข้อมูลระหว่างการพัฒนาได้อย่างอิสระ
- คลิก ถัดไป จากนั้นเลือกตำแหน่งที่ตั้งสำหรับฐานข้อมูลของคุณ คุณสามารถใช้ค่าเริ่มต้น คุณไม่สามารถเปลี่ยนตำแหน่งได้ในภายหลัง
- คลิก เปิดใช้งาน
4. กำหนดค่า Firebase
หากต้องการใช้ Firebase กับ Flutter คุณต้องทำงานต่อไปนี้ให้เสร็จเพื่อกำหนดค่าโปรเจ็กต์ Flutter เพื่อใช้ไลบรารี FlutterFire
อย่างถูกต้อง:
- เพิ่มการอ้างอิง
FlutterFire
ในโครงการของคุณ - ลงทะเบียนแพลตฟอร์มที่ต้องการในโครงการ Firebase
- ดาวน์โหลดไฟล์คอนฟิกูเรชันเฉพาะแพลตฟอร์ม จากนั้นเพิ่มลงในโค้ด
ในไดเร็กทอรีระดับบนสุดของแอพ Flutter มีไดเร็กทอรีย่อย android
, ios
, macos
และ web
ซึ่งเก็บไฟล์การกำหนดค่าเฉพาะแพลตฟอร์มสำหรับ iOS และ Android ตามลำดับ
กำหนดค่าการพึ่งพา
คุณต้องเพิ่มไลบรารี FlutterFire
สำหรับผลิตภัณฑ์ Firebase สองรายการที่คุณใช้ในแอปนี้: การรับรองความถูกต้องและ Firestore
- จากบรรทัดรับคำสั่ง เพิ่มการอ้างอิงต่อไปนี้:
$ 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
คุณเพิ่มแพ็คเกจที่จำเป็นแล้ว แต่คุณต้องกำหนดค่าโครงการ iOS, Android, macOS และ Web Runner เพื่อใช้ 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. เพิ่มฟังก์ชัน RSVP
เมื่อคุณเพิ่ม Firebase ลงในแอปแล้ว คุณสามารถสร้างปุ่ม RSVP ที่ลงทะเบียนบุคคลด้วย การรับรองความถูกต้อง สำหรับ 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.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
วิดเจ็ตสำหรับผู้บริโภคเป็นวิธีปกติที่สามารถใช้แพ็คเกจ provider
เพื่อสร้างส่วนของแผนผังใหม่เมื่อสถานะของแอพเปลี่ยนไป วิดเจ็ต AuthFunc
เป็นวิดเจ็ตเสริมที่คุณทดสอบ
ทดสอบโฟลว์การรับรองความถูกต้อง
- ในแอป ให้แตะปุ่ม RSVP เพื่อเริ่มต้น
SignInScreen
- ป้อนที่อยู่อีเมล หากคุณลงทะเบียนแล้ว ระบบจะให้คุณป้อนรหัสผ่าน มิฉะนั้น ระบบจะแจ้งให้คุณกรอกแบบฟอร์มลงทะเบียน
- ป้อนรหัสผ่านที่มีความยาวน้อยกว่าหกอักขระเพื่อตรวจสอบขั้นตอนการจัดการข้อผิดพลาด หากคุณลงทะเบียนแล้ว คุณจะเห็นรหัสผ่านแทน
- ป้อนรหัสผ่านไม่ถูกต้องเพื่อตรวจสอบขั้นตอนการจัดการข้อผิดพลาด
- ป้อนรหัสผ่านที่ถูกต้อง คุณจะเห็นประสบการณ์การเข้าสู่ระบบ ซึ่งให้ผู้ใช้สามารถออกจากระบบได้
6. เขียนข้อความไปที่ Firestore
เป็นเรื่องดีที่ทราบว่าผู้ใช้กำลังจะมา แต่คุณต้องให้แขกทำอย่างอื่นในแอป จะเกิดอะไรขึ้นถ้าพวกเขาสามารถฝากข้อความไว้ในสมุดเยี่ยมได้? พวกเขาสามารถแบ่งปันว่าทำไมพวกเขาถึงรู้สึกตื่นเต้นที่ได้มาหรือใครที่พวกเขาหวังว่าจะได้พบ
หากต้องการจัดเก็บข้อความแชทที่ผู้ใช้เขียนในแอป ให้ใช้ Firestore
แบบจำลองข้อมูล
Firestore เป็นฐานข้อมูล NoSQL และข้อมูลที่จัดเก็บไว้ในฐานข้อมูลจะแบ่งออกเป็นคอลเล็กชัน เอกสาร ฟิลด์ และคอลเล็กชันย่อย คุณจัดเก็บแต่ละข้อความของการแชทเป็นเอกสารในคอ guestbook
ชันสมุดเยี่ยม ซึ่งเป็นคอลเลกชันระดับบนสุด
เพิ่มข้อความไปยัง Firestore
ในส่วนนี้ คุณจะเพิ่มฟังก์ชันสำหรับผู้ใช้ในการเขียนข้อความไปยังฐานข้อมูล ขั้นแรก คุณเพิ่มฟิลด์แบบฟอร์มและปุ่มส่ง จากนั้นเพิ่มรหัสที่เชื่อมต่อองค์ประกอบเหล่านี้กับฐานข้อมูล
- สร้างไฟล์ใหม่ชื่อ
guest_book.dart
เพิ่มวิดเจ็ต StatefulGuestBook
เพื่อสร้างองค์ประกอบ 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'),
],
),
),
],
),
),
);
}
}
มีจุดสนใจสองสามจุดที่นี่ ขั้นแรก คุณต้องสร้างอินสแตนซ์ของฟอร์มเพื่อให้คุณสามารถตรวจสอบได้ว่าข้อความมีเนื้อหาอยู่จริง และแสดงข้อความแสดงข้อผิดพลาดให้ผู้ใช้เห็นหากไม่มี ในการตรวจสอบความถูกต้องของฟอร์ม คุณเข้าถึงสถานะของฟอร์มที่อยู่เบื้องหลังฟอร์มด้วย GlobalKey
สำหรับข้อมูลเพิ่มเติมเกี่ยวกับคีย์และวิธีการใช้ โปรดดูที่ เมื่อใดควรใช้คีย์
สังเกตวิธีการวางวิดเจ็ตด้วย คุณมี 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)),
แม้ว่านี่จะเพียงพอที่จะแสดงวิดเจ็ต แต่ก็ไม่เพียงพอที่จะทำอะไรที่เป็นประโยชน์ คุณอัปเดตรหัสนี้ในไม่ช้าเพื่อให้ใช้งานได้
ดูตัวอย่างแอป
เมื่อผู้ใช้คลิก SEND จะเรียกใช้ข้อมูลโค้ดต่อไปนี้ เพิ่มเนื้อหาของฟิลด์ป้อนข้อความไปยังคอลเลกชัน guestbook
ของฐานข้อมูล โดยเฉพาะอย่างยิ่ง เมธอด addMessageToGuestBook
เพิ่มเนื้อหาข้อความไปยังเอกสารใหม่ด้วย ID ที่สร้างขึ้นโดยอัตโนมัติในคอลเลกชัน guestbook
โปรดทราบว่า FirebaseAuth.instance.currentUser.uid
เป็นการอ้างอิงถึง ID เฉพาะที่สร้างขึ้นโดยอัตโนมัติซึ่งการรับรองความถูกต้องมอบให้กับผู้ใช้ที่เข้าสู่ระบบทั้งหมด
- ในไฟล์
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 ตอนนี้สิ่งที่คุณต้องทำคือเชื่อมต่อทั้งสอง
- ในไฟล์
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.
],
),
);
}
}
คุณได้แทนที่สองบรรทัดที่คุณเพิ่มเมื่อเริ่มต้นขั้นตอนนี้ด้วยการใช้งานเต็มรูปแบบ คุณใช้ Consumer<ApplicationState>
อีกครั้งเพื่อทำให้สถานะแอปพร้อมใช้งานสำหรับส่วนของแผนผังที่คุณแสดงผล ซึ่งช่วยให้คุณโต้ตอบกับบุคคลที่ป้อนข้อความใน UI และเผยแพร่ในฐานข้อมูล ในส่วนถัดไป คุณจะทดสอบว่าข้อความที่เพิ่มนั้นเผยแพร่ในฐานข้อมูลหรือไม่
ทดสอบการส่งข้อความ
- หากจำเป็น ให้ลงชื่อเข้าใช้แอป
- ป้อนข้อความ เช่น
Hey there!
แล้วคลิก ส่ง
การดำเนินการนี้เขียนข้อความไปยังฐานข้อมูล Firestore ของคุณ อย่างไรก็ตาม คุณไม่เห็นข้อความในแอป Flutter จริงของคุณ เนื่องจากคุณยังคงต้องใช้การดึงข้อมูล ซึ่งคุณจะดำเนินการในขั้นตอนต่อไป อย่างไรก็ตาม ใน แดชบอร์ด ฐานข้อมูล ของคอนโซล Firebase คุณจะเห็นข้อความที่เพิ่มไว้ในคอลเล็กชัน guestbook
ถ้าคุณส่งข้อความมากขึ้น แสดงว่าคุณเพิ่มเอกสารในคอลเลคชัน guestbook
ของคุณ ตัวอย่างเช่น ดูข้อมูลโค้ดต่อไปนี้:
7. อ่านข้อความ
เป็นเรื่องดีที่ผู้เยี่ยมชมสามารถเขียนข้อความไปยังฐานข้อมูล แต่ยังไม่เห็นข้อความเหล่านั้นในแอป ได้เวลาแก้ไขแล้ว!
ซิงโครไนซ์ข้อความ
หากต้องการแสดงข้อความ คุณต้องเพิ่มผู้ฟังที่จะทริกเกอร์เมื่อข้อมูลเปลี่ยนแปลง แล้วจึงสร้างองค์ประกอบ 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
ที่คุณกำหนด state และ getters ให้เพิ่มบรรทัดต่อไปนี้:
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. ขั้นตอนโบนัส: ฝึกฝนสิ่งที่คุณได้เรียนรู้
บันทึกสถานะ RSVP ของผู้เข้าร่วม
ตอนนี้ แอปของคุณอนุญาตให้ผู้คนแชทเมื่อพวกเขาสนใจในกิจกรรมเท่านั้น นอกจากนี้ วิธีเดียวที่คุณจะรู้ว่ามีคนกำลังมาคือเมื่อพวกเขาพูดเช่นนั้นในแชท
ในขั้นตอนนี้ คุณจะได้จัดระเบียบและแจ้งให้ผู้คนทราบว่าจะมากี่คน คุณเพิ่มความสามารถบางอย่างให้กับสถานะแอป ประการแรกคือความสามารถสำหรับผู้ใช้ที่เข้าสู่ระบบเพื่อเสนอว่าพวกเขากำลังเข้าร่วมหรือไม่ ประการที่สองเป็นตัวนับจำนวนผู้เข้าร่วมประชุม
- ในไฟล์
lib/app_state.dart
ให้เพิ่มบรรทัดต่อไปนี้ลงในส่วน accessors ของ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) {
_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();
});
}
รหัสนี้เพิ่มแบบสอบถามที่สมัครรับข้อมูลเสมอเพื่อกำหนดจำนวนผู้เข้าร่วมและแบบสอบถามที่สองที่ใช้งานเฉพาะในขณะที่ผู้ใช้เข้าสู่ระบบเพื่อตรวจสอบว่าผู้ใช้เข้าร่วมหรือไม่
- เพิ่มการแจงนับต่อไปนี้ที่ด้านบนของไฟล์
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'),
),
],
),
);
}
}
}
เริ่มต้นในสถานะที่ไม่แน่นอนโดยไม่ได้เลือกทั้ง ใช่ และ ไม่ใช่ เมื่อผู้ใช้เลือกว่าจะเข้าร่วมหรือไม่ คุณจะแสดงตัวเลือกนั้นที่ไฮไลต์ด้วยปุ่มที่กรอก และตัวเลือกอื่นๆ จะลดลงด้วยการแสดงผลแบบเรียบๆ
- อัปเดตเมธอด
build()
ของHomePage
เพื่อใช้ประโยชน์จาก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 เพื่อสร้างเว็บแอปเชิงโต้ตอบแบบเรียลไทม์!