Cloud Firestore Android Codelab

1. Tổng quan

Bàn thắng

Trong bảng mã này, bạn sẽ xây dựng một ứng dụng đề xuất nhà hàng trên Android được hỗ trợ bởi Cloud Firestore. Bạn sẽ học cách:

  • Đọc và ghi dữ liệu vào Firestore từ ứng dụng Android
  • Lắng nghe những thay đổi trong dữ liệu Firestore trong thời gian thực
  • Sử dụng Xác thực Firebase và các quy tắc bảo mật để bảo mật dữ liệu Firestore
  • Viết các truy vấn Firestore phức tạp

Điều kiện tiên quyết

Trước khi bắt đầu codelab này, hãy đảm bảo rằng bạn có:

  • Android Studio 4.0 hoặc cao hơn
  • Trình giả lập Android
  • Phiên bản Node.js 10 hoặc cao hơn
  • Java phiên bản 8 hoặc cao hơn

2. Tạo dự án Firebase

  1. Đăng nhập vào căn cứ hỏa lực console với tài khoản Google của bạn.
  2. Trong căn cứ hỏa lực console , bấm Add dự án.
  3. Như thể hiện trong ảnh chụp màn hình bên dưới, nhập tên cho dự án căn cứ hỏa lực của bạn (ví dụ, "thân thiện Eats"), và nhấn Continue.

9d2f625aebcab6af.png

  1. Bạn có thể được yêu cầu bật Google Analytics, vì các mục đích của bảng mã này, lựa chọn của bạn không quan trọng.
  2. Sau một phút hoặc lâu hơn, dự án Firebase của bạn sẽ sẵn sàng. Nhấp vào Tiếp tục.

3. Thiết lập dự án mẫu

Tải xuống mã

Chạy lệnh sau để sao chép mã mẫu cho codelab này. Điều này sẽ tạo ra một thư mục có tên friendlyeats-android trên máy tính của bạn:

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

Nếu bạn không có git trên máy của mình, bạn cũng có thể tải xuống mã trực tiếp từ GitHub.

Nhập khẩu các dự án vào Android Studio. Bạn có thể sẽ thấy một số lỗi biên dịch hoặc có thể là một cảnh báo về một thiếu google-services.json tập tin. Chúng tôi sẽ sửa điều này trong phần tiếp theo.

Thêm cấu hình Firebase

  1. Trong căn cứ hỏa lực console , chọn Tổng quan dự án trong nav trái. Nhấp vào nút Android để chọn nền tảng này. Khi được nhắc cho một tên gói sử dụng com.google.firebase.example.fireeats

73d151ed16016421.png

  1. Nhấn vào Register App và làm theo hướng dẫn để tải về google-services.json tập tin, và di chuyển nó vào app/ thư mục của mã mẫu. Sau đó nhấn Next.

4. Thiết lập Trình mô phỏng Firebase

Trong codelab này bạn sẽ sử dụng các căn cứ hỏa lực Emulator Suite để địa phương thi đua Mây FireStore và dịch vụ căn cứ hỏa lực khác. Điều này cung cấp một môi trường phát triển cục bộ an toàn, nhanh chóng và miễn phí để xây dựng ứng dụng của bạn.

Cài đặt Firebase CLI

Trước tiên, bạn sẽ cần phải cài đặt các căn cứ hỏa lực CLI . Cách dễ nhất để làm điều này là sử dụng npm :

npm install -g firebase-tools

Nếu bạn không có npm hoặc bạn gặp phải lỗi, đọc các hướng dẫn cài đặt để có được một nhị phân độc lập cho nền tảng của bạn.

Một khi bạn đã cài đặt CLI, chạy firebase --version phải báo cáo một phiên bản của 9.0.0 trở lên:

$ firebase --version
9.0.0

Đăng nhập

Chạy firebase login để kết nối CLI vào tài khoản Google của bạn. Thao tác này sẽ mở ra một cửa sổ trình duyệt mới để hoàn tất quá trình đăng nhập. Đảm bảo chọn cùng một tài khoản bạn đã sử dụng khi tạo dự án Firebase trước đó.

Từ bên trong friendlyeats-android thư mục chạy firebase use --add để kết nối dự án địa phương để dự án căn cứ hỏa lực của bạn. Thực hiện theo các hướng dẫn để chọn dự án mà bạn đã tạo trước đó và nếu được yêu cầu chọn một bí danh nhập default .

5. Chạy ứng dụng

Bây giờ đã đến lúc chạy Bộ trình giả lập Firebase và ứng dụng Android FriendlyEats lần đầu tiên.

Chạy trình giả lập

Trong terminal của bạn từ bên trong friendlyeats-android chạy thư mục firebase emulators:start khởi động các căn cứ hỏa lực Emulators. Bạn sẽ thấy các bản ghi như thế này:

$ 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.

Bây giờ bạn có một môi trường phát triển cục bộ hoàn chỉnh đang chạy trên máy của bạn! Đảm bảo để lệnh này chạy trong phần còn lại của bảng mã, ứng dụng Android của bạn sẽ cần kết nối với trình giả lập.

Kết nối ứng dụng với Trình giả lập

Mở tập tin FirebaseUtil.java trong Android Studio. Tệp này chứa logic để kết nối SDK Firebase với trình giả lập cục bộ đang chạy trên máy của bạn.

Ở đầu tệp, hãy kiểm tra dòng này:

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

Chúng tôi đang sử dụng BuildConfig để đảm bảo chúng tôi chỉ kết nối với các giả lập khi ứng dụng của chúng tôi đang chạy trong debug mode. Khi chúng tôi biên dịch ứng dụng trong release chế độ điều kiện này sẽ là sai lầm.

Bây giờ hãy nhìn vào các getFirestore() phương pháp:

    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;
    }

Chúng ta có thể thấy rằng nó đang sử dụng useEmulator(host, port) phương pháp để kết nối các căn cứ hỏa lực SDK để giả lập FireStore địa phương. Trong suốt ứng dụng chúng ta sẽ sử dụng FirebaseUtil.getFirestore() để truy cập vào trường hợp này của FirebaseFirestore vì vậy chúng tôi chắc chắn rằng chúng tôi luôn kết nối với các giả lập FireStore khi chạy ở debug mode.

Chạy ứng dụng

Nếu bạn đã thêm google-services.json tập đúng cách, dự án bây giờ sẽ biên dịch. Trong Android Studio nhấp chuột Build> Rebuild dự án và đảm bảo rằng không có lỗi còn lại.

Trong Android Studio Chạy ứng dụng trên giả lập Android của bạn. Lúc đầu, bạn sẽ thấy màn hình "Đăng nhập". Bạn có thể sử dụng bất kỳ email và mật khẩu nào để đăng nhập vào ứng dụng. Quá trình đăng nhập này đang kết nối với trình giả lập Xác thực Firebase, vì vậy không có thông tin đăng nhập thực nào được truyền.

Bây giờ mở Emulators UI bằng cách điều hướng đến http: // localhost: 4000 trong trình duyệt web của bạn. Sau đó click vào tab Authentication và bạn sẽ thấy tài khoản mà bạn vừa tạo:

Trình mô phỏng xác thực Firebase

Sau khi hoàn tất quá trình đăng nhập, bạn sẽ thấy màn hình chính của ứng dụng:

de06424023ffb4b9.png

Ngay sau đó, chúng tôi sẽ thêm một số dữ liệu để điền vào màn hình chính.

6. Ghi dữ liệu vào Firestore

Trong phần này, chúng tôi sẽ ghi một số dữ liệu vào Firestore để chúng tôi có thể điền vào màn hình chính hiện đang trống.

Các đối tượng mô hình chính trong ứng dụng của chúng tôi là một nhà hàng (xem model/Restaurant.java ). Dữ liệu Firestore được chia thành tài liệu, bộ sưu tập và bộ sưu tập con. Chúng tôi sẽ lưu trữ mỗi nhà hàng như một tài liệu trong một bộ sưu tập cấp cao nhất được gọi là "restaurants" . Để tìm hiểu thêm về mô hình dữ liệu FireStore, đọc về các tài liệu và các bộ sưu tập trong tài liệu .

Với mục đích trình diễn, chúng tôi sẽ thêm chức năng trong ứng dụng để tạo mười nhà hàng ngẫu nhiên khi chúng tôi nhấp vào nút "Thêm các mục ngẫu nhiên" trong menu mục bổ sung. Mở tập tin MainActivity.java và điền vào các onAddItemsClicked() phương pháp:

    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);
        }
    }

Có một số điều quan trọng cần lưu ý về mã trên:

  • Chúng tôi bắt đầu bằng cách nhận một tham chiếu đến "restaurants" bộ sưu tập. Bộ sưu tập được tạo ngầm khi tài liệu được thêm vào, vì vậy không cần tạo bộ sưu tập trước khi ghi dữ liệu.
  • Tài liệu có thể được tạo bằng POJO, chúng tôi sử dụng để tạo mỗi tài liệu Nhà hàng.
  • Các add() phương pháp bổ sung thêm một tài liệu vào một bộ sưu tập với một ID tự động tạo ra, vì vậy chúng tôi không cần phải xác định một ID duy nhất cho mỗi nhà hàng.

Bây giờ, hãy chạy lại ứng dụng và nhấp vào nút "Thêm các mục ngẫu nhiên" trong menu mục bổ sung để gọi mã bạn vừa viết:

95691e9b71ba55e3.png

Bây giờ mở Emulators UI bằng cách điều hướng đến http: // localhost: 4000 trong trình duyệt web của bạn. Sau đó click vào tab FireStore và bạn sẽ thấy dữ liệu bạn vừa thêm vào:

Trình mô phỏng xác thực Firebase

Dữ liệu này là 100% cục bộ cho máy của bạn. Trên thực tế, dự án thực của bạn thậm chí còn chưa chứa cơ sở dữ liệu Firestore! Điều này có nghĩa là có thể thử nghiệm sửa đổi và xóa dữ liệu này một cách an toàn mà không gây hậu quả.

Xin chúc mừng, bạn vừa ghi dữ liệu vào Firestore! Trong bước tiếp theo, chúng ta sẽ tìm hiểu cách hiển thị dữ liệu này trong ứng dụng.

7. Hiển thị dữ liệu từ Firestore

Trong bước này, chúng ta sẽ tìm hiểu cách lấy dữ liệu từ Firestore và hiển thị nó trong ứng dụng của chúng tôi. Bước đầu tiên để đọc dữ liệu từ FireStore là tạo ra một Query . Sửa đổi các onCreate() phương pháp:

        mFirestore = FirebaseUtil.getFirestore();

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

Bây giờ chúng tôi muốn lắng nghe truy vấn để nhận được tất cả các tài liệu phù hợp và được thông báo về các bản cập nhật trong tương lai theo thời gian thực. Bởi vì mục tiêu cuối cùng của chúng tôi là để ràng buộc dữ liệu này cho một người RecyclerView , chúng ta cần tạo một RecyclerView.Adapter lớp để lắng nghe những dữ liệu.

Mở FirestoreAdapter lớp, đã được thực hiện một phần rồi. Đầu tiên, chúng ta hãy làm cho bộ chuyển đổi thực hiện EventListener và xác định các onEvent chức năng để nó có thể nhận được bản cập nhật cho một truy vấn 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();
    }

  // ...
}

Trên tải ban đầu người nghe sẽ nhận được một ADDED sự kiện cho mỗi tài liệu mới. Khi tập kết quả của truy vấn thay đổi theo thời gian, người nghe sẽ nhận được nhiều sự kiện hơn có chứa các thay đổi. Bây giờ chúng ta hãy hoàn thành việc thực hiện trình nghe. Trước hết phải thêm ba phương pháp mới: onDocumentAdded , onDocumentModified , và trên 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());
    }

Sau đó, gọi những phương pháp mới từ 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();
    }

Cuối cùng thực hiện startListening() phương pháp để gắn người nghe:

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

Giờ đây, ứng dụng đã được định cấu hình đầy đủ để đọc dữ liệu từ Firestore. Chạy ứng dụng một lần nữa và bạn sẽ thấy những nhà hàng bạn thêm vào trong bước trước:

9e45f40faefce5d0.png

Bây giờ quay lại Giao diện người dùng giả lập trong trình duyệt của bạn và chỉnh sửa một trong các tên nhà hàng. Bạn sẽ thấy nó thay đổi trong ứng dụng gần như ngay lập tức!

8. Sắp xếp và lọc dữ liệu

Ứng dụng hiện hiển thị các nhà hàng được xếp hạng cao nhất trong toàn bộ bộ sưu tập, nhưng trong một ứng dụng nhà hàng thực, người dùng sẽ muốn sắp xếp và lọc dữ liệu. Ví dụ: ứng dụng sẽ có thể hiển thị "Các nhà hàng hải sản hàng đầu ở Philadelphia" hoặc "Bánh pizza rẻ nhất".

Nhấp vào thanh màu trắng ở đầu ứng dụng sẽ hiển thị hộp thoại bộ lọc. Trong phần này, chúng tôi sẽ sử dụng các truy vấn Firestore để làm cho hộp thoại này hoạt động:

67898572a35672a5.png

Hãy chỉnh sửa các onFilter() phương pháp MainActivity.java . Phương pháp này chấp nhận một Filters đối tượng mà là một đối tượng helper chúng ta tạo ra để bắt đầu ra của hộp thoại bộ lọc. Chúng tôi sẽ thay đổi phương pháp này để tạo truy vấn từ các bộ lọc:

    @Override
    public void onFilter(Filters filters) {
        // Construct query 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);
    }

Trong đoạn mã trên, chúng ta xây dựng một Query đối tượng bằng cách gắn whereorderBy khoản để phù hợp với các bộ lọc nhất định.

Chạy ứng dụng một lần nữa và chọn lọc sau đây để hiển thị hầu hết các nhà hàng giá thấp phổ biến:

7a67a8a400c80c50.png

Bây giờ bạn sẽ thấy một danh sách các nhà hàng đã lọc chỉ chứa các tùy chọn giá thấp:

a670188398c3c59.png

Nếu bạn đã làm được điều này đến nay, bạn hiện đã xây dựng một ứng dụng xem đề xuất nhà hàng hoạt động đầy đủ trên Firestore! Giờ đây, bạn có thể sắp xếp và lọc các nhà hàng trong thời gian thực. Trong một vài phần tiếp theo, chúng tôi đăng các bài đánh giá và bảo mật cho ứng dụng.

9. Tổ chức dữ liệu trong các bộ sưu tập con

Trong phần này, chúng tôi sẽ thêm xếp hạng vào ứng dụng để người dùng có thể đánh giá các nhà hàng yêu thích (hoặc ít yêu thích nhất) của họ.

Bộ sưu tập và bộ sưu tập con

Cho đến nay, chúng tôi đã lưu trữ tất cả dữ liệu nhà hàng trong một bộ sưu tập cấp cao nhất được gọi là "nhà hàng". Khi tỷ lệ người sử dụng nhà hàng chúng tôi muốn thêm một mới Rating đối tượng để các nhà hàng. Đối với nhiệm vụ này, chúng tôi sẽ sử dụng một bộ sưu tập con. Bạn có thể coi một bộ sưu tập con như một bộ sưu tập được đính kèm với một tài liệu. Vì vậy, mỗi tài liệu nhà hàng sẽ có một bộ sưu tập con xếp hạng đầy đủ các tài liệu xếp hạng. Bộ sưu tập con giúp tổ chức dữ liệu mà không làm đầy tài liệu của chúng tôi hoặc yêu cầu các truy vấn phức tạp.

Để truy cập vào một subcollection, gọi .collection() trên tài liệu gốc:

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

Bạn có thể truy cập và truy vấn bộ sưu tập con giống như với bộ sưu tập cấp cao nhất, không có giới hạn về kích thước hoặc thay đổi hiệu suất. Bạn có thể đọc thêm về mô hình dữ liệu FireStore đây .

Ghi dữ liệu trong một giao dịch

Thêm một Rating đến thích hợp subcollection chỉ cần gọi .add() , nhưng chúng ta cũng cần phải cập nhật Restaurant Đánh giá và tổng số xếp hạng trung bình của đối tượng để phản ánh các dữ liệu mới. Nếu chúng tôi sử dụng các thao tác riêng biệt để thực hiện hai thay đổi này, sẽ có một số điều kiện chủng tộc có thể dẫn đến dữ liệu cũ hoặc không chính xác.

Để đảm bảo rằng xếp hạng được thêm đúng cách, chúng tôi sẽ sử dụng một giao dịch để thêm xếp hạng cho một nhà hàng. Giao dịch này sẽ thực hiện một số hành động:

  • Đọc xếp hạng hiện tại của nhà hàng và tính toán xếp hạng mới
  • Thêm xếp hạng vào bộ sưu tập con
  • Cập nhật xếp hạng trung bình của nhà hàng và số lượng xếp hạng

Mở RestaurantDetailActivity.java và thực hiện các addRating chức năng:

    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;
            }
        });
    }

Các addRating() Hàm trả về một Task đại diện cho toàn bộ giao dịch. Trong onRating() nghe chức năng được bổ sung vào nhiệm vụ để đáp ứng với kết quả của giao dịch.

Bây giờ Chạy ứng dụng một lần nữa và click vào một trong những nhà hàng, mà nên đưa lên màn hình chi tiết nhà hàng. Nhấp vào nút + để bắt đầu thêm một bài đánh giá. Thêm đánh giá bằng cách chọn một số dấu sao và nhập một số văn bản.

78fa16cdf8ef435a.png

Đánh Submit sẽ khởi động giao dịch. Khi giao dịch hoàn tất, bạn sẽ thấy bài đánh giá của mình được hiển thị bên dưới và cập nhật về số lượng bài đánh giá của nhà hàng:

f9e670f40bd615b0.png

Chúc mừng! Giờ đây, bạn có một ứng dụng đánh giá nhà hàng trên thiết bị di động, địa phương, xã hội được xây dựng trên Cloud Firestore. Tôi nghe nói những ngày này rất phổ biến.

10. Bảo mật dữ liệu của bạn

Cho đến nay chúng tôi vẫn chưa xem xét tính bảo mật của ứng dụng này. Làm thế nào để chúng tôi biết rằng người dùng chỉ có thể đọc và ghi đúng dữ liệu của chính họ? FireStore datbases được đảm bảo bằng một tập tin cấu hình được gọi là Quy định an toàn .

Mở firestore.rules tập tin, bạn sẽ thấy như sau:

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;
    }
  }
}

Hãy thay đổi những quy tắc để ngăn chặn dữ liệu không mong muốn acesss hoặc thay đổi, mở firestore.rules tập tin và thay thế nội dung như sau:

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;
      }
    }
  }
}

Các quy tắc này hạn chế quyền truy cập để đảm bảo rằng khách hàng chỉ thực hiện các thay đổi an toàn. Ví dụ, cập nhật đối với tài liệu nhà hàng chỉ có thể thay đổi xếp hạng chứ không phải tên hoặc bất kỳ dữ liệu bất biến nào khác. Xếp hạng chỉ có thể được tạo nếu ID người dùng khớp với người dùng đã đăng nhập, điều này ngăn chặn việc giả mạo.

Để đọc thêm về Quy định an toàn, hãy truy cập các tài liệu hướng dẫn .

11. Kết luận

Bây giờ bạn đã tạo một ứng dụng đầy đủ tính năng trên Firestore. Bạn đã tìm hiểu về các tính năng quan trọng nhất của Firestore bao gồm:

  • Tài liệu và bộ sưu tập
  • Đọc và ghi dữ liệu
  • Sắp xếp và lọc với các truy vấn
  • Bộ sưu tập con
  • Giao dịch

Tìm hiểu thêm

Để tiếp tục tìm hiểu về Firestore, đây là một số nơi tốt để bắt đầu:

Ứng dụng nhà hàng trong bảng mã này dựa trên ứng dụng mẫu "Những người ăn thân thiện". Bạn có thể duyệt mã nguồn cho ứng dụng đó đây .

Tùy chọn: Triển khai đến sản xuất

Cho đến nay ứng dụng này mới chỉ sử dụng Firebase Emulator Suite. Nếu bạn muốn tìm hiểu cách triển khai ứng dụng này cho một dự án Firebase thực, hãy tiếp tục sang bước tiếp theo.

12. (Tùy chọn) Triển khai ứng dụng của bạn

Cho đến nay, ứng dụng này hoàn toàn là ứng dụng cục bộ, tất cả dữ liệu được chứa trong Firebase Emulator Suite. Trong phần này, bạn sẽ tìm hiểu cách định cấu hình dự án Firebase của mình để ứng dụng này hoạt động trong phiên bản sản xuất.

Xác thực Firebase

Trong căn cứ hỏa lực consle đi đến phần xác thực và điều hướng đến Sign-in tab Providers .

Bật phương thức đăng nhập Email:

334ef7f6ff4da4ce.png

Firestore

Tạo nên cơ sở dữ liệu

Điều hướng đến phần FireStore của giao diện điều khiển và nhấn Create Database:

  1. Khi được nhắc về Security Rules chọn để bắt đầu trong Locked Mode, chúng tôi sẽ cập nhật những quy tắc sớm.
  2. Chọn vị trí cơ sở dữ liệu mà bạn muốn sử dụng cho ứng dụng của mình. Lưu ý lựa chọn một vị trí cơ sở dữ liệu là một quyết định vĩnh viễn và thay đổi nó, bạn sẽ phải tạo một dự án mới. Để biết thêm thông tin về việc lựa chọn một vị trí dự án, xem tài liệu hướng dẫn .

Triển khai các quy tắc

Để triển khai Quy tắc bảo mật bạn đã viết trước đó, hãy chạy lệnh sau trong thư mục codelab:

$ firebase deploy --only firestore:rules

Điều này sẽ triển khai các nội dung của firestore.rules để dự án của bạn, mà bạn có thể khẳng định bằng cách điều hướng đến tab Quy tắc trong giao diện điều khiển.

Triển khai các chỉ mục

Ứng dụng FriendlyEats có tính năng sắp xếp và lọc phức tạp, đòi hỏi một số chỉ mục kết hợp tùy chỉnh. Đây có thể được tạo ra bằng tay trong các căn cứ hỏa lực console nhưng nó là đơn giản để viết định nghĩa của chúng trong firestore.indexes.json tập tin và triển khai chúng bằng cách sử dụng căn cứ hỏa lực CLI.

Nếu bạn mở firestore.indexes.json tập tin bạn sẽ thấy rằng các chỉ số cần thiết đã được cung cấp:

{
  "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": []
}

Để triển khai các chỉ mục này, hãy chạy lệnh sau:

$ firebase deploy --only firestore:indexes

Lưu ý rằng việc tạo chỉ mục không diễn ra ngay lập tức, bạn có thể theo dõi tiến trình trong bảng điều khiển Firebase.

Định cấu hình ứng dụng

Trong FirebaseUtil lớp chúng ta đã cấu hình căn cứ hỏa lực SDK để kết nối đến các giả lập khi ở chế độ debug:

public class FirebaseUtil {

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

    // ...
}

Nếu bạn muốn thử nghiệm ứng dụng của mình với dự án Firebase thực của mình, bạn có thể:

  1. Xây dựng ứng dụng ở chế độ phát hành và chạy trên thiết bị.
  2. Tạm thời thay đổi sUseEmulators để false và chạy các ứng dụng một lần nữa.

Lưu ý rằng bạn có thể cần phải đăng xuất khỏi ứng dụng và đăng nhập lại để đúng cách kết nối vào sản xuất.