Cloud Firestore Android Codelab

۱. مرور کلی

اهداف

در این آزمایشگاه کد، شما یک اپلیکیشن پیشنهاد رستوران در اندروید با پشتیبانی Cloud Firestore خواهید ساخت. شما یاد خواهید گرفت که چگونه:

  • خواندن و نوشتن داده‌ها در Firestore از یک برنامه اندروید
  • به تغییرات در داده‌های Firestore در لحظه گوش دهید
  • استفاده از احراز هویت فایربیس و قوانین امنیتی برای ایمن‌سازی داده‌های فایراستور
  • نوشتن کوئری‌های پیچیده Firestore

پیش‌نیازها

قبل از شروع این آزمایشگاه کد، مطمئن شوید که موارد زیر را دارید:

  • اندروید استودیو فلامینگو یا جدیدتر
  • یک شبیه‌ساز اندروید با API 19 یا بالاتر
  • Node.js نسخه ۱۶ یا بالاتر
  • جاوا نسخه ۱۷ یا بالاتر

۲. یک پروژه فایربیس ایجاد کنید

  1. با استفاده از حساب گوگل خود وارد کنسول فایربیس شوید.
  2. برای ایجاد یک پروژه جدید، روی دکمه کلیک کنید و سپس نام پروژه را وارد کنید (برای مثال، FriendlyEats ).
  3. روی ادامه کلیک کنید.
  4. در صورت درخواست، شرایط Firebase را مرور و قبول کنید و سپس روی ادامه کلیک کنید.
  5. (اختیاری) دستیار هوش مصنوعی را در کنسول Firebase (با نام "Gemini در Firebase") فعال کنید.
  6. برای این codelab، به گوگل آنالیتیکس نیاز ندارید ، بنابراین گزینه گوگل آنالیتیکس را غیرفعال کنید .
  7. روی ایجاد پروژه کلیک کنید، منتظر بمانید تا پروژه شما آماده شود و سپس روی ادامه کلیک کنید.

۳. پروژه نمونه را راه‌اندازی کنید

کد را دانلود کنید

دستور زیر را برای کپی کردن کد نمونه برای این codelab اجرا کنید. این کار پوشه‌ای به نام friendlyeats-android در دستگاه شما ایجاد می‌کند:

$ git clone https://github.com/firebase/friendlyeats-android

اگر گیت را روی دستگاه خود ندارید، می‌توانید کد را مستقیماً از گیت‌هاب دانلود کنید.

پیکربندی Firebase را اضافه کنید

  1. در کنسول Firebase ، در منوی سمت چپ، گزینه Project Overview را انتخاب کنید. برای انتخاب پلتفرم، روی دکمه Android کلیک کنید. در صورت درخواست نام بسته، com.google.firebase.example.fireeats استفاده کنید.

73d151ed16016421.png

  1. روی ثبت برنامه کلیک کنید و دستورالعمل‌ها را برای دانلود فایل google-services.json دنبال کنید و آن را به پوشه app/ کدی که دانلود کرده‌اید منتقل کنید. سپس روی Next کلیک کنید.

وارد کردن پروژه

اندروید استودیو را باز کنید. روی File > New > Import Project کلیک کنید و پوشه friendlyeats-android را انتخاب کنید.

۴. شبیه‌سازهای Firebase را راه‌اندازی کنید

در این آزمایشگاه کد، شما از مجموعه شبیه‌ساز Firebase برای شبیه‌سازی محلی Cloud Firestore و سایر سرویس‌های Firebase استفاده خواهید کرد. این یک محیط توسعه محلی امن، سریع و بدون هزینه برای ساخت برنامه شما فراهم می‌کند.

نصب رابط خط فرمان فایربیس

ابتدا باید Firebase CLI را نصب کنید. اگر از macOS یا Linux استفاده می‌کنید، می‌توانید دستور cURL زیر را اجرا کنید:

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

اگر از ویندوز استفاده می‌کنید، دستورالعمل‌های نصب را برای دریافت یک فایل باینری مستقل یا نصب از طریق npm مطالعه کنید.

پس از نصب رابط خط فرمان (CLI)، اجرای دستور firebase --version باید نسخه 9.0.0 یا بالاتر را گزارش دهد:

$ firebase --version
9.0.0

ورود

برای اتصال رابط خط فرمان (CLI) به حساب گوگل خود، firebase login اجرا کنید. این کار یک پنجره مرورگر جدید برای تکمیل فرآیند ورود به سیستم باز می‌کند. حتماً همان حسابی را که قبلاً هنگام ایجاد پروژه Firebase خود استفاده کرده‌اید، انتخاب کنید.

از داخل پوشه friendlyeats-android firebase use --add اجرا کنید تا پروژه محلی خود را به پروژه Firebase خود متصل کنید. دستورالعمل‌ها را دنبال کنید تا پروژه‌ای را که قبلاً ایجاد کرده‌اید انتخاب کنید و اگر از شما خواسته شد یک نام مستعار انتخاب کنید، default وارد کنید.

۵. برنامه را اجرا کنید

حالا وقت آن رسیده که برای اولین بار Firebase Emulator Suite و اپلیکیشن اندروید FriendlyEats را اجرا کنیم.

شبیه‌سازها را اجرا کنید

در ترمینال خود، از داخل دایرکتوری 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.

اکنون یک محیط توسعه محلی کامل روی دستگاه شما اجرا می‌شود! حتماً این دستور را در ادامه‌ی کدنویسی اجرا کنید، برنامه‌ی اندروید شما باید به شبیه‌سازها متصل شود.

اتصال برنامه به شبیه‌سازها

فایل‌های util/FirestoreInitializer.kt و util/AuthInitializer.kt را در اندروید استودیو باز کنید. این فایل‌ها حاوی منطق اتصال SDKهای فایربیس به شبیه‌سازهای محلی در حال اجرا روی دستگاه شما، پس از راه‌اندازی برنامه هستند.

در متد create() از کلاس FirestoreInitializer ، این قطعه کد را بررسی کنید:

    // Use emulators only in debug builds
    if (BuildConfig.DEBUG) {
        firestore.useEmulator(FIRESTORE_EMULATOR_HOST, FIRESTORE_EMULATOR_PORT)
    }

ما از BuildConfig استفاده می‌کنیم تا مطمئن شویم که فقط زمانی که برنامه در حالت debug mode) اجرا می‌شود، به شبیه‌سازها متصل می‌شویم. وقتی برنامه را در حالت release mode) کامپایل می‌کنیم، این شرط نادرست (false) خواهد بود.

می‌توانیم ببینیم که از متد useEmulator(host, port) برای اتصال Firebase SDK به شبیه‌ساز محلی Firestore استفاده می‌کند. در سراسر برنامه، ما از FirebaseUtil.getFirestore() برای دسترسی به این نمونه از FirebaseFirestore استفاده خواهیم کرد، بنابراین مطمئن هستیم که همیشه هنگام اجرا در حالت debug به شبیه‌ساز Firestore متصل می‌شویم.

برنامه را اجرا کنید

اگر فایل google-services.json را به درستی اضافه کرده باشید، پروژه اکنون باید کامپایل شود. در اندروید استودیو روی Build > Rebuild Project کلیک کنید و مطمئن شوید که هیچ خطایی باقی نمانده است.

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

اکنون با مراجعه به آدرس http://localhost:4000 در مرورگر وب خود، رابط کاربری Emulators را باز کنید. سپس روی تب Authentication کلیک کنید. در این صورت باید حسابی که ایجاد کرده‌اید را مشاهده کنید:

شبیه‌ساز احراز هویت فایربیس

پس از تکمیل فرآیند ورود، باید صفحه اصلی برنامه را مشاهده کنید:

de06424023ffb4b9.png

به زودی مقداری داده برای پر کردن صفحه اصلی اضافه خواهیم کرد.

۶. نوشتن داده‌ها در 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" شروع کردیم. مجموعه‌ها به صورت ضمنی هنگام اضافه شدن اسناد ایجاد می‌شوند، بنابراین نیازی به ایجاد مجموعه قبل از نوشتن داده‌ها نبود.
  • اسناد را می‌توان با استفاده از کلاس‌های داده کاتلین ایجاد کرد، که ما برای ایجاد هر سند رستوران از آنها استفاده می‌کنیم.
  • متد add() یک سند را به مجموعه‌ای با یک شناسه خودکار اضافه می‌کند، بنابراین نیازی به تعیین شناسه منحصر به فرد برای هر رستوران نداشتیم.

حالا دوباره برنامه را اجرا کنید و روی دکمه‌ی «افزودن موارد تصادفی» در منوی سرریز (در گوشه‌ی بالا سمت راست) کلیک کنید تا کدی که نوشتید فراخوانی شود:

۹۵۶۹۱e۹b۷۱ba۵۵e۳.png

حالا با رفتن به آدرس http://localhost:4000 در مرورگر وب خود، رابط کاربری Emulators را باز کنید. سپس روی تب Firestore کلیک کنید. در این صورت باید داده‌هایی را که اضافه کرده‌اید، ببینید:

شبیه‌ساز احراز هویت فایربیس

این داده‌ها ۱۰۰٪ محلی در دستگاه شما هستند. در واقع، پروژه واقعی شما هنوز حتی شامل پایگاه داده Firestore هم نیست! این بدان معناست که می‌توان بدون هیچ مشکلی، تغییر و حذف این داده‌ها را آزمایش کرد.

تبریک می‌گوییم، شما داده‌ها را در Firestore نوشتید! در مرحله بعدی یاد خواهیم گرفت که چگونه این داده‌ها را در برنامه نمایش دهیم.

۷. نمایش داده‌ها از 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 پیکربندی شده است. برنامه را دوباره اجرا کنید و باید رستوران‌هایی را که در مرحله قبل اضافه کرده‌اید، ببینید:

9e45f40faefce5d0.png

حالا به رابط کاربری شبیه‌ساز در مرورگر خود برگردید و یکی از نام‌های رستوران را ویرایش کنید. تقریباً بلافاصله باید تغییر آن را در برنامه ببینید!

۸. مرتب‌سازی و فیلتر کردن داده‌ها

این برنامه در حال حاضر رستوران‌های برتر را در کل مجموعه نمایش می‌دهد، اما در یک برنامه رستوران واقعی، کاربر می‌خواهد داده‌ها را مرتب‌سازی و فیلتر کند. برای مثال، برنامه باید بتواند «برترین رستوران‌های غذاهای دریایی در فیلادلفیا» یا «ارزان‌ترین پیتزا» را نشان دهد.

کلیک روی نوار سفید بالای برنامه، پنجره‌ی فیلترها را نمایش می‌دهد. در این بخش، از کوئری‌های Firestore برای کار کردن این پنجره استفاده خواهیم کرد:

۶۷۸۹۸۵۷۲a۳۵۶۷۲a۵.png

بیایید متد onFilter() از MainFragment.kt را ویرایش کنیم. این متد یک شیء Filters را می‌پذیرد که یک شیء کمکی است که ما برای گرفتن خروجی کادر محاوره‌ای filters ایجاد کرده‌ایم. ما این متد را برای ساخت یک query از filterها تغییر خواهیم داد:

    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
    }

در قطعه کد بالا، ما با اتصال عبارات where و orderBy برای مطابقت با فیلترهای داده شده، یک شیء Query می‌سازیم.

برنامه را دوباره اجرا کنید و فیلتر زیر را برای نمایش محبوب‌ترین رستوران‌های ارزان‌قیمت انتخاب کنید:

7a67a8a400c80c50.png

اکنون باید یک لیست فیلتر شده از رستوران‌ها را ببینید که فقط شامل گزینه‌های ارزان قیمت هستند:

a670188398c3c59.png

اگر تا اینجا پیش رفته‌اید، اکنون یک برنامه مشاهده پیشنهاد رستوران کاملاً کاربردی در Firestore ساخته‌اید! اکنون می‌توانید رستوران‌ها را به صورت بلادرنگ مرتب و فیلتر کنید. در چند بخش بعدی، نظرات مربوط به رستوران‌ها را اضافه خواهیم کرد و قوانین امنیتی را به برنامه اضافه خواهیم کرد.

۹. سازماندهی داده‌ها در زیرمجموعه‌ها

در این بخش، به برنامه رتبه‌بندی اضافه خواهیم کرد تا کاربران بتوانند رستوران‌های مورد علاقه (یا رستوران‌هایی که کمتر مورد علاقه‌شان است) را نقد کنند.

مجموعه‌ها و زیرمجموعه‌ها

تاکنون تمام داده‌های رستوران‌ها را در یک مجموعه سطح بالا به نام "رستوران‌ها" ذخیره کرده‌ایم. وقتی کاربری به یک رستوران امتیاز می‌دهد، می‌خواهیم یک شیء 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() شنونده‌ها (listeners) به task اضافه می‌شوند تا به نتیجه تراکنش پاسخ دهند.

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

78fa16cdf8ef435a.png

با زدن دکمه ارسال، تراکنش آغاز می‌شود. پس از اتمام تراکنش، نقد و بررسی شما در زیر نمایش داده می‌شود و تعداد نقد و بررسی‌های رستوران نیز به‌روزرسانی می‌شود:

f9e670f40bd615b0.png

تبریک! شما اکنون یک اپلیکیشن نقد و بررسی رستوران اجتماعی، محلی و موبایلی دارید که بر روی Cloud Firestore ساخته شده است. شنیده‌ام که این روزها این اپلیکیشن‌ها بسیار محبوب هستند.

۱۰. داده‌های خود را ایمن کنید

تاکنون امنیت این برنامه را در نظر نگرفته‌ایم. از کجا می‌دانیم که کاربران فقط می‌توانند داده‌های صحیح خودشان را بخوانند و بنویسند؟ پایگاه‌های داده Firestore توسط یک فایل پیکربندی به نام Security Rules ایمن شده‌اند.

فایل 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;
      }
    }
  }
}

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

برای مطالعه بیشتر در مورد قوانین امنیتی، به مستندات مراجعه کنید.

۱۱. نتیجه‌گیری

اکنون شما یک برنامه کاملاً کاربردی بر روی Firestore ایجاد کرده‌اید. شما با مهم‌ترین ویژگی‌های Firestore از جمله موارد زیر آشنا شدید:

  • اسناد و مجموعه‌ها
  • خواندن و نوشتن داده‌ها
  • مرتب‌سازی و فیلتر کردن با پرس‌وجوها
  • زیرمجموعه‌ها
  • تراکنش‌ها

اطلاعات بیشتر

برای ادامه یادگیری در مورد Firestore، در اینجا چند مکان خوب برای شروع وجود دارد:

برنامه رستوران موجود در این آزمایشگاه کد، بر اساس برنامه نمونه "Friendly Eats" ساخته شده است. می‌توانید کد منبع آن برنامه را اینجا مرور کنید.

اختیاری: استقرار در محیط عملیاتی

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

۱۲. (اختیاری) برنامه خود را مستقر کنید

تاکنون این برنامه کاملاً محلی بوده است، تمام داده‌ها در مجموعه شبیه‌ساز Firebase موجود است. در این بخش یاد خواهید گرفت که چگونه پروژه Firebase خود را پیکربندی کنید تا این برنامه در محیط عملیاتی کار کند.

احراز هویت فایربیس

در کنسول فایربیس به بخش احراز هویت (Authentication) بروید و روی شروع (Get started) کلیک کنید. به تب روش ورود (Sign-in method) بروید و گزینه ایمیل/رمز عبور (Email/Password) را از ارائه دهندگان بومی (Native providers) انتخاب کنید.

روش ورود با ایمیل/رمز عبور را فعال کنید و روی ذخیره کلیک کنید.

ارائه دهندگان ورود به سیستم.png

فایراستور

ایجاد پایگاه داده

به بخش پایگاه داده Firestore در کنسول بروید و روی ایجاد پایگاه داده کلیک کنید:

  1. وقتی از شما در مورد قوانین امنیتی سوال شد، گزینه شروع در حالت تولید (Production Mode) را انتخاب کنید، ما به زودی این قوانین را به‌روزرسانی خواهیم کرد.
  2. محل پایگاه داده‌ای را که می‌خواهید برای برنامه خود استفاده کنید، انتخاب کنید. توجه داشته باشید که انتخاب محل پایگاه داده یک تصمیم دائمی است و برای تغییر آن باید یک پروژه جدید ایجاد کنید. برای اطلاعات بیشتر در مورد انتخاب محل پروژه، به مستندات مراجعه کنید.

قوانین استقرار

برای پیاده‌سازی قوانین امنیتی که قبلاً نوشتید، دستور زیر را در دایرکتوری codelab اجرا کنید:

$ firebase deploy --only firestore:rules

این کار محتویات firestore.rules را در پروژه شما مستقر می‌کند، که می‌توانید با رفتن به تب 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 کیت توسعه نرم‌افزار Firebase را طوری پیکربندی کرده‌ایم که در حالت اشکال‌زدایی (debug mode) به شبیه‌سازها متصل شود:

    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 خود آزمایش کنید، می‌توانید:

  1. برنامه را در حالت انتشار بسازید و آن را روی یک دستگاه اجرا کنید.
  2. موقتاً BuildConfig.DEBUG با false جایگزین کنید و برنامه را دوباره اجرا کنید.

توجه داشته باشید که برای اتصال صحیح به محیط عملیاتی، ممکن است لازم باشد از برنامه خارج شده و دوباره وارد شوید.