1. نظرة عامة
الأهداف
في هذا الدرس التطبيقي حول الترميز، ستُنشئ تطبيقًا لاقتراحات المطاعم على Android باستخدام Cloud Firestore. ستتعرّف على كيفية:
- قراءة البيانات وكتابتها في Firestore من تطبيق Android
- الاستماع إلى التغييرات في بيانات Firestore في الوقت الفعلي
- استخدام قواعد الأمان ومصادقة Firebase لتأمين بيانات Firestore
- كتابة طلبات بحث معقدة في Firestore
المتطلبات الأساسية
قبل بدء هذا الدليل التعليمي حول الرموز البرمجية، تأكَّد من توفّر ما يلي:
- الإصدار Flamingo من "استوديو Android" أو إصدار أحدث
- محاكي Android مزوّد لواجهة برمجة التطبيقات 19 أو إصدار أحدث
- الإصدار 16 من Node.js أو إصدار أحدث
- إصدار Java 17 أو إصدار أحدث
2- إنشاء مشروع على Firebase
- سجِّل الدخول إلى وحدة تحكُّم Firebase باستخدام حسابك على Google.
- في وحدة تحكُّم Firebase، انقر على إضافة مشروع.
- كما هو موضّح في لقطة الشاشة أدناه، أدخِل اسمًا لمشروعك على Firebase (مثل "Friendly Eats")، ثم انقر على متابعة.
- قد يُطلب منك تفعيل "إحصاءات Google"، ولكن لا يهمّ اختيارك لأغراض هذا الدليل التعليمي.
- بعد دقيقة أو نحو ذلك، سيصبح مشروعك على Firebase جاهزًا. انقر على متابعة.
3- إعداد نموذج المشروع
تنزيل الرمز
نفِّذ الأمر التالي لنسخ نموذج الرمز البرمجي لهذا الدليل التعليمي. سيؤدي ذلك إلى إنشاء مجلد باسم friendlyeats-android
على جهازك:
$ git clone https://github.com/firebase/friendlyeats-android
إذا لم يكن لديك git على جهازك، يمكنك أيضًا تنزيل الرمز البرمجي مباشرةً من GitHub.
إضافة إعدادات Firebase
- في وحدة تحكُّم Firebase، اختَر نظرة عامة على المشروع في شريط التنقّل الأيمن. انقر على الزر Android لاختيار النظام الأساسي. عند طلب اسم حزمة، استخدِم
com.google.firebase.example.fireeats
.
- انقر على تسجيل التطبيق واتّبِع التعليمات لتنزيل ملف
google-services.json
ونقله إلى مجلدapp/
في الرمز الذي نزّلته للتو. بعد ذلك، انقر على التالي.
استيراد المشروع
افتح "استوديو Android". انقر على ملف > جديد > استيراد مشروع واختَر المجلد friendlyeats-android.
4. إعداد محاكيات Firebase
في هذا الدليل التعليمي، ستستخدم مجموعة أدوات المحاكاة في Firebase لمحاكاة Cloud Firestore وخدمات Firebase الأخرى محليًا. ويوفّر ذلك بيئة تطوير محلية آمنة وسريعة وبدون تكلفة لإنشاء تطبيقك.
تثبيت Firebase CLI
عليك أولاً تثبيت واجهة برمجة تطبيقات Firebase. إذا كنت تستخدم نظام التشغيل macOS أو Linux، يمكنك تنفيذ الأمر cURL التالي:
curl -sL https://firebase.tools | bash
إذا كنت تستخدم نظام التشغيل Windows، اطّلِع على تعليمات التثبيت للحصول على ملف ثنائي مستقل أو للتثبيت من خلال npm
.
بعد تثبيت واجهة برمجة التطبيقات، من المفترض أن يؤدي تشغيل firebase --version
إلى عرض الإصدار 9.0.0
أو إصدار أحدث:
$ firebase --version 9.0.0
تسجيل الدخول
شغِّل firebase login
لربط وحدة تحكّم سطر الأوامر بحسابك على Google. سيؤدي ذلك إلى فتح نافذة متصفّح جديدة لإكمال عملية تسجيل الدخول. احرص على اختيار الحساب نفسه الذي استخدمته عند إنشاء مشروعك على Firebase في وقت سابق.
ربط مشروعك
من داخل مجلد friendlyeats-android
، يمكنك تشغيل firebase use --add
لربط مشروعك على الجهاز بمشروعك على Firebase. اتّبِع التعليمات لاختيار المشروع الذي أنشأته في وقت سابق، وإذا طُلب منك اختيار اسم بديل، أدخِل default
.
5- تشغيل التطبيق
حان الآن وقت تشغيل مجموعة أدوات المحاكاة في Firebase وتطبيق FriendlyEats لنظام التشغيل Android للمرة الأولى.
تشغيل المحاكيات
في المحطة الطرفية، من داخل الدليل friendlyeats-android
، شغِّل firebase emulators:start
لبدء تشغيل محاكيات Firebase. من المفترض أن تظهر لك السجلات على النحو التالي:
$ firebase emulators:start i emulators: Starting emulators: auth, firestore i firestore: Firestore Emulator logging to firestore-debug.log i ui: Emulator UI logging to ui-debug.log ┌─────────────────────────────────────────────────────────────┐ │ ✔ All emulators ready! It is now safe to connect your app. │ │ i View Emulator UI at http://localhost:4000 │ └─────────────────────────────────────────────────────────────┘ ┌────────────────┬────────────────┬─────────────────────────────────┐ │ Emulator │ Host:Port │ View in Emulator UI │ ├────────────────┼────────────────┼─────────────────────────────────┤ │ Authentication │ localhost:9099 │ http://localhost:4000/auth │ ├────────────────┼────────────────┼─────────────────────────────────┤ │ Firestore │ localhost:8080 │ http://localhost:4000/firestore │ └────────────────┴────────────────┴─────────────────────────────────┘ Emulator Hub running at localhost:4400 Other reserved ports: 4500 Issues? Report them at https://github.com/firebase/firebase-tools/issues and attach the *-debug.log files.
لديك الآن بيئة تطوير محلية كاملة تعمل على جهازك. احرص على إبقاء هذا الأمر قيد التشغيل طوال فترة استخدام ورشة رموز البرامج، إذ سيحتاج تطبيق Android إلى الاتصال بالمحاكيات.
ربط التطبيق بالمحاكيات
افتح الملفَّين util/FirestoreInitializer.kt
وutil/AuthInitializer.kt
في Android Studio. تحتوي هذه الملفات على منطق ربط حِزم تطوير البرامج (SDK) لمنصة Firebase بالمحاكيات المحلية التي تعمل على جهازك عند بدء تشغيل التطبيق.
في طريقة create()
لفئة FirestoreInitializer
، راجِع هذه القطعة من الرمز البرمجي:
// Use emulators only in debug builds
if (BuildConfig.DEBUG) {
firestore.useEmulator(FIRESTORE_EMULATOR_HOST, FIRESTORE_EMULATOR_PORT)
}
نحن نستخدم BuildConfig
للتأكّد من عدم الاتصال بالمحاكيات إلا عندما يكون تطبيقنا قيد التشغيل في وضع debug
. عند تجميع التطبيق في وضع release
، سيكون هذا الشرط خطأ.
نرى أنّه يستخدم طريقة useEmulator(host, port)
لربط حزمة تطوير البرامج (SDK) لمنصّة Firebase بمحاكي Firestore المحلي. في جميع أنحاء التطبيق، سنستخدم FirebaseUtil.getFirestore()
للوصول إلى هذه النسخة من FirebaseFirestore
لنتأكّد من أنّنا نتصل دائمًا بمحاكي Firestore عند التشغيل في وضع debug
.
تشغيل التطبيق
إذا أضفت ملف google-services.json
بشكل صحيح، من المفترض أن يتم الآن تجميع المشروع. في "استوديو Android"، انقر على إنشاء > إعادة إنشاء المشروع وتأكَّد من عدم توفّر أي أخطاء متبقية.
في Android Studio، شغِّل التطبيق على محاكي Android. ستظهر لك أولاً شاشة "تسجيل الدخول". يمكنك استخدام أي بريد إلكتروني وكلمة مرور لتسجيل الدخول إلى التطبيق. تتصل عملية تسجيل الدخول هذه بمحاكي Firebase Authentication، لذا لا يتم نقل أي بيانات اعتماد حقيقية.
افتح الآن واجهة مستخدم المحاكيات من خلال الانتقال إلى http://localhost:4000 في متصفّح الويب. بعد ذلك، انقر على علامة التبويب المصادقة، ومن المفترض أن يظهر لك الحساب الذي أنشأته للتو:
بعد إكمال عملية تسجيل الدخول، من المفترض أن تظهر لك شاشة التطبيق الرئيسية:
سنضيف قريبًا بعض البيانات لتعبئة الشاشة الرئيسية.
6- كتابة البيانات في Firestore
في هذا القسم، سنكتب بعض البيانات في Firestore لنتمكّن من ملء الشاشة الرئيسية الفارغة حاليًا.
عنصر النموذج الرئيسي في تطبيقنا هو مطعم (راجِع model/Restaurant.kt
). يتم تقسيم بيانات Firestore إلى مستندات ومجموعات ومجموعات فرعية. سنخزّن كل مطعم كمستند في مجموعة على مستوى أعلى تُسمى "restaurants"
. لمزيد من المعلومات عن نموذج بيانات Firestore، يمكنك الاطّلاع على المستندات والمجموعات في المستندات.
لأغراض توضيحية، سنضيف وظيفة في التطبيق لإنشاء عشرة مطاعم عشوائية عند النقر على الزر "إضافة عناصر عشوائية" في قائمة الخيارات الإضافية. افتح الملف MainFragment.kt
واستبدِل المحتوى في الطريقة onAddItemsClicked()
بما يلي:
private fun onAddItemsClicked() {
val restaurantsRef = firestore.collection("restaurants")
for (i in 0..9) {
// Create random restaurant / ratings
val randomRestaurant = RestaurantUtil.getRandom(requireContext())
// Add restaurant
restaurantsRef.add(randomRestaurant)
}
}
في ما يلي بعض النقاط المهمة التي يجب مراعاتها بشأن الرمز أعلاه:
- بدأنا بالحصول على مرجع إلى مجموعة
"restaurants"
. يتم إنشاء المجموعات بشكل ضمني عند إضافة المستندات، لذا لم تكن هناك حاجة إلى إنشاء المجموعة قبل كتابة البيانات. - يمكن إنشاء المستندات باستخدام فئات بيانات Kotlin التي نستخدمها لإنشاء كل مستند مطعم.
- تُضيف الطريقة
add()
مستندًا إلى مجموعة باستخدام معرّف يتم إنشاؤه تلقائيًا، لذا لم نحتاج إلى تحديد معرّف فريد لكل مطعم.
الآن، شغِّل التطبيق مرة أخرى وانقر على الزر "إضافة عناصر عشوائية" في القائمة الكاملة (في أعلى يسار الشاشة) لتشغيل الرمز الذي كتبته للتو:
افتح الآن واجهة مستخدم المحاكيات من خلال الانتقال إلى http://localhost:4000 في متصفّح الويب. بعد ذلك، انقر على علامة التبويب Firestore ومن المفترض أن تظهر لك البيانات التي أضفتها للتو:
هذه البيانات محلية تمامًا على جهازك. في الواقع، لا يحتوي مشروعك الحقيقي على قاعدة بيانات Firestore حتى الآن. وهذا يعني أنّه من الآمن تجربة تعديل هذه البيانات وحذفها بدون أي عواقب.
تهانينا، لقد كتبت للتو بيانات في Firestore. في الخطوة التالية، سنتعرّف على كيفية عرض هذه البيانات في التطبيق.
7. عرض البيانات من Firestore
في هذه الخطوة، سنتعرّف على كيفية استرداد البيانات من Firestore وعرضها في تطبيقنا. الخطوة الأولى لقراءة البيانات من Firestore هي إنشاء Query
. افتح الملف MainFragment.kt
وأضِف الرمز التالي إلى بداية طريقة onViewCreated()
:
// Firestore
firestore = Firebase.firestore
// Get the 50 highest rated restaurants
query = firestore.collection("restaurants")
.orderBy("avgRating", Query.Direction.DESCENDING)
.limit(LIMIT.toLong())
نريد الآن الاستماع إلى طلب البحث لنحصل على جميع المستندات المطابقة ونتلقّى إشعارًا بالتغييرات المستقبلية في الوقت الفعلي. بما أنّ هدفنا النهائي هو ربط هذه البيانات بـ RecyclerView
، علينا إنشاء فئة RecyclerView.Adapter
للاستماع إلى البيانات.
افتح فئة FirestoreAdapter
التي سبق أن تم تنفيذها جزئيًا. أولاً، لنجعل المُحوِّل ينفِّذ EventListener
ويحدِّد دالة onEvent
حتى يتمكّن من تلقّي تعديلات على طلب بحث في Firestore:
abstract class FirestoreAdapter<VH : RecyclerView.ViewHolder>(private var query: Query?) :
RecyclerView.Adapter<VH>(),
EventListener<QuerySnapshot> { // Add this implements
// ...
// Add this method
override fun onEvent(documentSnapshots: QuerySnapshot?, e: FirebaseFirestoreException?) {
// Handle errors
if (e != null) {
Log.w(TAG, "onEvent:error", e)
return
}
// Dispatch the event
if (documentSnapshots != null) {
for (change in documentSnapshots.documentChanges) {
// snapshot of the changed document
when (change.type) {
DocumentChange.Type.ADDED -> {
// TODO: handle document added
}
DocumentChange.Type.MODIFIED -> {
// TODO: handle document changed
}
DocumentChange.Type.REMOVED -> {
// TODO: handle document removed
}
}
}
}
onDataChanged()
}
// ...
}
عند التحميل الأولي، سيتلقّى المستمع حدث ADDED
واحدًا لكل مستند جديد. مع تغيُّر مجموعة نتائج طلب البحث بمرور الوقت، سيتلقّى المستمع المزيد من الأحداث التي تحتوي على التغييرات. لننهِ الآن تنفيذ المستمع. أضِف أولاً ثلاث طرق جديدة: onDocumentAdded
وonDocumentModified
وonDocumentRemoved
:
private fun onDocumentAdded(change: DocumentChange) {
snapshots.add(change.newIndex, change.document)
notifyItemInserted(change.newIndex)
}
private fun onDocumentModified(change: DocumentChange) {
if (change.oldIndex == change.newIndex) {
// Item changed but remained in same position
snapshots[change.oldIndex] = change.document
notifyItemChanged(change.oldIndex)
} else {
// Item changed and changed position
snapshots.removeAt(change.oldIndex)
snapshots.add(change.newIndex, change.document)
notifyItemMoved(change.oldIndex, change.newIndex)
}
}
private fun onDocumentRemoved(change: DocumentChange) {
snapshots.removeAt(change.oldIndex)
notifyItemRemoved(change.oldIndex)
}
بعد ذلك، يمكنك الاتصال بهذه الطرق الجديدة من onEvent
:
override fun onEvent(documentSnapshots: QuerySnapshot?, e: FirebaseFirestoreException?) {
// Handle errors
if (e != null) {
Log.w(TAG, "onEvent:error", e)
return
}
// Dispatch the event
if (documentSnapshots != null) {
for (change in documentSnapshots.documentChanges) {
// snapshot of the changed document
when (change.type) {
DocumentChange.Type.ADDED -> {
onDocumentAdded(change) // Add this line
}
DocumentChange.Type.MODIFIED -> {
onDocumentModified(change) // Add this line
}
DocumentChange.Type.REMOVED -> {
onDocumentRemoved(change) // Add this line
}
}
}
}
onDataChanged()
}
أخيرًا، نفِّذ الطريقة startListening()
لإرفاق المستمع:
fun startListening() {
if (registration == null) {
registration = query.addSnapshotListener(this)
}
}
تم الآن ضبط التطبيق بالكامل لقراءة البيانات من Firestore. شغِّل التطبيق مرة أخرى ومن المفترض أن تظهر لك المطاعم التي أضفتها في الخطوة السابقة:
الآن، ارجع إلى واجهة مستخدم المحاكي في المتصفّح وعدِّل أحد أسماء المطاعم. من المفترض أن يظهر التغيير في التطبيق على الفور تقريبًا.
8. ترتيب البيانات وتصفيتها
يعرض التطبيق حاليًا المطاعم الأعلى تقييمًا في المجموعة بأكملها، ولكن في تطبيق المطاعم الحقيقي، سيحتاج المستخدم إلى ترتيب البيانات وفلترةها. على سبيل المثال، يجب أن يتمكّن التطبيق من عرض "أفضل مطاعم المأكولات البحرية في القاهرة" أو "أرخص بيتزا".
يؤدي النقر على الشريط الأبيض في أعلى التطبيق إلى عرض مربّع حوار الفلاتر. في هذا القسم، سنستخدم طلبات بحث Firestore لتشغيل مربّع الحوار هذا:
لنعدِّل طريقة onFilter()
في MainFragment.kt
. تقبل هذه الطريقة كائن Filters
وهو كائن مساعد أنشأناه لتسجيل ناتج مربّع حوار الفلاتر. سنغيّر هذه الطريقة لإنشاء طلب بحث من الفلاتر:
override fun onFilter(filters: Filters) {
// Construct query basic query
var query: Query = firestore.collection("restaurants")
// Category (equality filter)
if (filters.hasCategory()) {
query = query.whereEqualTo(Restaurant.FIELD_CATEGORY, filters.category)
}
// City (equality filter)
if (filters.hasCity()) {
query = query.whereEqualTo(Restaurant.FIELD_CITY, filters.city)
}
// Price (equality filter)
if (filters.hasPrice()) {
query = query.whereEqualTo(Restaurant.FIELD_PRICE, filters.price)
}
// Sort by (orderBy with direction)
if (filters.hasSortBy()) {
query = query.orderBy(filters.sortBy.toString(), filters.sortDirection)
}
// Limit items
query = query.limit(LIMIT.toLong())
// Update the query
adapter.setQuery(query)
// Set header
binding.textCurrentSearch.text = HtmlCompat.fromHtml(
filters.getSearchDescription(requireContext()),
HtmlCompat.FROM_HTML_MODE_LEGACY
)
binding.textCurrentSortBy.text = filters.getOrderDescription(requireContext())
// Save filters
viewModel.filters = filters
}
في المقتطف أعلاه، ننشئ عنصرًا من النوع Query
من خلال إرفاق عبارةَي where
وorderBy
لمطابقة الفلاتر المحدّدة.
شغِّل التطبيق مرة أخرى واختَر الفلتر التالي لعرض المطاعم ذات الأسعار المنخفضة والأكثر رواجًا:
من المفترض أن تظهر لك الآن قائمة مفلتَرة بالمطاعم التي تتضمّن خيارات منخفضة السعر فقط:
إذا وصلت إلى هذه المرحلة، يعني ذلك أنّك أنشأت الآن تطبيقًا يعمل بكامل طاقته لعرض اقتراحات المطاعم على Firestore. يمكنك الآن ترتيب المطاعم وفلترتها في الوقت الفعلي. في الأقسام القليلة التالية، سنضيف مراجعات إلى المطاعم وسنضيف قواعد أمان إلى التطبيق.
9. تنظيم البيانات في مجموعات فرعية
في هذا القسم، سنضيف تقييمات إلى التطبيق حتى يتمكّن المستخدمون من مراجعة المطاعم المفضّلة لديهم (أو الأقلّ تفضيلاً).
المجموعات والمجموعات الفرعية
لقد نجحنا حتى الآن في تخزين كل بيانات المطاعم في مجموعة من المستوى الأعلى تُسمى "مطاعم". عندما يقيم مستخدم مطعمًا، نريد إضافة عنصر Rating
جديد إلى المطاعم. في هذه المهمة، سنستخدم مجموعة فرعية. يمكنك اعتبار المجموعة الفرعية مجموعة مرفقة بمستند. وبالتالي، سيتضمّن كل مستند مطعم مجموعة فرعية من التقييمات مليئة بمستندات التقييم. تساعد المجموعات الفرعية في تنظيم البيانات بدون تضخيم مستنداتنا أو طلب طلبات بحث معقّدة.
للوصول إلى مجموعة فرعية، استخدِم العنصر .collection()
في المستند الرئيسي:
val subRef = firestore.collection("restaurants")
.document("abc123")
.collection("ratings")
يمكنك الوصول إلى مجموعة فرعية وإجراء طلب بحث عنها تمامًا كما هو الحال مع المجموعة من المستوى الأعلى، ولا تتوفّر أي قيود على الحجم أو تغييرات في الأداء. يمكنك الاطّلاع على مزيد من المعلومات حول نموذج بيانات Firestore هنا.
كتابة البيانات في معاملة
لا تتطلّب إضافة Rating
إلى المجموعة الفرعية المناسبة سوى استدعاء .add()
، ولكن علينا أيضًا تعديل متوسط تقييم عنصر Restaurant
وعدد التقييمات لتعكس البيانات الجديدة. إذا استخدمنا عمليات منفصلة لإجراء هذين التغييرَين، هناك عدد من حالات تداخل العمليات التي يمكن أن تؤدي إلى ظهور بيانات قديمة أو غير صحيحة.
لضمان إضافة التقييمات بشكلٍ صحيح، سنستخدم معاملة لإضافة تقييمات إلى مطعم. ستنفِّذ هذه المعاملة بعض الإجراءات:
- قراءة التقييم الحالي للمطعم واحتساب التقييم الجديد
- إضافة التقييم إلى المجموعة الفرعية
- تعديل متوسط تقييم المطعم وعدد التقييمات
افتح RestaurantDetailFragment.kt
ونفِّذ الدالة addRating
:
private fun addRating(restaurantRef: DocumentReference, rating: Rating): Task<Void> {
// Create reference for new rating, for use inside the transaction
val ratingRef = restaurantRef.collection("ratings").document()
// In a transaction, add the new rating and update the aggregate totals
return firestore.runTransaction { transaction ->
val restaurant = transaction.get(restaurantRef).toObject<Restaurant>()
?: throw Exception("Restaurant not found at ${restaurantRef.path}")
// Compute new number of ratings
val newNumRatings = restaurant.numRatings + 1
// Compute new average rating
val oldRatingTotal = restaurant.avgRating * restaurant.numRatings
val newAvgRating = (oldRatingTotal + rating.rating) / newNumRatings
// Set new restaurant info
restaurant.numRatings = newNumRatings
restaurant.avgRating = newAvgRating
// Commit to Firestore
transaction.set(restaurantRef, restaurant)
transaction.set(ratingRef, rating)
null
}
}
تعرض الدالة addRating()
Task
يمثّل المعاملة بالكامل. في الدالة onRating()
، تتم إضافة مستمعي الأحداث إلى المهمة للردّ على نتيجة المعاملة.
الآن، يمكنك تشغيل التطبيق مرة أخرى والنقر على أحد المطاعم، ما سيؤدي إلى عرض شاشة تفاصيل المطعم. انقر على الزر + لبدء إضافة مراجعة. أضِف مراجعة من خلال اختيار عدد من النجوم وإدخال بعض النصوص.
سيؤدي النقر على إرسال إلى بدء المعاملة. عند اكتمال المعاملة، ستظهر مراجعتك أدناه وستظهر لك معلومات جديدة حول عدد مراجعات المطعم:
تهانينا! أصبح لديك الآن تطبيق مراجعات المطاعم على الأجهزة الجوّالة والمحلية والتفاعلية، وهو مبني على Cloud Firestore. أعلم أنّ هذه الميزة رائجة جدًا في الوقت الحالي.
10. تأمين بياناتك
لم نأخذ حتى الآن في الاعتبار أمان هذا التطبيق. كيف نعرف أنّه يمكن للمستخدمين قراءة وكتابة بياناتهم الصحيحة فقط؟ يتم تأمين قواعد بيانات Firestore من خلال ملف إعدادات يُسمى قواعد الأمان.
افتح ملف firestore.rules
واستبدِل المحتوى بما يلي:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Determine if the value of the field "key" is the same
// before and after the request.
function isUnchanged(key) {
return (key in resource.data)
&& (key in request.resource.data)
&& (resource.data[key] == request.resource.data[key]);
}
// Restaurants
match /restaurants/{restaurantId} {
// Any signed-in user can read
allow read: if request.auth != null;
// Any signed-in user can create
// WARNING: this rule is for demo purposes only!
allow create: if request.auth != null;
// Updates are allowed if no fields are added and name is unchanged
allow update: if request.auth != null
&& (request.resource.data.keys() == resource.data.keys())
&& isUnchanged("name");
// Deletes are not allowed.
// Note: this is the default, there is no need to explicitly state this.
allow delete: if false;
// Ratings
match /ratings/{ratingId} {
// Any signed-in user can read
allow read: if request.auth != null;
// Any signed-in user can create if their uid matches the document
allow create: if request.auth != null
&& request.resource.data.userId == request.auth.uid;
// Deletes and updates are not allowed (default)
allow update, delete: if false;
}
}
}
}
تفرض هذه القواعد قيودًا على الوصول لضمان إجراء العملاء تغييرات آمنة فقط. على سبيل المثال، يمكن أن تؤدي التعديلات على مستند مطعم إلى تغيير التقييمات فقط، وليس الاسم أو أي بيانات أخرى غير قابلة للتغيير. لا يمكن إنشاء التقييمات إلا إذا كان رقم تعريف المستخدم مطابقًا للمستخدم الذي سجّل الدخول، ما يمنع انتحال الهوية.
لقراءة المزيد من المعلومات عن قواعد الأمان، يُرجى الانتقال إلى المستندات.
11. الخاتمة
لقد أنشأت الآن تطبيقًا مزوّدًا بميزات كاملة على Firestore. لقد تعرّفت على أهم ميزات Firestore، بما في ذلك:
- المستندات والمجموعات
- قراءة البيانات وكتابتها
- الترتيب والفلترة باستخدام طلبات البحث
- المجموعات الفرعية
- المعاملات
مزيد من المعلومات
لمواصلة التعرّف على Firestore، إليك بعض المراجع الجيدة للبدء:
استند تطبيق المطعم في هذا الدليل التعليمي إلى مثال التطبيق "Friendly Eats". يمكنك تصفُّح رمز المصدر لهذا التطبيق هنا.
اختياري: النشر في قناة الإصدار العلني
حتى الآن، لم يستخدم هذا التطبيق سوى مجموعة محاكيات Firebase. إذا كنت تريد معرفة كيفية نشر هذا التطبيق في مشروع حقيقي على Firebase، انتقِل إلى الخطوة التالية.
12. (اختياري) نشر تطبيقك
حتى الآن، كان هذا التطبيق مُثبَّتًا على الجهاز فقط، وكانت جميع البيانات مضمّنةً في مجموعة أدوات المحاكاة في Firebase. في هذا القسم، ستتعرّف على كيفية ضبط مشروعك على Firebase لكي يعمل هذا التطبيق في قناة الإصدار العلني.
مصادقة Firebase
في "وحدة تحكّم Firebase"، انتقِل إلى قسم المصادقة وانقر على البدء. انتقِل إلى علامة التبويب طريقة تسجيل الدخول واختَر البريد الإلكتروني/كلمة المرور من مزوّدي الخدمات الأصليين.
فعِّل طريقة تسجيل الدخول باستخدام البريد الإلكتروني/كلمة المرور وانقر على حفظ.
Firestore
إنشاء قاعدة بيانات
انتقِل إلى قسم قاعدة بيانات Firestore في وحدة التحكّم وانقر على إنشاء قاعدة بيانات:
- عند سؤالك عن قواعد الأمان، اختَر البدء في وضع الإنتاج، وسنعدّل هذه القواعد قريبًا.
- اختَر موقع قاعدة البيانات الذي تريد استخدامه لتطبيقك. يُرجى العِلم أنّ اختيار موقع قاعدة البيانات هو قرار دائم، ولتغييره عليك إنشاء مشروع جديد. لمزيد من المعلومات عن اختيار موقع المشروع، يُرجى الاطّلاع على المستندات.
قواعد النشر
لنشر قواعد الأمان التي كتبتها سابقًا، نفِّذ الأمر التالي في دليل codelab:
$ firebase deploy --only firestore:rules
سيؤدي ذلك إلى نشر محتوى firestore.rules
في مشروعك، ويمكنك التأكّد من ذلك من خلال الانتقال إلى علامة التبويب القواعد في وحدة التحكّم.
نشر الفهارس
يتضمّن تطبيق FriendlyEats عمليات ترتيب وفلترة معقّدة تتطلّب عددًا من الفهارس المركّبة المخصّصة. يمكن إنشاء هذه العناصر يدويًا في وحدة تحكّم Firebase، ولكن من الأسهل كتابة تعريفاتها في ملف firestore.indexes.json
ونشرها باستخدام واجهة برمجة التطبيقات Firebase CLI.
إذا فتحت ملف firestore.indexes.json
، ستلاحظ أنّه سبق أن تم تقديم الفهارس المطلوبة:
{
"indexes": [
{
"collectionId": "restaurants",
"queryScope": "COLLECTION",
"fields": [
{ "fieldPath": "city", "mode": "ASCENDING" },
{ "fieldPath": "avgRating", "mode": "DESCENDING" }
]
},
{
"collectionId": "restaurants",
"queryScope": "COLLECTION",
"fields": [
{ "fieldPath": "category", "mode": "ASCENDING" },
{ "fieldPath": "avgRating", "mode": "DESCENDING" }
]
},
{
"collectionId": "restaurants",
"queryScope": "COLLECTION",
"fields": [
{ "fieldPath": "price", "mode": "ASCENDING" },
{ "fieldPath": "avgRating", "mode": "DESCENDING" }
]
},
{
"collectionId": "restaurants",
"queryScope": "COLLECTION",
"fields": [
{ "fieldPath": "city", "mode": "ASCENDING" },
{ "fieldPath": "numRatings", "mode": "DESCENDING" }
]
},
{
"collectionId": "restaurants",
"queryScope": "COLLECTION",
"fields": [
{ "fieldPath": "category", "mode": "ASCENDING" },
{ "fieldPath": "numRatings", "mode": "DESCENDING" }
]
},
{
"collectionId": "restaurants",
"queryScope": "COLLECTION",
"fields": [
{ "fieldPath": "price", "mode": "ASCENDING" },
{ "fieldPath": "numRatings", "mode": "DESCENDING" }
]
},
{
"collectionId": "restaurants",
"queryScope": "COLLECTION",
"fields": [
{ "fieldPath": "city", "mode": "ASCENDING" },
{ "fieldPath": "price", "mode": "ASCENDING" }
]
},
{
"collectionId": "restaurants",
"fields": [
{ "fieldPath": "category", "mode": "ASCENDING" },
{ "fieldPath": "price", "mode": "ASCENDING" }
]
}
],
"fieldOverrides": []
}
لنشر هذه الفهارس، شغِّل الأمر التالي:
$ firebase deploy --only firestore:indexes
يُرجى العِلم أنّ إنشاء الفهرس ليس فوريًا، ويمكنك تتبُّع مستوى التقدّم في وحدة تحكُّم Firebase.
ضبط التطبيق
في الملفَين util/FirestoreInitializer.kt
وutil/AuthInitializer.kt
، أعددنا حزمة تطوير البرامج (SDK) لمنصّة Firebase للربط بالمحاكيات في وضع تصحيح الأخطاء:
override fun create(context: Context): FirebaseFirestore {
val firestore = Firebase.firestore
// Use emulators only in debug builds
if (BuildConfig.DEBUG) {
firestore.useEmulator(FIRESTORE_EMULATOR_HOST, FIRESTORE_EMULATOR_PORT)
}
return firestore
}
إذا كنت تريد اختبار تطبيقك باستخدام مشروعك الحقيقي على Firebase، يمكنك إجراء أيّ مما يلي:
- أنشئ التطبيق في وضع الإصدار وشغِّله على جهاز.
- استبدِل
BuildConfig.DEBUG
مؤقتًا بـfalse
وشغِّل التطبيق مرة أخرى.
يُرجى العِلم أنّه قد تحتاج إلى تسجيل الخروج من التطبيق وتسجيل الدخول مرة أخرى للربط بشكل صحيح بحساب الإصدار العلني.