Cloud Firestore Android Codelab

1. بررسی اجمالی

اهداف

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

  • خواندن و نوشتن داده ها در Firestore از یک برنامه Android
  • به تغییرات داده های Firestore در زمان واقعی گوش دهید
  • از Firebase Authentication و قوانین امنیتی برای ایمن سازی داده های Firestore استفاده کنید
  • پرس و جوهای پیچیده Firestore را بنویسید

پیش نیازها

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

  • Android Studio نسخه 4.0 یا بالاتر
  • شبیه ساز اندروید با API 19 یا بالاتر
  • Node.js نسخه 10 یا بالاتر
  • جاوا نسخه 8 یا بالاتر

2. یک پروژه Firebase ایجاد کنید

  1. با حساب Google خود وارد کنسول Firebase شوید.
  2. در کنسول Firebase ، روی افزودن پروژه کلیک کنید.
  3. همانطور که در تصویر زیر نشان داده شده است، یک نام برای پروژه Firebase خود وارد کنید (به عنوان مثال، "Friendly Eats") و روی Continue کلیک کنید.

9d2f625aebcab6af.png

  1. ممکن است از شما خواسته شود که Google Analytics را فعال کنید، برای اهداف این کد کد، انتخاب شما مهم نیست.
  2. پس از یک دقیقه یا بیشتر، پروژه Firebase شما آماده خواهد شد. روی Continue کلیک کنید.

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

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

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

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

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

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

  1. در کنسول Firebase ، نمای کلی پروژه را در ناوبری سمت چپ انتخاب کنید. برای انتخاب پلتفرم روی دکمه اندروید کلیک کنید. وقتی نام بسته از شما خواسته شد از com.google.firebase.example.fireeats استفاده کنید

73d151ed16016421.png

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

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

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

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

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

Firebase CLI را نصب کنید

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

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

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

پس از نصب CLI، اجرای firebase --version باید نسخه 9.0.0 یا بالاتر را گزارش کند:

$ firebase --version
9.0.0

وارد شدن

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

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

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

اکنون زمان اجرای 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.

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

برنامه را به شبیه سازها وصل کنید

فایل FirebaseUtil.java را در Android Studio باز کنید. این فایل حاوی منطق اتصال Firebase SDK به شبیه سازهای محلی در حال اجرا در دستگاه شما است.

در بالای فایل، این خط را بررسی کنید:

    /** Use emulators only in debug builds **/
    private static final boolean sUseEmulators = BuildConfig.DEBUG;

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

حال نگاهی به getFirestore() :

    public static FirebaseFirestore getFirestore() {
        if (FIRESTORE == null) {
            FIRESTORE = FirebaseFirestore.getInstance();

            // Connect to the Cloud Firestore emulator when appropriate. The host '10.0.2.2' is a
            // special IP address to let the Android emulator connect to 'localhost'.
            if (sUseEmulators) {
                FIRESTORE.useEmulator("10.0.2.2", 8080);
            }
        }

        return FIRESTORE;
    }

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

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

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

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

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

شبیه ساز Firebase Auth

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

de06424023ffb4b9.png

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

6. داده ها را در Firestore بنویسید

در این بخش داده هایی را در Firestore می نویسیم تا بتوانیم صفحه اصلی خالی فعلی را پر کنیم.

شی مدل اصلی در برنامه ما یک رستوران است (به model/Restaurant.java مراجعه کنید). داده های Firestore به اسناد، مجموعه ها و زیر مجموعه ها تقسیم می شوند. ما هر رستوران را به عنوان یک سند در یک مجموعه سطح بالا به نام "restaurants" ذخیره خواهیم کرد. برای کسب اطلاعات بیشتر در مورد مدل داده Firestore، اسناد و مجموعه‌ها را در اسناد مطالعه کنید.

برای اهداف نمایشی، وقتی روی دکمه "افزودن موارد تصادفی" در منوی سرریز کلیک می کنیم، عملکردی را به برنامه اضافه می کنیم تا ده رستوران تصادفی ایجاد کنیم. فایل MainActivity.java را باز کنید و محتوای onAddItemsClicked() را با:

    private void onAddItemsClicked() {
        // Get a reference to the restaurants collection
        CollectionReference restaurants = mFirestore.collection("restaurants");

        for (int i = 0; i < 10; i++) {
            // Get a random Restaurant POJO
            Restaurant restaurant = RestaurantUtil.getRandom(this);

            // Add a new document to the restaurants collection
            restaurants.add(restaurant);
        }
    }

چند نکته مهم در مورد کد بالا وجود دارد:

  • ما با ارجاع به مجموعه "restaurants" شروع کردیم. وقتی اسناد اضافه می شوند، مجموعه ها به طور ضمنی ایجاد می شوند، بنابراین نیازی به ایجاد مجموعه قبل از نوشتن داده ها نیست.
  • اسناد را می توان با استفاده از POJOs (Plain Old Java Object) ایجاد کرد، که ما از آن برای ایجاد هر سند رستوران استفاده می کنیم.
  • متد add() سندی را با یک شناسه تولید خودکار به مجموعه اضافه می کند، بنابراین نیازی به تعیین شناسه منحصر به فرد برای هر رستوران نداریم.

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

95691e9b71ba55e3.png

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

شبیه ساز Firebase Auth

این داده ها 100% محلی برای دستگاه شما هستند. در واقع، پروژه واقعی شما هنوز حاوی پایگاه داده Firestore نیست! این بدان معناست که آزمایش با تغییر و حذف این داده‌ها بدون عواقب بی‌خطر است.

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

7. نمایش داده ها از Firestore

در این مرحله یاد می گیریم که چگونه داده ها را از Firestore بازیابی کرده و در برنامه خود نمایش دهیم. اولین قدم برای خواندن داده ها از Firestore ایجاد یک Query است. فایل MainActivity.java را باز کنید و کد زیر را به ابتدای onCreate() اضافه کنید:

        mFirestore = FirebaseUtil.getFirestore();

        // Get the 50 highest rated restaurants
        mQuery = mFirestore.collection("restaurants")
                .orderBy("avgRating", Query.Direction.DESCENDING)
                .limit(LIMIT);

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

کلاس FirestoreAdapter را که قبلاً تا حدی پیاده سازی شده است باز کنید. ابتدا، اجازه دهید آداپتور onEvent EventListener تعریف کنیم تا بتواند به‌روزرسانی‌های یک Query Firestore را دریافت کند:

public abstract class FirestoreAdapter<VH extends RecyclerView.ViewHolder>
        extends RecyclerView.Adapter<VH>
        implements EventListener<QuerySnapshot> { // Add this "implements"

    // ...

    // Add this method
    @Override
    public void onEvent(QuerySnapshot documentSnapshots,
                        FirebaseFirestoreException e) {

        // Handle errors
        if (e != null) {
            Log.w(TAG, "onEvent:error", e);
            return;
        }

        // Dispatch the event
        for (DocumentChange change : documentSnapshots.getDocumentChanges()) {
            // Snapshot of the changed document
            DocumentSnapshot snapshot = change.getDocument();

            switch (change.getType()) {
                case ADDED:
                    // TODO: handle document added
                    break;
                case MODIFIED:
                    // TODO: handle document modified
                    break;
                case REMOVED:
                    // TODO: handle document removed
                    break;
            }
        }

        onDataChanged();
    }

  // ...
}

در بارگذاری اولیه شنونده یک رویداد ADDED برای هر سند جدید دریافت می کند. از آنجایی که مجموعه نتیجه پرس و جو در طول زمان تغییر می کند، شنونده رویدادهای بیشتری حاوی تغییرات را دریافت خواهد کرد. حالا بیایید اجرای شنونده را به پایان برسانیم. ابتدا سه روش جدید اضافه کنید: onDocumentAdded ، onDocumentModified و onDocumentRemoved :

    protected void onDocumentAdded(DocumentChange change) {
        mSnapshots.add(change.getNewIndex(), change.getDocument());
        notifyItemInserted(change.getNewIndex());
    }

    protected void onDocumentModified(DocumentChange change) {
        if (change.getOldIndex() == change.getNewIndex()) {
            // Item changed but remained in same position
            mSnapshots.set(change.getOldIndex(), change.getDocument());
            notifyItemChanged(change.getOldIndex());
        } else {
            // Item changed and changed position
            mSnapshots.remove(change.getOldIndex());
            mSnapshots.add(change.getNewIndex(), change.getDocument());
            notifyItemMoved(change.getOldIndex(), change.getNewIndex());
        }
    }

    protected void onDocumentRemoved(DocumentChange change) {
        mSnapshots.remove(change.getOldIndex());
        notifyItemRemoved(change.getOldIndex());
    }

سپس این متدهای جدید را از onEvent :

    @Override
    public void onEvent(QuerySnapshot documentSnapshots,
                        FirebaseFirestoreException e) {

        // ...

        // Dispatch the event
        for (DocumentChange change : documentSnapshots.getDocumentChanges()) {
            // Snapshot of the changed document
            DocumentSnapshot snapshot = change.getDocument();

            switch (change.getType()) {
                case ADDED:
                    onDocumentAdded(change); // Add this line
                    break;
                case MODIFIED:
                    onDocumentModified(change); // Add this line
                    break;
                case REMOVED:
                    onDocumentRemoved(change); // Add this line
                    break;
            }
        }

        onDataChanged();
    }

در نهایت startListening() را برای پیوست کردن شنونده پیاده سازی کنید:

    public void startListening() {
        if (mQuery != null && mRegistration == null) {
            mRegistration = mQuery.addSnapshotListener(this);
        }
    }

اکنون برنامه به طور کامل برای خواندن داده ها از Firestore پیکربندی شده است. برنامه را دوباره اجرا کنید و باید رستوران هایی را که در مرحله قبل اضافه کرده اید ببینید:

9e45f40faefce5d0.png

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

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

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

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

67898572a35672a5.png

بیایید onFilter() MainActivity.java را ویرایش کنیم. این روش یک شی Filters را می پذیرد که یک شی کمکی است که ما برای گرفتن خروجی گفتگوی فیلترها ایجاد کرده ایم. ما این روش را برای ساخت یک پرس و جو از فیلترها تغییر می دهیم:

    @Override
    public void onFilter(Filters filters) {
        // Construct basic query
        Query query = mFirestore.collection("restaurants");

        // Category (equality filter)
        if (filters.hasCategory()) {
            query = query.whereEqualTo("category", filters.getCategory());
        }

        // City (equality filter)
        if (filters.hasCity()) {
            query = query.whereEqualTo("city", filters.getCity());
        }

        // Price (equality filter)
        if (filters.hasPrice()) {
            query = query.whereEqualTo("price", filters.getPrice());
        }

        // Sort by (orderBy with direction)
        if (filters.hasSortBy()) {
            query = query.orderBy(filters.getSortBy(), filters.getSortDirection());
        }

        // Limit items
        query = query.limit(LIMIT);

        // Update the query
        mQuery = query;
        mAdapter.setQuery(query);

        // Set header
        mCurrentSearchView.setText(Html.fromHtml(filters.getSearchDescription(this)));
        mCurrentSortByView.setText(filters.getOrderDescription(this));

        // Save filters
        mViewModel.setFilters(filters);
    }

در قطعه بالا، یک شی Query با where کردن عبارت‌های Where و orderBy برای مطابقت با فیلترهای داده شده ایجاد می‌کنیم.

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

7a67a8a400c80c50.png

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

a670188398c3c59.png

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

9. داده ها را در زیر مجموعه ها سازماندهی کنید

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

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

تا کنون ما تمام داده های رستوران را در یک مجموعه سطح بالا به نام "رستوران" ذخیره کرده ایم. وقتی کاربر به یک رستوران امتیاز می دهد، می خواهیم یک شی Rating جدید به رستوران ها اضافه کنیم. برای این کار از یک زیر مجموعه استفاده می کنیم. شما می توانید یک زیر مجموعه را به عنوان مجموعه ای در نظر بگیرید که به یک سند پیوست شده است. بنابراین هر سند رستوران یک زیرمجموعه رتبه بندی پر از اسناد رتبه بندی خواهد داشت. مجموعه‌های فرعی به سازماندهی داده‌ها کمک می‌کنند بدون اینکه اسناد ما را متورم کند یا نیاز به جستجوهای پیچیده داشته باشد.

برای دسترسی به یک زیر مجموعه، .collection() را در سند والد فراخوانی کنید:

CollectionReference subRef = mFirestore.collection("restaurants")
        .document("abc123")
        .collection("ratings");

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

نوشتن داده ها در یک تراکنش

افزودن یک Rating به زیر مجموعه مناسب فقط نیازمند فراخوانی .add() است، اما همچنین باید میانگین رتبه‌بندی شی Restaurant و تعداد رتبه‌بندی‌ها را به‌روزرسانی کنیم تا داده‌های جدید را منعکس کنیم. اگر از عملیات جداگانه برای ایجاد این دو تغییر استفاده کنیم، تعدادی شرایط مسابقه وجود دارد که می تواند منجر به داده های قدیمی یا نادرست شود.

برای اطمینان از اینکه رتبه‌بندی‌ها به درستی اضافه می‌شوند، از تراکنش برای افزودن رتبه‌بندی به رستوران استفاده می‌کنیم. این تراکنش چند عمل انجام می دهد:

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

RestaurantDetailActivity.java را باز کنید و تابع addRating را پیاده سازی کنید:

    private Task<Void> addRating(final DocumentReference restaurantRef,
                                 final Rating rating) {
        // Create reference for new rating, for use inside the transaction
        final DocumentReference ratingRef = restaurantRef.collection("ratings")
                .document();

        // In a transaction, add the new rating and update the aggregate totals
        return mFirestore.runTransaction(new Transaction.Function<Void>() {
            @Override
            public Void apply(Transaction transaction)
                    throws FirebaseFirestoreException {

                Restaurant restaurant = transaction.get(restaurantRef)
                        .toObject(Restaurant.class);

                // Compute new number of ratings
                int newNumRatings = restaurant.getNumRatings() + 1;

                // Compute new average rating
                double oldRatingTotal = restaurant.getAvgRating() *
                        restaurant.getNumRatings();
                double newAvgRating = (oldRatingTotal + rating.getRating()) /
                        newNumRatings;

                // Set new restaurant info
                restaurant.setNumRatings(newNumRatings);
                restaurant.setAvgRating(newAvgRating);

                // Commit to Firestore
                transaction.set(restaurantRef, restaurant);
                transaction.set(ratingRef, rating);

                return null;
            }
        });
    }

تابع addRating() یک Task را برمی گرداند که کل تراکنش را نشان می دهد. در تابع onRating() شنوندگان به وظیفه اضافه می شوند تا به نتیجه تراکنش پاسخ دهند.

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

78fa16cdf8ef435a.png

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

f9e670f40bd615b0.png

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

10. داده های خود را ایمن کنید

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

فایل firestore.rules را باز کنید، باید موارد زیر را مشاهده کنید:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      //
      // WARNING: These rules are insecure! We will replace them with
      // more secure rules later in the codelab
      //
      allow read, write: if request.auth != null;
    }
  }
}

بیایید این قوانین را تغییر دهیم تا از دسترسی یا تغییرات ناخواسته به داده جلوگیری کنیم، فایل 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 از جمله:

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

بیشتر بدانید

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

برنامه رستوران در این کد لبه بر اساس برنامه مثال "Friendly Eats" است. می‌توانید کد منبع آن برنامه را در اینجا مرور کنید.

اختیاری: استقرار در تولید

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

12. (اختیاری) برنامه خود را اجرا کنید

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

احراز هویت Firebase

در کنسول Firebase به بخش Authentication بروید و روی شروع کلیک کنید. به برگه روش ورود به سیستم بروید و گزینه ایمیل/گذرواژه را از ارائه دهندگان بومی انتخاب کنید.

روش ورود به سیستم ایمیل/گذرواژه را فعال کنید و روی ذخیره کلیک کنید.

sign-in-providers.png

فایر استور

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

به بخش Firestore Database کنسول بروید و روی Create Database کلیک کنید:

  1. وقتی در مورد قوانین امنیتی از شما خواسته شد که در حالت تولید شروع به کار کنید، به زودی آن قوانین را به‌روزرسانی خواهیم کرد.
  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 نظارت کنید.

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

در کلاس FirebaseUtil ما Firebase SDK را برای اتصال به شبیه سازها در حالت اشکال زدایی پیکربندی کردیم:

public class FirebaseUtil {

    /** Use emulators only in debug builds **/
    private static final boolean sUseEmulators = BuildConfig.DEBUG;

    // ...
}

اگر می خواهید برنامه خود را با پروژه Firebase واقعی خود آزمایش کنید، می توانید:

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

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