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

1- قبل البدء

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

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

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

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

المحتوى الذي ستنشئه

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

cd5c4753bbee8af.png 8cb4d21f656540bf.png

المعلومات التي ستطّلع عليها

ستتعلّم كيفية بدء استخدام منصّة Firebase وكيفية دمج حزمة Firebase Emulator واستخدامها في سير عمل تطوير Flutter. سيتم تناول المواضيع التالية حول Firebase:

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

المتطلبات

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

2- إنشاء مشروع على Firebase وإعداده

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

إنشاء مشروع على Firebase

  1. سجِّل الدخول إلى "وحدة تحكُّم Firebase".
  2. في وحدة تحكُّم Firebase، انقر على إضافة مشروع (أو إنشاء مشروع)، وأدخِل اسمًا لمشروع Firebase (مثل "Firebase-Flutter-Codelab").

fe6aeab3b91965ed.png

  1. انقر على خيارات إنشاء المشروع. وافِق على بنود Firebase إذا طُلب منك ذلك. يمكنك تخطّي عملية إعداد "إحصاءات Google" لأنّك لن تستخدم "إحصاءات Google" لهذا التطبيق.

d1fcec48bf251eaa.png

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

يستخدم التطبيق الذي تنشئه منتجَين من Firebase متوفّران لتطبيقات Flutter، وهما:

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

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

تفعيل Cloud Firestore

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

تفعيل Cloud Firestore:

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

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

ستحتاج إلى تنزيل رمز إجراء التفعيل وتثبيت واجهة سطر الأوامر في Firebase قبل أن نبدأ.

الحصول على رمز إجراء التفعيل

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

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

بدلاً من ذلك، إذا كانت أداة cli (واجهة المستخدم في 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

يوفر واجهة سطر الأوامر في Firebase أدوات لإدارة مشاريع Firebase. يجب توفر واجهة سطر الأوامر لاستخدام "مجموعة أدوات المحاكاة"، لذا يجب تثبيتها.

وهناك العديد من الطرق لتثبيت واجهة سطر الأوامر. وأبسط طريقة، إذا كنت تستخدم نظام التشغيل 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

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

أولاً، عليك تثبيت واجهة سطر الأوامر:

dart pub global activate flutterfire_cli

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

flutterfire --help

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

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

أولاً، يمكنك إنهاء إعداد Firebase من خلال تشغيل ما يلي:

firebase init

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

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

f11dcab439e6ac1e.png

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

3bdc0c6934991c25.png

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

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

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

8544e41037637b07.png

إعداد FlutterFire

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

flutterfire configure

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

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

619b7aca6dc15472.png 301c9534f594f472.png

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

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

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

11563f4c7216de81.png

وهذه هي الصفحة الرئيسية لواجهة مستخدم المحاكي المحلي. ويسرد هذا التقرير جميع أدوات المحاكاة المتاحة، ويتم تصنيف كل جهاز محاكاة بحالة التشغيل أو الإيقاف.

5- محاكي مصادقة Firebase

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

3c1bfded40733189.png

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

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

إضافة مستخدم

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

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

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

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، وتوفّر المعلومات المطلوبة، مثل User.displayName التي تتم مناقشتها قليلاً.

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

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

قائمة المهام: رمز الإجراء: أعِد تحميل التطبيق، ثم اضغط على زر "تسجيل الدخول" عند ظهوره. ويؤدي هذا إلى انتقال التطبيق إلى صفحة تعرض الرسالة "مرحبًا بك من جديد يا شخص!" في أعلى الصفحة يجب أن تعمل عملية المصادقة لأنّها تسمح لك بالانتقال إلى هذه الصفحة، ولكن يجب إجراء تعديل بسيط على 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 وابحث عن "Data" (البيانات) . يُفترض أن ترى أنه توجد الآن مجموعة مختارات في جذر قاعدة البيانات تسمى "Entries". يجب أن يحتوي على مستند يحتوي على المعلومات نفسها التي أدخلتها في النموذج.

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.

ويمكنك أيضًا استخدام أي مجموعة من هذه الخيارات. هذه هي الملاحظة من "مستندات Google": يمكن تحديد دليل التصدير باستخدام هذه العلامة: 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. يمكنك العثور على الرمز المكتمل لهذا الدرس التطبيقي في قسم "الاكتمال" الدليل على GitHub: Flutter Codelabs

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

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

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

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

Sparky فخور بك!

2a0ad195769368b1.gif