อ่านและเขียนข้อมูล

(ไม่บังคับ) สร้างต้นแบบและทดสอบด้วย Firebase Emulator Suite

ก่อนที่จะพูดถึงวิธีที่แอปอ่านและเขียนไปยัง Realtime Database เราจะมาแนะนำชุดเครื่องมือที่คุณสามารถใช้สร้างต้นแบบและทดสอบ Realtime Database ได้ ฟังก์ชัน: Firebase Emulator Suite หากคุณกำลังลองใช้ข้อมูลอื่น โมเดล, การเพิ่มประสิทธิภาพของกฎความปลอดภัย หรือการทำงานเพื่อค้นหา วิธีที่คุ้มค่าในการโต้ตอบกับระบบหลังบ้าน ทำให้สามารถทำงานในองค์กรได้ โดยไม่ต้องใช้บริการแบบสดอาจเป็นความคิดที่ดี

โปรแกรมจำลอง Realtime Database เป็นส่วนหนึ่งของชุดโปรแกรมจำลอง ทำให้แอปของคุณสามารถโต้ตอบกับเนื้อหาและการกำหนดค่าฐานข้อมูลที่เลียนแบบ รวมทั้งทรัพยากรโครงการจำลอง (ฟังก์ชัน ฐานข้อมูลอื่นๆ และกฎความปลอดภัย).emulator_suite_short

การใช้โปรแกรมจำลอง Realtime Database ประกอบด้วยไม่กี่ขั้นตอนดังนี้

  1. การเพิ่มบรรทัดโค้ดลงในการกำหนดค่าการทดสอบของแอปเพื่อเชื่อมต่อกับโปรแกรมจำลอง
  2. จากรูทของไดเรกทอรีโปรเจ็กต์ในเครื่องโดยเรียกใช้ firebase emulators:start
  3. การโทรจากโค้ดต้นแบบของแอปโดยใช้แพลตฟอร์ม 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 จะซิงค์ข้อมูลนั้นกับฐานข้อมูลระยะไกล เซิร์ฟเวอร์และไคลเอ็นต์อื่นๆ อย่าง "สุดความสามารถ" พื้นฐาน

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

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

เราจะพูดคุยเพิ่มเติมเกี่ยวกับพฤติกรรมออฟไลน์ใน ดูข้อมูลเพิ่มเติมเกี่ยวกับความสามารถในการทำงานแบบออนไลน์และออฟไลน์

ขั้นตอนถัดไป