التطوير المحلي لتطبيقات Flutter باستخدام حزمة محاكي Firebase

1. قبل البدء

في هذا الدرس التطبيقي، ستتعلّم كيفية استخدام "مجموعة محاكي Firebase" مع Flutter أثناء التطوير المحلي. ستتعرّف على كيفية استخدام المصادقة باستخدام عنوان البريد الإلكتروني وكلمة المرور من خلال Emulator Suite، وكيفية قراءة البيانات وكتابتها في محاكي Firestore. أخيرًا، ستعمل على استيراد البيانات وتصديرها من المحاكيات، وذلك للعمل مع البيانات الزائفة نفسها في كل مرة تعود فيها إلى مرحلة التطوير.

المتطلبات الأساسية

يفترض هذا الدرس التطبيقي حول الترميز أنّ لديك بعض الخبرة في استخدام Flutter. إذا لم يكن الأمر كذلك، ننصحك بالتعرّف أولاً على الأساسيات. الروابط التالية مفيدة:

يجب أن يكون لديك أيضًا بعض الخبرة في Firebase، ولكن لا بأس إذا لم يسبق لك إضافة Firebase إلى مشروع Flutter. إذا لم تكن على دراية بوحدة تحكّم Firebase، أو إذا كنت مستخدمًا جديدًا تمامًا لبرنامج Firebase، يُرجى الاطّلاع أولاً على الروابط التالية:

ما ستنشئه

يرشدك هذا الدرس التطبيقي حول الترميز إلى كيفية إنشاء تطبيق بسيط لتدوين اليوميات. سيتضمّن التطبيق شاشة تسجيل دخول وشاشة تتيح لك قراءة إدخالات دفتر اليومية السابقة وإنشاء إدخالات جديدة.

cd5c4753bbee8af.png 8cb4d21f656540bf.png

أهداف الدورة التعليمية

ستتعرّف على كيفية بدء استخدام Firebase وكيفية دمج "مجموعة أدوات محاكي Firebase" واستخدامها في سير عمل تطوير تطبيقات Flutter. سيتم تناول مواضيع Firebase التالية:

يُرجى العِلم أنّ هذه المواضيع يتم تناولها بالقدر اللازم لتغطية "مجموعة محاكي Firebase". يركّز هذا الدرس العملي على إضافة مشروع Firebase إلى تطبيق Flutter، والتطوير باستخدام "مجموعة أدوات محاكي Firebase". لن نتناول مناقشات تفصيلية حول Firebase Authentication أو Firestore. إذا لم تكن معتادًا على هذه المواضيع، ننصحك بالبدء باستخدام Getting to Know Firebase for Flutter codelab.

المتطلبات

  • معرفة عملية بلغة Flutter وتثبيت حزمة تطوير البرامج (SDK)
  • محرّرات النصوص Intellij JetBrains أو VS Code
  • متصفّح Google Chrome (أو هدف التطوير المفضّل الآخر لـ Flutter) ستفترض بعض أوامر الوحدة الطرفية في هذا الدرس التطبيقي العملي أنّك تشغّل تطبيقك على Chrome)

2. إنشاء مشروع Firebase وإعداده

أول مهمة عليك إكمالها هي إنشاء مشروع Firebase في وحدة تحكّم Firebase على الويب. سيركّز الجزء الأكبر من هذا الدرس التطبيقي حول الترميز على "مجموعة أدوات المحاكاة" التي تستخدم واجهة مستخدم تعمل محليًا، ولكن عليك إعداد مشروع Firebase كامل أولاً.

إنشاء مشروع Firebase

  1. سجِّل الدخول إلى وحدة تحكّم Firebase باستخدام حسابك على Google.
  2. انقر على الزر لإنشاء مشروع جديد، ثم أدخِل اسم المشروع (على سبيل المثال، Firebase-Flutter-Codelab).
  3. انقر على متابعة.
  4. إذا طُلب منك ذلك، راجِع بنود Firebase واقبلها، ثم انقر على متابعة.
  5. (اختياري) فعِّل ميزة "المساعدة المستندة إلى الذكاء الاصطناعي" في وحدة تحكّم Firebase (المعروفة باسم "Gemini في Firebase").
  6. في هذا الدرس العملي، لا تحتاج إلى "إحصاءات Google"، لذا أوقِف خيار "إحصاءات Google".
  7. انقر على إنشاء مشروع، وانتظِر إلى أن يتم توفير مشروعك، ثم انقر على متابعة.

لمزيد من المعلومات عن مشاريع Firebase، اطّلِع على مقالة التعرّف على مشاريع Firebase.

إعداد منتجات Firebase

يستخدم التطبيق الذي تعمل على إنشائه منتجَين من منتجات Firebase المتاحة لتطبيقات Flutter:

  • مصادقة Firebase للسماح للمستخدمين بتسجيل الدخول إلى تطبيقك
  • ‫Cloud Firestore لحفظ البيانات المنظَّمة على السحابة الإلكترونية وتلقّي إشعار فوري عند تغيُّر البيانات

يتطلّب هذان المنتجان إعدادًا خاصًا أو يجب تفعيلهما باستخدام وحدة تحكّم Firebase.

تفعيل Cloud Firestore

يستخدم تطبيق Flutter خدمة Cloud Firestore لحفظ إدخالات دفتر اليوميات.

فعِّل Cloud Firestore:

  1. في قسم إنشاء في وحدة تحكّم Firebase، انقر على Cloud Firestore.
  2. انقر على إنشاء قاعدة بيانات. 99e8429832d23fa3.png
  3. انقر على الخيار بدء التشغيل في وضع الاختبار. اقرأ بيان إخلاء المسؤولية حول قواعد الأمان. يضمن وضع الاختبار إمكانية الكتابة بحرية في قاعدة البيانات أثناء التطوير. انقر على التالي. 6be00e26c72ea032.png
  4. اختَر الموقع الجغرافي لقاعدة البيانات (يمكنك استخدام الموقع التلقائي). يُرجى العلم أنّه لا يمكن تغيير هذا الموقع الجغرافي لاحقًا. 278656eefcfb0216.png
  5. انقر على تفعيل.

3- إعداد تطبيق Flutter

عليك تنزيل رمز البداية وتثبيت واجهة سطر الأوامر (CLI) في Firebase قبل أن نبدأ.

الحصول على الرمز الأوّلي

استنسِخ مستودع GitHub من سطر الأوامر:

git clone https://github.com/flutter/codelabs.git flutter-codelabs

بدلاً من ذلك، إذا كانت أداة واجهة سطر الأوامر في GitHub مثبّتة:

gh repo clone flutter/codelabs flutter-codelabs

يجب استنساخ نموذج الرمز في الدليل flutter-codelabs الذي يحتوي على رمز لمجموعة من دروس البرمجة. يتوفّر الرمز البرمجي لهذا الدرس التطبيقي حول الترميز في flutter-codelabs/firebase-emulator-suite.

يتألف هيكل الدليل ضمن flutter-codelabs/firebase-emulator-suite من مشروعَين في Flutter. أحدها يُسمى complete، ويمكنك الرجوع إليه إذا أردت الانتقال إلى جزء معيّن أو الرجوع إلى التعليمات البرمجية الخاصة بك. اسم المشروع الآخر هو start.

يظهر الرمز الذي تريد البدء به في الدليل flutter-codelabs/firebase-emulator-suite/start. افتح هذا الدليل أو استورِده إلى بيئة التطوير المتكاملة (IDE) المفضّلة لديك.

cd flutter-codelabs/firebase-emulator-suite/start

تثبيت Firebase CLI

توفّر واجهة سطر الأوامر (CLI) في Firebase أدوات لإدارة مشاريعك على Firebase. يجب تثبيت واجهة سطر الأوامر لاستخدام Emulator Suite.

تتوفّر مجموعة متنوعة من الطرق لتثبيت واجهة سطر الأوامر. أسهل طريقة، إذا كنت تستخدم نظام التشغيل MacOS أو Linux، هي تشغيل الأمر التالي من الوحدة الطرفية:

curl -sL https://firebase.tools | bash

بعد تثبيت واجهة سطر الأوامر، يجب إثبات ملكية حسابك على Firebase.

  1. سجِّل الدخول إلى Firebase باستخدام حسابك على Google عن طريق تنفيذ الأمر التالي:
firebase login
  1. يربط هذا الأمر جهازك المحلي بخدمة Firebase ويمنحك إذن الوصول إلى مشاريعك على Firebase.
  1. اختبِر تثبيت واجهة سطر الأوامر بشكلٍ صحيح وإمكانية وصولها إلى حسابك من خلال إدراج مشاريعك على Firebase. نفِّذ الأمر التالي:
firebase projects:list
  1. يجب أن تكون القائمة المعروضة هي نفسها مشاريع Firebase المُدرَجة في وحدة تحكّم Firebase. يجب أن يظهر لك firebase-flutter-codelab على الأقل.

تثبيت FlutterFire CLI

تم إنشاء واجهة سطر الأوامر FlutterFire استنادًا إلى واجهة سطر الأوامر Firebase، وهي تسهّل دمج مشروع Firebase مع تطبيق Flutter.

أولاً، ثبِّت واجهة سطر الأوامر:

dart pub global activate flutterfire_cli

تأكَّد من تثبيت واجهة سطر الأوامر. نفِّذ الأمر التالي ضِمن دليل مشروع Flutter وتأكَّد من أنّ واجهة سطر الأوامر تعرض قائمة المساعدة.

flutterfire --help

استخدام Firebase CLI وFlutterFire CLI لإضافة مشروع Firebase إلى تطبيق Flutter

بعد تثبيت واجهتَي سطر الأوامر، يمكنك إعداد منتجات Firebase الفردية (مثل Firestore) وتنزيل المحاكيات وإضافة Firebase إلى تطبيق Flutter باستخدام بضعة أوامر فقط في نافذة الأوامر.

أولاً، أكمِل عملية إعداد Firebase من خلال تنفيذ ما يلي:

firebase init

سيوجّهك هذا الأمر خلال سلسلة من الأسئلة اللازمة لإعداد مشروعك. تعرض لقطات الشاشة التالية مسار العمل:

  1. عندما يُطلب منك اختيار الميزات، اختَر "Firestore" و "المحاكيات". (لا يتوفّر خيار "المصادقة"، لأنّه لا يستخدم إعدادات قابلة للتعديل من ملفات مشروع Flutter). fe6401d769be8f53.png
  2. بعد ذلك، اختَر "استخدام مشروع حالي" عند مطالبتك بذلك.

f11dcab439e6ac1e.png

  1. الآن، اختَر المشروع الذي أنشأته في خطوة سابقة: flutter-firebase-codelab.

3bdc0c6934991c25.png

  1. بعد ذلك، سيُطلب منك الإجابة عن سلسلة من الأسئلة حول تسمية الملفات التي سيتم إنشاؤها. أقترح الضغط على مفتاح Enter لكل سؤال لاختيار الإعداد التلقائي. 9bfa2d507e199c59.png
  2. أخيرًا، عليك ضبط المحاكيات. اختَر Firestore وAuthentication من القائمة، ثم اضغط على "Enter" لكل سؤال حول المنافذ المحدّدة التي سيتم استخدامها لكل محاكي. يجب اختيار الإعداد التلقائي "نعم" عند سؤالك عمّا إذا كنت تريد استخدام واجهة مستخدم المحاكي.

في نهاية العملية، من المفترض أن يظهر لك ناتج يشبه لقطة الشاشة التالية.

ملاحظة مهمة: قد يختلف الناتج قليلاً عن الناتج الذي يظهر في لقطة الشاشة أدناه، لأنّ السؤال الأخير سيكون "لا" تلقائيًا إذا كنت قد نزّلت المحاكيات.

8544e41037637b07.png

ضبط FlutterFire

بعد ذلك، يمكنك استخدام FlutterFire لإنشاء رمز Dart البرمجي اللازم لاستخدام Firebase في تطبيق Flutter.

flutterfire configure

عند تنفيذ هذا الأمر، سيُطلب منك اختيار مشروع Firebase الذي تريد استخدامه والمنصات التي تريد إعدادها. في هذا الدرس العملي، تستخدِم الأمثلة Flutter Web، ولكن يمكنك إعداد مشروعك على Firebase لاستخدام جميع الخيارات.

تعرض لقطات الشاشة التالية الطلبات التي عليك الإجابة عنها.

619b7aca6dc15472.png 301c9534f594f472.png

تعرض لقطة الشاشة هذه الناتج في نهاية العملية. إذا كنت على دراية بمنصة Firebase، ستلاحظ أنّه لم يكن عليك إنشاء تطبيقات في وحدة التحكّم، وأنّ واجهة سطر الأوامر FlutterFire CLI قد نفّذت ذلك نيابةً عنك.

12199a85ade30459.png

إضافة حِزم Firebase إلى تطبيق Flutter

تتمثّل خطوة الإعداد الأخيرة في إضافة حِزم Firebase ذات الصلة إلى مشروع Flutter. في نافذة الوحدة الطرفية، تأكَّد من أنّك في جذر مشروع Flutter في flutter-codelabs/firebase-emulator-suite/start. بعد ذلك، شغِّل الأوامر الثلاثة التالية:

flutter pub add firebase_core
flutter pub add firebase_auth
flutter pub add cloud_firestore

هذه هي الحِزم الوحيدة التي ستستخدمها في هذا التطبيق.

4. تفعيل محاكيات Firebase

حتى الآن، تم إعداد تطبيق Flutter ومشروعك على Firebase ليتمكّنا من استخدام المحاكيات، ولكن لا يزال عليك توجيه رمز Flutter لإعادة توجيه طلبات Firebase الصادرة إلى المنافذ المحلية.

أولاً، أضِف رمز إعداد Firebase ورمز إعداد المحاكي إلى الدالة main في main.dart..

main.dart

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

import 'app_state.dart';
import 'firebase_options.dart';
import 'logged_in_view.dart';
import 'logged_out_view.dart';


void main() async {
 WidgetsFlutterBinding.ensureInitialized();
 await Firebase.initializeApp(
   options: DefaultFirebaseOptions.currentPlatform,
 );

 if (kDebugMode) {
   try {
     FirebaseFirestore.instance.useFirestoreEmulator('localhost', 8080);
     await FirebaseAuth.instance.useAuthEmulator('localhost', 9099);
   } catch (e) {
     // ignore: avoid_print
     print(e);
   }
 }

 runApp(MyApp());
}

تؤدي الأسطر القليلة الأولى من الرمز البرمجي إلى تهيئة Firebase. في معظم الحالات، إذا كنت تستخدم Firebase في تطبيق Flutter، عليك البدء باستدعاء WidgetsFlutterBinding.ensureInitialized وFirebase.initializeApp.

بعد ذلك، يطلب الرمز الذي يبدأ بالسطر if (kDebugMode) من تطبيقك استهداف المحاكيات بدلاً من مشروع Firebase للإنتاج. تضمن kDebugMode عدم استهداف المحاكيات إلا إذا كنت في بيئة تطوير. بما أنّ kDebugMode هي قيمة ثابتة، يعرف مترجم Dart كيفية إزالة كتلة الرمز هذه بالكامل في وضع الإصدار.

بدء تشغيل المحاكيات

يجب بدء المحاكيات قبل بدء تطبيق Flutter. ابدأ المحاكيات أولاً من خلال تنفيذ ما يلي في نافذة الأوامر:

firebase emulators:start

يؤدي هذا الأمر إلى تشغيل المحاكيات، وعرض منافذ المضيف المحلي التي يمكننا التفاعل معها. عند تنفيذ هذا الأمر، من المفترض أن تظهر لك نتيجة مشابهة لما يلي:

bb7181eb70829606.png

توضّح لك هذه النتيجة المحاكيات التي يتم تشغيلها، والمكان الذي يمكنك الانتقال إليه للاطّلاع على المحاكيات. أولاً، اطّلِع على واجهة المستخدم لمحاكي Android على localhost:4000.

11563f4c7216de81.png

هذه هي الصفحة الرئيسية لواجهة المستخدم لمحاكي Firebase المحلي. تعرض هذه القائمة جميع المحاكيات المتاحة، ويتم تصنيف كل محاكي بحسب حالته، أي مفعَّل أو غير مفعَّل.

5- محاكي Firebase Auth

المحاكي الأول الذي ستستخدمه هو محاكي المصادقة. ابدأ باستخدام محاكي Auth من خلال النقر على "الانتقال إلى المحاكي" في بطاقة "المصادقة" في واجهة المستخدم، وستظهر لك صفحة مشابهة لما يلي:

3c1bfded40733189.png

تتشابه هذه الصفحة مع صفحة وحدة تحكّم الويب في Auth. يحتوي على جدول يسرد المستخدمين مثل وحدة التحكّم على الإنترنت، ويتيح لك إضافة المستخدمين يدويًا. أحد الاختلافات الكبيرة هنا هو أنّ خيار طريقة المصادقة الوحيد المتاح على المحاكيات هو عبر البريد الإلكتروني وكلمة المرور. وهذا يكفي للتطوير على الجهاز.

بعد ذلك، ستنتقل إلى عملية إضافة مستخدم إلى محاكي Firebase Auth، ثم تسجيل دخول هذا المستخدم من خلال واجهة مستخدم Flutter.

إضافة مستخدم

انقر على الزرّ "إضافة مستخدم"، واملأ النموذج بالمعلومات التالية:

  • الاسم المعروض: Dash
  • البريد الإلكتروني: dash@email.com
  • كلمة المرور: dashword

أرسِل النموذج، وسيظهر لك الجدول الآن ويتضمّن مستخدمًا. يمكنك الآن تعديل الرمز لتسجيل الدخول باستخدام هذا المستخدم.

logged_out_view.dart

الرمز الوحيد في الأداة LoggedOutView الذي يجب تعديله هو الرمز الموجود في دالة معاودة الاتصال التي يتم تفعيلها عندما يضغط المستخدم على زر تسجيل الدخول. عدِّل الرمز ليصبح على النحو التالي:

class LoggedOutView extends StatelessWidget {
 final AppState state;
 const LoggedOutView({super.key, required this.state});
 @override
 Widget build(BuildContext context) {
   return Scaffold(
     appBar: AppBar(
       title: const Text('Firebase Emulator Suite Codelab'),
     ),
     body: Center(
       child: Column(
         mainAxisAlignment: MainAxisAlignment.center,
         children: [
          Text(
           'Please log in',
            style: Theme.of(context).textTheme.displaySmall,
          ),
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: ElevatedButton(
             onPressed: () async {
              await state.logIn('dash@email.com', 'dashword').then((_) {
                if (state.user != null) {
                 context.go('/');
                }
              });
              },
              child: const Text('Log In'),
          ),
        ),
      ],
    ),
   ),
  );
 }
}

يستبدل الرمز المعدَّل السلاسل TODO بعنوان البريد الإلكتروني وكلمة المرور اللذين أنشأتهما في محاكي المصادقة. في السطر التالي، تم استبدال السطر if(true) برمز يتحقّق مما إذا كانت قيمة state.user فارغة. يوضّح الرمز البرمجي في AppClass هذا الأمر بشكل أكبر.

app_state.dart

يجب تعديل جزأين من الرمز في AppState. أولاً، امنح العضو في الفئة AppState.user النوع User من الحزمة firebase_auth، بدلاً من النوع Object.

ثانيًا، املأ طريقة AppState.login كما هو موضّح أدناه:

import 'dart:async';

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';

import 'entry.dart';

class AppState {
 AppState() {
   _entriesStreamController = StreamController.broadcast(onListen: () {
     _entriesStreamController.add([
       Entry(
         date: '10/09/2022',
         text: lorem,
         title: '[Example] My Journal Entry',
       )
     ]);
   });
 }

 User? user; // <-- changed variable type
 Stream<List<Entry>> get entries => _entriesStreamController.stream;
 late final StreamController<List<Entry>> _entriesStreamController;

 Future<void> logIn(String email, String password) async {
   final credential = await FirebaseAuth.instance
       .signInWithEmailAndPassword(email: email, password: password);
   if (credential.user != null) {
     user = credential.user!;
     _listenForEntries();
   } else {
     print('no user!');
   }
 } 
 // ...
}

أصبح تعريف النوع للمستخدم الآن User?. يأتي الصف User من Firebase Auth، ويوفّر المعلومات اللازمة، مثل User.displayName، التي سنتناولها بعد قليل.

هذا هو الرمز الأساسي اللازم لتسجيل دخول المستخدم باستخدام عنوان بريد إلكتروني وكلمة مرور في Firebase Auth. يتم إجراء طلب إلى FirebaseAuth لتسجيل الدخول، ما يؤدي إلى عرض عنصر Future<UserCredential>. عند اكتمال المستقبل، يتحقّق هذا الرمز مما إذا كان هناك User مرفقًا بـ UserCredential. إذا كان هناك مستخدم في عنصر بيانات الاعتماد، يعني ذلك أنّ المستخدم سجّل الدخول بنجاح، ويمكن ضبط السمة AppState.user. إذا لم يكن كذلك، يعني ذلك أنّه حدث خطأ، وسيتم طباعته.

يُرجى العِلم أنّ سطر الرمز الوحيد في هذه الطريقة الذي يخصّ هذا التطبيق (بدلاً من رمز FirebaseAuth العام) هو استدعاء الطريقة _listenForEntries، والتي سيتم تناولها في الخطوة التالية.

TODO: Action Icon – Reload your app, and then press the Login button when it renders. يؤدي ذلك إلى انتقال التطبيق إلى صفحة مكتوب فيها "مرحبًا بك مجددًا، [اسم الشخص]" في أعلى الصفحة. يجب أن تعمل المصادقة، لأنّها سمحت لك بالانتقال إلى هذه الصفحة، ولكن يجب إجراء تعديل بسيط على logged_in_view.dart لعرض الاسم الفعلي للمستخدم.

logged_in_view.dart

غيِّر السطر الأول في طريقة LoggedInView.build:

class LoggedInView extends StatelessWidget {
 final AppState state;
 LoggedInView({super.key, required this.state});

 final PageController _controller = PageController(initialPage: 1);

 @override
 Widget build(BuildContext context) {
   final name = state.user!.displayName ?? 'No Name';

   return Scaffold(
 // ...

الآن، يحصل هذا السطر على displayName من السمة User في العنصر AppState. تم ضبط displayName هذا في المحاكي عند تحديد المستخدم الأول. من المفترض أن يعرض تطبيقك الآن الرسالة "مرحبًا مجددًا، Dash" عند تسجيل الدخول، بدلاً من TODO.

6. قراءة البيانات وكتابتها في محاكي Firestore

أولاً، جرِّب محاكي Firestore. في الصفحة الرئيسية لواجهة مستخدم المحاكي (localhost:4000)، انقر على "الانتقال إلى المحاكي" في بطاقة Firestore. يجب أن يظهر على النحو التالي:

المحاكي:

791fce7dc137910a.png

وحدة تحكّم Firebase:

e0dde9aea34af050.png

إذا كانت لديك أي خبرة في استخدام Firestore، ستلاحظ أنّ هذه الصفحة تشبه صفحة Firestore في "وحدة تحكّم Firebase". ومع ذلك، هناك بعض الاختلافات الجديرة بالذكر.

  1. يمكنك محو جميع البيانات بنقرة زر واحدة. قد يكون ذلك خطيرًا مع بيانات الإنتاج، ولكنّه مفيد للتكرار السريع. إذا كنت تعمل على مشروع جديد وتغيّر نموذج البيانات، يمكنك محوه بسهولة.
  2. تتضمّن علامة التبويب "الطلبات". تتيح لك علامة التبويب هذه مشاهدة الطلبات الواردة إلى هذا المحاكي. سأناقش علامة التبويب هذه بمزيد من التفصيل بعد قليل.
  3. لا تتوفّر علامات تبويب للقواعد أو الفهارس أو الاستخدام. تتوفّر أداة (سيتم تناولها في القسم التالي) تساعد في كتابة قواعد الأمان، ولكن لا يمكنك ضبط قواعد الأمان للمحاكي المحلي.

باختصار، يوفّر هذا الإصدار من Firestore أدوات أكثر فائدة أثناء التطوير، ويزيل الأدوات المطلوبة في مرحلة الإنتاج.

الكتابة إلى Firestore

قبل مناقشة علامة التبويب "الطلبات" في المحاكي، عليك أولاً تقديم طلب. ويتطلّب ذلك إجراء تعديلات على الرمز. ابدأ بربط النموذج في التطبيق لكتابة يوميات جديدة Entry في Firestore.

في ما يلي الخطوات العامة لإرسال Entry:

  1. ملأ المستخدم النموذج ونقر على الزر Submit
  2. تطلب واجهة المستخدم AppState.writeEntryToFirebase
  3. تضيف AppState.writeEntryToFirebase إدخالاً إلى Firebase

لا يلزم تغيير أي من الرموز البرمجية المستخدَمة في الخطوتين 1 أو 2. سيتم إضافة الرمز الوحيد المطلوب للخطوة 3 في فئة AppState. أجرِ التغيير التالي على AppState.writeEntryToFirebase.

app_state.dart

import 'dart:async';

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';

import 'entry.dart';

class AppState {
 AppState() {
   _entriesStreamController = StreamController.broadcast(onListen: () {
     _entriesStreamController.add([
       Entry(
         date: '10/09/2022',
         text: lorem,
         title: '[Example] My Journal Entry',
       )
     ]);
   });
 }

 User? user;
 Stream<List<Entry>> get entries => _entriesStreamController.stream;
 late final StreamController<List<Entry>> _entriesStreamController;

 Future<void> logIn(String email, String password) async {
   final credential = await FirebaseAuth.instance
       .signInWithEmailAndPassword(email: email, password: password);
   if (credential.user != null) {
     user = credential.user!;
     _listenForEntries();
   } else {
     print('no user!');
   }
 }

 void writeEntryToFirebase(Entry entry) {
   FirebaseFirestore.instance.collection('Entries').add(<String, String>{
     'title': entry.title,
     'date': entry.date.toString(),
     'text': entry.text,
   });
 }
 // ...
}

يحصل الرمز البرمجي في طريقة writeEntryToFirebase على مرجع إلى المجموعة المسماة "Entries" في Firestore. ثم يضيف إدخالاً جديدًا يجب أن يكون من النوع Map<String, String>.

في هذه الحالة، لم تكن مجموعة "الإدخالات" متوفرة في Firestore، لذا أنشأتها Firestore.

بعد إضافة هذا الرمز، أعِد تحميل تطبيقك أو أعِد تشغيله، ثم سجِّل الدخول وانتقِل إلى طريقة العرض EntryForm. يمكنك ملء النموذج بأي Strings تريده. (سيقبل حقل "التاريخ" أي سلسلة، لأنّه تم تبسيطه في هذا الدرس العملي. لا يتضمّن هذا النوع من الحقول عملية تحقّق قوية أو يهتمّ بعناصر DateTime بأي شكل من الأشكال).

اضغط على "إرسال" في النموذج. لن يحدث أي شيء في التطبيق، ولكن يمكنك الاطّلاع على الإدخال الجديد في واجهة مستخدم المحاكي.

علامة التبويب "الطلبات" في محاكي Firestore

في واجهة المستخدم، انتقِل إلى محاكي Firestore، واطّلِع على علامة التبويب "البيانات". من المفترض أن تظهر الآن مجموعة في جذر قاعدة البيانات باسم "الإدخالات". يجب أن يتضمّن هذا المستند المعلومات نفسها التي أدخلتها في النموذج.

a978fb34fb8a83da.png

يؤكّد ذلك أنّ AppState.writeEntryToFirestore قد عمل، ويمكنك الآن استكشاف الطلب بشكل أكبر في علامة التبويب "الطلبات". انقر على علامة التبويب هذه الآن.

طلبات محاكي Firestore

من المفترض أن تظهر لك هنا قائمة مشابهة لما يلي:

f0b37f0341639035.png

يمكنك النقر على أي من عناصر القائمة هذه والاطّلاع على الكثير من المعلومات المفيدة. انقر على عنصر القائمة CREATE الذي يتوافق مع طلبك لإنشاء إدخال جديد في دفتر اليومية. سيظهر لك جدول جديد على النحو التالي:

385d62152e99aad4.png

كما ذكرنا، يوفّر محاكي Firestore أدوات لتطوير قواعد الأمان في تطبيقك. يعرض هذا العرض السطر الذي اجتازه هذا الطلب في قواعد الأمان (أو لم يجتزه، إذا كان الأمر كذلك). في التطبيقات الأكثر فعالية، يمكن أن تتوسع "قواعد الأمان" وتتضمّن عمليات تحقّق متعددة من الأذونات. يتم استخدام طريقة العرض هذه للمساعدة في كتابة قواعد التفويض وتصحيح أخطائها.

كما يوفّر طريقة سهلة لفحص كل جزء من هذا الطلب، بما في ذلك البيانات الوصفية وبيانات المصادقة. تُستخدَم هذه البيانات لكتابة قواعد تفويض معقّدة.

القراءة من Firestore

تستخدم Firestore مزامنة البيانات لإرسال البيانات المعدَّلة إلى الأجهزة المرتبطة. في رمز Flutter، يمكنك الاستماع (أو الاشتراك) في مجموعات ومستندات Firestore، وسيتم إعلام الرمز الخاص بك في أي وقت تتغير فيه البيانات. في هذا التطبيق، يتم الاستماع إلى تحديثات Firestore في الطريقة المسماة AppState._listenForEntries.

يعمل هذا الرمز بالتزامن مع الرمزين StreamController وStream اللذين يُطلق عليهما AppState._entriesStreamController وAppState.entries على التوالي. تمت كتابة هذا الرمز البرمجي مسبقًا، وكذلك جميع الرموز البرمجية اللازمة في واجهة المستخدم لعرض البيانات من Firestore.

عدِّل طريقة _listenForEntries لتتطابق مع الرمز البرمجي أدناه:

app_state.dart

import 'dart:async';

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';

import 'entry.dart';

class AppState {
 AppState() {
   _entriesStreamController = StreamController.broadcast(onListen: () {
     _entriesStreamController.add([
       Entry(
         date: '10/09/2022',
         text: lorem,
         title: '[Example] My Journal Entry',
       )
     ]);
   });
 }

 User? user;
 Stream<List<Entry>> get entries => _entriesStreamController.stream;
 late final StreamController<List<Entry>> _entriesStreamController;

 Future<void> logIn(String email, String password) async {
   final credential = await FirebaseAuth.instance
       .signInWithEmailAndPassword(email: email, password: password);
   if (credential.user != null) {
     user = credential.user!;
     _listenForEntries();
   } else {
     print('no user!');
   }
 }

 void writeEntryToFirebase(Entry entry) {
   FirebaseFirestore.instance.collection('Entries').add(<String, String>{
     'title': entry.title,
     'date': entry.date.toString(),
     'text': entry.text,
   });
 }

 void _listenForEntries() {
   FirebaseFirestore.instance
       .collection('Entries')
       .snapshots()
       .listen((event) {
     final entries = event.docs.map((doc) {
       final data = doc.data();
       return Entry(
         date: data['date'] as String,
         text: data['text'] as String,
         title: data['title'] as String,
       );
     }).toList();

     _entriesStreamController.add(entries);
   });
 }
 // ...
}

تستمع هذه التعليمات البرمجية إلى مجموعة "الإدخالات" في Firestore. عندما يُعلم Firestore هذا العميل بأنّ هناك بيانات جديدة، يمرّر تلك البيانات والرمز في _listenForEntries ويغيّر جميع المستندات الفرعية إلى كائن يمكن لتطبيقنا استخدامه (Entry). بعد ذلك، يضيف هذه الإدخالات إلى StreamController المسمّى _entriesStreamController (الذي تستمع إليه واجهة المستخدم). هذا الرمز هو التحديث الوحيد المطلوب.

أخيرًا، تذكَّر أنّ الطريقة AppState.logIn تستدعي _listenForEntries، ما يؤدي إلى بدء عملية الاستماع بعد تسجيل المستخدم الدخول.

// ...
Future<void> logIn(String email, String password) async {
 final credential = await FirebaseAuth.instance
     .signInWithEmailAndPassword(email: email, password: password);
 if (credential.user != null) {
   user = credential.user!;
   _listenForEntries();
 } else {
   print('no user!');
 }
}
// ...

الآن، شغِّل التطبيق. من المفترض أن يظهر على النحو التالي:

b8a31c7a8900331.gif

7. تصدير البيانات واستيرادها إلى المحاكي

تتيح محاكيات Firebase استيراد البيانات وتصديرها. يتيح لك استخدام عمليات الاستيراد والتصدير مواصلة التطوير باستخدام البيانات نفسها عند أخذ استراحة من التطوير ثم استئنافه. يمكنك أيضًا إرسال ملفات البيانات إلى git، وسيتمكّن المطوّرون الآخرون الذين تعمل معهم من استخدام البيانات نفسها.

تصدير بيانات المحاكي

أولاً، عليك تصدير بيانات المحاكي المتوفّرة لديك. أثناء استمرار تشغيل المحاكيات، افتح نافذة وحدة طرفية جديدة وأدخِل الأمر التالي:

firebase emulators:export ./emulators_data

.emulators_data هو وسيط يحدّد المكان الذي سيتم تصدير البيانات إليه في Firebase. إذا لم يكن الدليل متوفّرًا، سيتم إنشاؤه. يمكنك استخدام أي اسم تريده لهذا الدليل.

عند تشغيل هذا الأمر، ستظهر لك النتيجة التالية في الوحدة الطرفية التي شغّلت الأمر فيها:

i  Found running emulator hub for project flutter-firebase-codelab-d6b79 at http://localhost:4400
i  Creating export directory /Users/ewindmill/Repos/codelabs/firebase-emulator-suite/complete/emulators_data
i  Exporting data to: /Users/ewindmill/Repos/codelabs/firebase-emulator-suite/complete/emulators_data
✔  Export complete

وإذا بدّلت إلى نافذة الوحدة الطرفية التي يتم تشغيل المحاكيات فيها، ستظهر لك النتائج التالية:

i  emulators: Received export request. Exporting data to /Users/ewindmill/Repos/codelabs/firebase-emulator-suite/complete/emulators_data.
✔  emulators: Export complete.

وأخيرًا، إذا نظرت في دليل مشروعك، من المفترض أن يظهر لك دليل باسم ./emulators_data يحتوي على ملفات JSON، بالإضافة إلى ملفات البيانات الوصفية الأخرى، مع البيانات التي حفظتها.

استيراد بيانات المحاكي

يمكنك الآن استيراد هذه البيانات كجزء من سير عمل التطوير، والبدء من حيث توقفت.

أولاً، أوقِف المحاكيات إذا كانت قيد التشغيل من خلال الضغط على CTRL+C في الوحدة الطرفية.

بعد ذلك، شغِّل الأمر emulators:start الذي سبق أن رأيته، ولكن مع علامة تحدّد البيانات المطلوب استيرادها:

firebase emulators:start --import ./emulators_data

عند تشغيل المحاكيات، انتقِل إلى واجهة مستخدم المحاكي على localhost:4000، وستظهر لك البيانات نفسها التي كنت تعمل عليها سابقًا.

تصدير البيانات تلقائيًا عند إغلاق المحاكيات

يمكنك أيضًا تصدير البيانات تلقائيًا عند إغلاق المحاكيات، بدلاً من تذكُّر تصدير البيانات في نهاية كل جلسة تطوير.

عند بدء المحاكيات، شغِّل الأمر emulators:start مع علامتَين إضافيتَين.

firebase emulators:start --import ./emulators_data --export-on-exit

حسنًا! سيتم الآن حفظ بياناتك وإعادة تحميلها في كل مرة تستخدم فيها المحاكي لهذا المشروع. يمكنك أيضًا تحديد دليل مختلف كوسيطة للأمر –export-on-exit flag، ولكن سيتم تلقائيًا استخدام الدليل الذي تم تمريره إلى –import.

يمكنك أيضًا استخدام أي مجموعة من هذه الخيارات. في ما يلي ملاحظة من المستندات: يمكن تحديد دليل التصدير باستخدام هذه العلامة: firebase emulators:start --export-on-exit=./saved-data. في حال استخدام --import، يكون مسار التصدير هو نفسه تلقائيًا، على سبيل المثال: firebase emulators:start --import=./data-path --export-on-exit. أخيرًا، إذا أردت ذلك، مرِّر مسارات دليل مختلفة إلى العلامتَين --import و--export-on-exit.

8. تهانينا!

لقد أكملت دورة "بدء استخدام محاكي Firebase وFlutter". يمكنك العثور على الرمز البرمجي المكتمل لهذا الدرس التطبيقي حول الترميز في المجلد "complete" على GitHub: دروس Flutter التطبيقية حول الترميز

المواضيع التي تناولناها

  • إعداد تطبيق Flutter لاستخدام Firebase
  • إعداد مشروع Firebase
  • FlutterFire CLI
  • Firebase CLI
  • محاكي Firebase Authentication
  • محاكي Firestore في Firebase
  • استيراد بيانات المحاكي وتصديرها

الخطوات التالية

مزيد من المعلومات

"سباركي" فخور بك!

2a0ad195769368b1.gif