خواندن و نوشتن داده ها

(اختیاری) نمونه اولیه و آزمایش با Firebase Emulator Suite

قبل از صحبت در مورد نحوه خواندن و نوشتن برنامه شما از پایگاه داده Realtime و نوشتن آن، بیایید مجموعه ای از ابزارها را معرفی کنیم که می توانید از آنها برای نمونه سازی و آزمایش عملکرد Realtime Database استفاده کنید: Firebase Emulator Suite. اگر در حال آزمایش مدل های مختلف داده، بهینه سازی قوانین امنیتی خود هستید یا برای یافتن مقرون به صرفه ترین راه برای تعامل با back-end تلاش می کنید، اینکه بتوانید به صورت محلی بدون استقرار سرویس های زنده کار کنید می تواند ایده خوبی باشد.

شبیه ساز پایگاه داده بیدرنگ بخشی از مجموعه شبیه ساز است که به برنامه شما امکان می دهد با محتوای پایگاه داده شبیه سازی شده و پیکربندی شما و همچنین به صورت اختیاری منابع پروژه شبیه سازی شده شما (توابع، سایر پایگاه های داده و قوانین امنیتی) تعامل داشته باشد.emulator_suite_short.

استفاده از شبیه ساز Realtime Database فقط شامل چند مرحله است:

  1. افزودن یک خط کد به پیکربندی آزمایشی برنامه برای اتصال به شبیه ساز.
  2. از ریشه دایرکتوری پروژه محلی خود، firebase emulators:start اجرا کنید.
  3. برقراری تماس از کد نمونه اولیه برنامه خود با استفاده از یک SDK پلتفرم پایگاه داده بیدرنگ به طور معمول، یا با استفاده از Realtime Database REST API.

یک بررسی دقیق شامل پایگاه داده بیدرنگ و توابع ابری در دسترس است. همچنین باید به معرفی Emulator Suite نگاهی بیندازید.

یک مرجع پایگاه داده دریافت کنید

برای خواندن یا نوشتن داده ها از پایگاه داده، به یک نمونه از 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 s استفاده کنید.

می‌توانید از DatabaseEvent برای خواندن داده‌ها در یک مسیر مشخص استفاده کنید، همانطور که در زمان رویداد وجود دارد. این رویداد یک بار هنگام پیوستن شنونده و بار دیگر هر بار که داده‌ها، از جمله کودکان، تغییر می‌کنند، فعال می‌شود. این رویداد دارای یک ویژگی snapshot است که شامل تمام داده‌های آن مکان، از جمله داده‌های فرزند است. اگر داده‌ای وجود نداشته باشد، ویژگی exists عکس فوری false و خاصیت value آن null خواهد بود.

مثال زیر یک برنامه وبلاگ نویسی اجتماعی را نشان می دهد که جزئیات یک پست را از پایگاه داده بازیابی می کند:

DatabaseReference starCountRef =
        FirebaseDatabase.instance.ref('posts/$postId/starCount');
starCountRef.onValue.listen((DatabaseEvent event) {
    final data = event.snapshot.value;
    updateStarCount(data);
});

شنونده یک 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() می تواند استفاده از پهنای باند را افزایش دهد و منجر به از دست دادن عملکرد شود، که می توان با استفاده از یک شنونده بلادرنگ همانطور که در بالا نشان داده شده است، جلوگیری کرد.

خواندن داده ها یکبار با یکبار()

در برخی موارد ممکن است بخواهید به جای بررسی مقدار به روز شده در سرور، مقدار از حافظه نهان محلی بلافاصله برگردانده شود. در این موارد می‌توانید 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 بازیابی می کند. سپس می‌توان از کلید برای ایجاد ورودی دوم در پست‌های کاربر در /user-posts/$userid/$postid استفاده کرد.

با استفاده از این مسیرها، می‌توانید به‌روزرسانی‌های همزمان چندین مکان در درخت JSON را با یک فراخوانی برای update() انجام دهید، مانند اینکه چگونه این مثال پست جدید را در هر دو مکان ایجاد می‌کند. به‌روزرسانی‌های همزمان ساخته شده به این روش اتمی هستند: یا همه به‌روزرسانی‌ها موفق می‌شوند یا همه به‌روزرسانی‌ها با شکست مواجه می‌شوند.

یک پاسخ تماس تکمیلی اضافه کنید

اگر می‌خواهید بدانید چه زمانی داده‌های شما متعهد شده‌اند، می‌توانید تماس‌های تکمیلی را ثبت کنید. هر دو 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

افزایش سمت سرور اتمی

در مورد استفاده بالا، ما دو مقدار را برای پایگاه داده می نویسیم: شناسه کاربری که پست را ستاره/برداشته ستاره می کند، و تعداد ستاره افزایش یافته. اگر از قبل می‌دانیم که کاربر پست را ستاره‌دار می‌کند، می‌توانیم به جای تراکنش از عملیات افزایش اتمی استفاده کنیم.

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 آن داده ها را با سرورهای پایگاه داده راه دور و با سایر کلاینت ها بر اساس "بهترین تلاش" همگام سازی می کند.

در نتیجه، همه نوشته‌ها در پایگاه داده، بلافاصله رویدادهای محلی را راه‌اندازی می‌کنند، قبل از اینکه داده‌ای روی سرور نوشته شود. این بدان معناست که برنامه شما بدون توجه به تأخیر شبکه یا اتصال، پاسخگو باقی می ماند.

پس از برقراری مجدد اتصال، برنامه شما مجموعه مناسبی از رویدادها را دریافت می کند تا کلاینت بدون نیاز به نوشتن کد سفارشی با وضعیت سرور فعلی همگام شود.

در مورد قابلیت های آنلاین و آفلاین بیشتر بدانید در مورد رفتار آفلاین بیشتر صحبت خواهیم کرد.

مراحل بعدی