(ไม่บังคับ) สร้างต้นแบบและทดสอบด้วย Firebase Emulator Suite
ก่อนที่จะพูดถึงวิธีที่แอปอ่านและเขียนไปยัง Realtime Database เราจะมาแนะนำชุดเครื่องมือที่คุณสามารถใช้สร้างต้นแบบและทดสอบ Realtime Database ได้ ฟังก์ชัน: Firebase Emulator Suite หากคุณกำลังลองใช้ข้อมูลอื่น โมเดล, การเพิ่มประสิทธิภาพของกฎความปลอดภัย หรือการทำงานเพื่อค้นหา วิธีที่คุ้มค่าในการโต้ตอบกับระบบหลังบ้าน ทำให้สามารถทำงานในองค์กรได้ โดยไม่ต้องใช้บริการแบบสดอาจเป็นความคิดที่ดี
โปรแกรมจำลอง Realtime Database เป็นส่วนหนึ่งของชุดโปรแกรมจำลอง ทำให้แอปของคุณสามารถโต้ตอบกับเนื้อหาและการกำหนดค่าฐานข้อมูลที่เลียนแบบ รวมทั้งทรัพยากรโครงการจำลอง (ฟังก์ชัน ฐานข้อมูลอื่นๆ และกฎความปลอดภัย).emulator_suite_short
การใช้โปรแกรมจำลอง Realtime Database ประกอบด้วยไม่กี่ขั้นตอนดังนี้
- การเพิ่มบรรทัดโค้ดลงในการกำหนดค่าการทดสอบของแอปเพื่อเชื่อมต่อกับโปรแกรมจำลอง
- จากรูทของไดเรกทอรีโปรเจ็กต์ในเครื่องโดยเรียกใช้
firebase emulators:start
- การโทรจากโค้ดต้นแบบของแอปโดยใช้แพลตฟอร์ม Realtime Database SDK ตามปกติ หรือใช้ Realtime Database REST API
โปรดดูคำแนะนำแบบทีละขั้นเกี่ยวกับ Realtime Database และ Cloud Functions โดยละเอียด นอกจากนี้คุณควรดูข้อมูลที่ข้อมูลเบื้องต้นเกี่ยวกับชุดโปรแกรมจำลอง
รับ DatabaseReference
หากต้องการอ่านหรือเขียนข้อมูลจากฐานข้อมูล คุณต้องมีอินสแตนซ์
DatabaseReference
:
DatabaseReference ref = FirebaseDatabase.instance.ref();
เขียนข้อมูล
เอกสารนี้ครอบคลุมพื้นฐานการอ่านและการเขียนข้อมูล Firebase
ข้อมูล Firebase เขียนไปยัง DatabaseReference
และดึงข้อมูลโดย
กำลังรอหรือฟังเหตุการณ์ที่เกิดจากข้อมูลอ้างอิง ปล่อยเหตุการณ์แล้ว
อีกครั้งเป็นสถานะเริ่มต้นของข้อมูล และอีกครั้งเมื่อข้อมูลเปลี่ยนแปลง
การดำเนินการเขียนพื้นฐาน
สำหรับการดำเนินการเขียนพื้นฐาน คุณสามารถใช้ set()
เพื่อบันทึกข้อมูลไปยัง
โดยแทนที่ข้อมูลที่มีอยู่ในเส้นทางนั้น คุณสามารถตั้งค่าข้อมูลอ้างอิง
เป็นประเภท String
, boolean
, int
, double
, Map
, List
เช่น เพิ่มผู้ใช้ด้วย set()
ดังนี้
DatabaseReference ref = FirebaseDatabase.instance.ref("users/123");
await ref.set({
"name": "John",
"age": 18,
"address": {
"line1": "100 Mountain View"
}
});
การใช้ set()
ในลักษณะนี้จะเขียนทับข้อมูลในตำแหน่งที่ระบุ
รวมถึงโหนดย่อย อย่างไรก็ตาม คุณยังอัปเดตรายการย่อยได้โดยไม่ต้อง
การเขียนออบเจ็กต์ใหม่ทั้งหมด หากต้องการอนุญาตให้ผู้ใช้อัปเดตโปรไฟล์ของตนเอง
คุณสามารถอัปเดตชื่อผู้ใช้ได้ดังนี้
DatabaseReference ref = FirebaseDatabase.instance.ref("users/123");
// Only update the name, leave the age and address!
await ref.update({
"age": 19,
});
เมธอด update()
ยอมรับเส้นทางย่อยไปยังโหนด ซึ่งช่วยให้คุณอัปเดตเส้นทางย่อยหลายรายการได้
บนฐานข้อมูลพร้อมกัน:
DatabaseReference ref = FirebaseDatabase.instance.ref("users");
await ref.update({
"123/age": 19,
"123/address/line1": "1 Mountain View",
});
อ่านข้อมูล
อ่านข้อมูลโดยการฟังเหตุการณ์มูลค่า
ในการอ่านข้อมูลที่เส้นทางและฟังการเปลี่ยนแปลง ให้ใช้
พร็อพเพอร์ตี้ onValue
ของ DatabaseReference
ที่จะฟัง
DatabaseEvent
วินาที
คุณสามารถใช้ DatabaseEvent
เพื่ออ่านข้อมูลในเส้นทางที่กำหนด
ตามที่มีอยู่ ณ เวลาที่เกิดเหตุการณ์ เหตุการณ์นี้จะเกิดขึ้นเมื่อ
Listener จะแนบอยู่และอีกครั้งทุกครั้งที่
ข้อมูล รวมถึงเด็กด้วย
การเปลี่ยนแปลง เหตุการณ์มีพร็อพเพอร์ตี้ snapshot
ที่มีข้อมูลทั้งหมดในเวลานั้น
ตำแหน่ง รวมถึงข้อมูลย่อย หากไม่มีข้อมูล สแนปชอต
พร็อพเพอร์ตี้ exists
จะเป็น false
และพร็อพเพอร์ตี้ value
จะเป็นค่าว่าง
ตัวอย่างต่อไปนี้แสดงแอปพลิเคชันการเขียนบล็อกโซเชียลที่เรียก รายละเอียดของโพสต์จากฐานข้อมูล:
DatabaseReference starCountRef =
FirebaseDatabase.instance.ref('posts/$postId/starCount');
starCountRef.onValue.listen((DatabaseEvent event) {
final data = event.snapshot.value;
updateStarCount(data);
});
Listener จะได้รับ DataSnapshot
ที่มีข้อมูลตามที่ระบุ
ตำแหน่งในฐานข้อมูล ณ เวลาที่เกิดเหตุการณ์ในพร็อพเพอร์ตี้ value
อ่านข้อมูลครั้งเดียว
อ่านครั้งเดียวโดยใช้ get()
SDK ออกแบบมาเพื่อจัดการการโต้ตอบกับเซิร์ฟเวอร์ฐานข้อมูล ไม่ว่าจะเป็น ออนไลน์หรือออฟไลน์
โดยทั่วไป คุณควรใช้เทคนิคเหตุการณ์มูลค่าที่อธิบายไว้ข้างต้นในการอ่าน เพื่อรับการแจ้งเตือนเมื่อมีการอัปเดตข้อมูลจากแบ็กเอนด์ เทคนิคเหล่านั้น ลดการใช้งานและการเรียกเก็บเงินของคุณ และได้รับการเพิ่มประสิทธิภาพเพื่อช่วยให้ผู้ใช้ ขณะออนไลน์และออฟไลน์
หากต้องการข้อมูลเพียงครั้งเดียว คุณสามารถใช้ get()
เพื่อดูภาพรวมของ
จากฐานข้อมูล หาก get()
ไม่สามารถแสดงผล
ค่าของเซิร์ฟเวอร์ ไคลเอ็นต์จะตรวจสอบแคชที่จัดเก็บในตัวเครื่อง และแสดงผลข้อผิดพลาด
หากยังไม่พบค่า
ตัวอย่างต่อไปนี้แสดงการดึงข้อมูลชื่อผู้ใช้ที่เปิดเผยต่อสาธารณะของผู้ใช้ เพียงครั้งเดียวจากฐานข้อมูล:
final ref = FirebaseDatabase.instance.ref();
final snapshot = await ref.child('users/$userId').get();
if (snapshot.exists) {
print(snapshot.value);
} else {
print('No data available.');
}
การใช้ get()
โดยไม่จำเป็นอาจเพิ่มการใช้แบนด์วิดท์และทำให้สูญเสียสิทธิ์ใช้งาน
ซึ่งป้องกันได้โดยใช้ Listener แบบเรียลไทม์ตามที่แสดงไว้
ที่ด้านบน
อ่านข้อมูลครั้งเดียวด้วย one()
ในบางกรณี คุณอาจต้องการให้คืนค่าจากแคชในเครื่อง
ทันที แทนที่จะตรวจสอบค่าที่อัปเดตแล้วบนเซิร์ฟเวอร์ ในการแสดงผลเหล่านั้น
คุณสามารถใช้ once()
เพื่อรับข้อมูลจากดิสก์แคชในเครื่อง
ทันที
วิธีนี้เป็นประโยชน์สําหรับข้อมูลที่ต้องโหลดเพียงครั้งเดียวและไม่คาดว่าจะโหลด เปลี่ยนบ่อยครั้งหรือต้องใช้การฟังอย่างต่อเนื่อง เช่น แอปการเขียนบล็อก ในตัวอย่างก่อนหน้านี้ใช้วิธีนี้เพื่อโหลดโปรไฟล์ของผู้ใช้ เริ่มเขียนโพสต์ใหม่:
final event = await ref.once(DatabaseEventType.value);
final username = event.snapshot.value?.username ?? 'Anonymous';
การอัปเดตหรือลบข้อมูล
อัปเดตช่องข้อมูลที่เฉพาะเจาะจง
เพื่อเขียนไปยังโหนดย่อยที่ต้องการพร้อมกันโดยไม่เขียนทับโหนดอื่น
โหนดย่อย ให้ใช้เมธอด update()
เมื่อโทรหา update()
คุณจะอัปเดตค่าย่อยระดับล่างได้โดย
โดยระบุเส้นทางของคีย์นี้ หากมีการจัดเก็บข้อมูลในหลายตำแหน่งเพื่อปรับขนาด
คุณจะสามารถอัปเดตอินสแตนซ์ทั้งหมดของข้อมูลนั้นได้โดยใช้
การขยายข้อมูล ตัวอย่างเช่น
แอปบล็อกโซเชียลอาจต้องการสร้างโพสต์และอัปเดตไปพร้อมๆ กัน
ฟีดกิจกรรมล่าสุดและฟีดกิจกรรมของผู้ใช้ที่โพสต์ วิธีการคือ
แอปพลิเคชันการเขียนบล็อกจะใช้โค้ดดังนี้
void writeNewPost(String uid, String username, String picture, String title,
String body) async {
// A post entry.
final postData = {
'author': username,
'uid': uid,
'body': body,
'title': title,
'starCount': 0,
'authorPic': picture,
};
// Get a key for a new Post.
final newPostKey =
FirebaseDatabase.instance.ref().child('posts').push().key;
// Write the new post's data simultaneously in the posts list and the
// user's post list.
final Map<String, Map> updates = {};
updates['/posts/$newPostKey'] = postData;
updates['/user-posts/$uid/$newPostKey'] = postData;
return FirebaseDatabase.instance.ref().update(updates);
}
ตัวอย่างนี้ใช้ push()
เพื่อสร้างโพสต์ในโหนดที่มีโพสต์สำหรับ
ผู้ใช้ทั้งหมดที่ /posts/$postid
และดึงคีย์ไปพร้อมๆ กันด้วย
key
จากนั้นสามารถใช้คีย์เพื่อสร้างรายการที่ 2 ในส่วน
โพสต์ที่ /user-posts/$userid/$postid
การใช้เส้นทางเหล่านี้จะทำให้คุณสามารถอัปเดตสถานที่หลายแห่งพร้อมกันใน
โครงสร้าง JSON ที่มีการเรียกไปยัง update()
ครั้งเดียว เช่นตามตัวอย่างนี้
สร้างโพสต์ใหม่ในทั้ง 2 ตำแหน่ง การอัปเดตพร้อมกันในลักษณะนี้
เป็นแบบอะตอมมิก นั่นคือการอัปเดตทั้งหมดสำเร็จหรือล้มเหลว
เพิ่มการติดต่อกลับที่เสร็จสมบูรณ์
หากต้องการทราบว่าข้อมูลของคุณถูกคอมมิตแล้วเมื่อใด คุณสามารถลงทะเบียน
Callback ที่เสร็จสมบูรณ์ ทั้ง set()
และ update()
แสดงผล Future
คุณสามารถแนบ Callback ที่สำเร็จและข้อผิดพลาดที่ถูกเรียกเมื่อการเขียนมี
กับฐานข้อมูลแล้ว และเมื่อการเรียกไม่สำเร็จ
FirebaseDatabase.instance
.ref('users/$userId/email')
.set(emailAddress)
.then((_) {
// Data saved successfully!
})
.catchError((error) {
// The write failed...
});
ลบข้อมูล
วิธีที่ง่ายที่สุดในการลบข้อมูลคือการเรียกใช้ remove()
ตามการอ้างอิง
ตำแหน่งของข้อมูลนั้น
คุณยังลบได้โดยการระบุค่า Null เป็นค่าสำหรับการดำเนินการเขียนอื่น
เช่น set()
หรือ update()
คุณสามารถใช้เทคนิคนี้กับ update()
เพื่อ
ลบผู้เผยแพร่โฆษณาย่อยหลายคนในการเรียก API ครั้งเดียว
บันทึกข้อมูลเป็นธุรกรรม
เมื่อทำงานกับข้อมูลที่อาจเสียหายจากการแก้ไขพร้อมกัน
เช่น ตัวนับที่เพิ่มขึ้น คุณสามารถใช้ธุรกรรมโดยส่ง
เครื่องจัดการธุรกรรมไปยัง runTransaction()
เครื่องจัดการธุรกรรมจะใช้
สถานะปัจจุบันของข้อมูลเป็นอาร์กิวเมนต์และ
จะแสดงสถานะใหม่ที่ต้องการเขียน หากลูกค้ารายอื่น
ลงในตำแหน่งก่อนที่จะเขียนค่าใหม่ได้สำเร็จ
จะมีการเรียกฟังก์ชันอัปเดตอีกครั้งด้วยค่าปัจจุบันใหม่ และการเขียนคือ
ลองอีกครั้งแล้ว
เช่น ในตัวอย่างแอปบล็อกโซเชียล คุณสามารถอนุญาตให้ผู้ใช้ติดดาว และยกเลิกการติดดาวโพสต์ และติดตามจำนวนดาวที่โพสต์ได้รับ ดังนี้
void toggleStar(String uid) async {
DatabaseReference postRef =
FirebaseDatabase.instance.ref("posts/foo-bar-123");
TransactionResult result = await postRef.runTransaction((Object? post) {
// Ensure a post at the ref exists.
if (post == null) {
return Transaction.abort();
}
Map<String, dynamic> _post = Map<String, dynamic>.from(post as Map);
if (_post["stars"] is Map && _post["stars"][uid] != null) {
_post["starCount"] = (_post["starCount"] ?? 1) - 1;
_post["stars"][uid] = null;
} else {
_post["starCount"] = (_post["starCount"] ?? 0) + 1;
if (!_post.containsKey("stars")) {
_post["stars"] = {};
}
_post["stars"][uid] = true;
}
// Return the new data.
return Transaction.success(_post);
});
}
โดยค่าเริ่มต้น ระบบจะเพิ่มเหตุการณ์ทุกครั้งที่ฟังก์ชันการอัปเดตธุรกรรมทำงาน
คุณจึงเรียกใช้ฟังก์ชันหลายครั้ง คุณอาจเห็นสถานะตรงกลาง
คุณสามารถตั้งค่า applyLocally
เป็น false
เพื่อระงับสถานะระดับกลางเหล่านี้ และ
แต่ให้รอจนกว่าธุรกรรมจะเสร็จสมบูรณ์ก่อนที่จะรวบรวมเหตุการณ์
await ref.runTransaction((Object? post) {
// ...
}, applyLocally: false);
ผลลัพธ์ของธุรกรรมคือ TransactionResult
ซึ่งมีข้อมูล
เช่น มีสัญญาผูกมัดธุรกรรมหรือไม่ และภาพรวมใหม่
DatabaseReference ref = FirebaseDatabase.instance.ref("posts/123");
TransactionResult result = await ref.runTransaction((Object? post) {
// ...
});
print('Committed? ${result.committed}'); // true / false
print('Snapshot? ${result.snapshot}'); // DataSnapshot
การยกเลิกธุรกรรม
หากคุณต้องการยกเลิกธุรกรรมอย่างปลอดภัย โปรดโทรหา Transaction.abort()
เพื่อ
โยน AbortTransactionException
:
TransactionResult result = await ref.runTransaction((Object? user) {
if (user !== null) {
return Transaction.abort();
}
// ...
});
print(result.committed); // false
ส่วนเพิ่มจากฝั่งเซิร์ฟเวอร์แบบอะตอม
ในกรณีการใช้งานด้านบน เราจะเขียนค่า 2 ค่าลงในฐานข้อมูลซึ่งได้แก่ รหัสของ ผู้ใช้ที่ติดดาว/ยกเลิกการติดดาวโพสต์ และจำนวนดาวที่เพิ่มขึ้น หากเรา ทราบอยู่แล้วว่าผู้ใช้ติดดาวโพสต์ เราสามารถใช้การเพิ่มระดับอะตอม แทนที่จะเป็นธุรกรรม
void addStar(uid, key) async {
Map<String, Object?> updates = {};
updates["posts/$key/stars/$uid"] = true;
updates["posts/$key/starCount"] = ServerValue.increment(1);
updates["user-posts/$key/stars/$uid"] = true;
updates["user-posts/$key/starCount"] = ServerValue.increment(1);
return FirebaseDatabase.instance.ref().update(updates);
}
โค้ดนี้ไม่ได้ใช้การดำเนินการธุรกรรม ดังนั้นจึงไม่ได้รับโดยอัตโนมัติ ให้เรียกใช้อีกครั้งหากมีการอัปเดตที่ขัดแย้งกัน แต่เนื่องจากการดำเนินการเพิ่ม จะเกิดขึ้นโดยตรงบนเซิร์ฟเวอร์ฐานข้อมูล จึงไม่มีโอกาสเกิดข้อขัดแย้ง
หากต้องการตรวจหาและปฏิเสธความขัดแย้งเฉพาะแอปพลิเคชัน เช่น ผู้ใช้ ติดดาวโพสต์ที่ติดดาวอยู่แล้ว คุณควรเขียนแบบกำหนดเอง กฎความปลอดภัยสำหรับกรณีการใช้งานนั้น
ใช้งานข้อมูลแบบออฟไลน์
หากลูกค้าสูญเสียการเชื่อมต่อเครือข่าย แอปจะยังคงทำงานต่อไป อย่างถูกต้อง
ลูกค้าทุกรายที่เชื่อมต่อกับฐานข้อมูล Firebase จะมีเวอร์ชันภายในของตนเอง ของข้อมูลที่ใช้งานอยู่ เมื่อเขียนข้อมูล ระบบจะเขียนลงในเวอร์ชันนี้ในเครื่อง ก่อน จากนั้นไคลเอ็นต์ Firebase จะซิงค์ข้อมูลนั้นกับฐานข้อมูลระยะไกล เซิร์ฟเวอร์และไคลเอ็นต์อื่นๆ อย่าง "สุดความสามารถ" พื้นฐาน
ด้วยเหตุนี้ การเขียนทั้งหมดไปยังฐานข้อมูลจะทริกเกอร์เหตุการณ์ในระบบทันที ข้อมูลใดก็ตามที่เขียนไปยังเซิร์ฟเวอร์ ซึ่งหมายความว่าแอปของคุณจะยัง ตอบสนองตามอุปกรณ์โดยไม่คำนึงถึงเวลาในการตอบสนองหรือการเชื่อมต่อของเครือข่าย
เมื่อเชื่อมต่ออินเทอร์เน็ตอีกครั้งแล้ว แอปจะได้รับชุด เหตุการณ์เพื่อให้ไคลเอ็นต์ซิงค์กับสถานะเซิร์ฟเวอร์ปัจจุบันโดยไม่ต้อง เขียนโค้ดที่กำหนดเอง
เราจะพูดคุยเพิ่มเติมเกี่ยวกับพฤติกรรมออฟไลน์ใน ดูข้อมูลเพิ่มเติมเกี่ยวกับความสามารถในการทำงานแบบออนไลน์และออฟไลน์