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

(ไม่บังคับ) สร้างต้นแบบและทดสอบด้วย 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 จะซิงค์ข้อมูลนั้นกับฐานข้อมูลระยะไกล เซิร์ฟเวอร์และไคลเอ็นต์อื่นๆ อย่าง "สุดความสามารถ" พื้นฐาน

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

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

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

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