Cloud Firestore Android Codelab

Sử dụng bộ sưu tập để sắp xếp ngăn nắp các trang Lưu và phân loại nội dung dựa trên lựa chọn ưu tiên của bạn.

1. Sơ lượt

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 trở lên
  • Trình giả lập Android có API 19 trở lên
  • Node.js phiên bản 10 trở lên
  • Java phiên bản 8 trở lên

2. Tạo dự án Firebase

  1. Đăng nhập vào bảng điều khiển Firebase bằng tài khoản Google của bạn.
  2. Trong bảng điều khiển Firebase , nhấp vào Thêm dự án .
  3. Như được hiển thị trong ảnh chụp màn hình bên dưới, hãy nhập tên cho dự án Firebase của bạn (ví dụ: "Người ăn thân thiện") và nhấp vào Tiếp tục .

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. Thao tác này sẽ tạo một thư mục có tên là friendlyeats-android trên máy 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.

Thêm cấu hình Firebase

  1. Trong bảng điều khiển Firebase , chọn Tổng quan về dự án ở điều hướng bên trái. Nhấp vào nút Android để chọn nền tảng. Khi được nhắc nhập tên gói, hãy sử dụng com.google.firebase.example.fireeats

73d151ed16016421.png

  1. Nhấp vào Đăng ký ứng dụng và làm theo hướng dẫn để tải xuống tệp google-services.json và di chuyển tệp đó vào app/ thư mục của mã bạn vừa tải xuống. Sau đó nhấp vào Tiếp theo .

Nhập dự án

Mở Android Studio. Nhấp vào Tệp > Mới > Nhập Dự án và chọn thư mục thân thiện-android .

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

Trong codelab này, bạn sẽ sử dụng Firebase Emulator Suite để mô phỏng cục bộ Cloud Firestore và các dịch vụ Firebase 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 cài đặt Firebase CLI . Nếu bạn đang sử dụng macOS hoặc Linux, bạn có thể chạy lệnh cURL sau:

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

Nếu bạn đang sử dụng Windows, hãy đọc hướng dẫn cài đặt để nhận tệp nhị phân độc lập hoặc cài đặt qua npm .

Khi bạn đã cài đặt CLI, chạy firebase --version sẽ báo cáo phiên bản 9.0.0 trở lên:

$ firebase --version
9.0.0

Đăng nhập

Chạy firebase login để kết nối CLI với 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 thư mục friendlyeats-android hãy chạy firebase use --add để kết nối dự án cục bộ với dự án Firebase của bạn. Làm theo lời nhắc để chọn dự án bạn đã tạo trước đó và nếu được yêu cầu chọn bí danh, hãy nhập default .

5. Chạy ứng dụng

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

Chạy trình giả lập

Trong thiết bị đầu cuối của bạn từ trong thư mục friendlyeats-android , chạy firebase emulators:start Firebase. 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ở các tệp util/FirestoreInitializer.ktutil/AuthInitializer.kt trong Android Studio. Các 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 khi khởi động ứng dụng.

Trên phương thức create() của lớp FirestoreInitializer , hãy kiểm tra đoạn mã này:

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

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

Chúng ta có thể thấy rằng nó đang sử dụng phương useEmulator(host, port) để kết nối SDK Firebase với trình giả lập Firestore cục bộ. Trong suốt ứng dụng, chúng tôi sẽ sử dụng FirebaseUtil.getFirestore() để truy cập phiên bản FirebaseFirestore này, 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 trình giả lập Firestore khi chạy ở chế độ debug .

Chạy ứng dụng

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

Trong Android Studio Chạy ứng dụng trên trình 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ờ, hãy mở giao diện người dùng Trình giả lập bằng cách điều hướng đến http: // localhost: 4000 trong trình duyệt web của bạn. Sau đó nhấp vào tab Xác thực và bạn sẽ thấy tài khoản 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.

Đố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.kt ). 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 dưới dạng tài liệu trong 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, hãy đọc về các tài liệu và 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 MainFragment.kt và thay thế nội dung trong phương thức onAddItemsClicked() bằng:

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

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 tham khảo bộ sưu tập "restaurants" . Bộ sưu tập được tạo ngầm định 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 cách sử dụng các lớp dữ liệu Kotlin mà chúng tôi sử dụng để tạo mỗi tài liệu Nhà hàng.
  • Phương thức add() thêm tài liệu vào bộ sưu tập có ID được tạo tự động, vì vậy chúng tôi không cần chỉ đị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óc trên cùng bên phải) để gọi mã bạn vừa viết:

95691e9b71ba55e3.png

Bây giờ, hãy mở giao diện người dùng Trình giả lập bằng cách điều hướng đến http: // localhost: 4000 trong trình duyệt web của bạn. Sau đó nhấp vào tab Firestore và bạn sẽ thấy dữ liệu bạn vừa thêm:

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à bạn 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 ra hậu quả gì.

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 một Query . Mở tệp MainFragment.kt và thêm mã sau vào đầu phương thức onViewCreated() :

        // Firestore
        firestore = Firebase.firestore

        // Get the 50 highest rated restaurants
        query = firestore.collection("restaurants")
            .orderBy("avgRating", Query.Direction.DESCENDING)
            .limit(LIMIT.toLong())

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. Vì mục tiêu cuối cùng của chúng tôi là liên kết dữ liệu này với RecyclerView , chúng tôi cần tạo một lớp RecyclerView.Adapter để lắng nghe dữ liệu.

Mở lớp FirestoreAdapter , lớp này đã được triển khai một phần. Đầu tiên, hãy làm cho bộ điều hợp triển khai EventListener và xác định hàm onEvent để nó có thể nhận các bản cập nhật cho một truy vấn 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()
    }
    
    // ...
}

Khi tải ban đầu, người nghe sẽ nhận được một sự kiện ADDED cho mỗi tài liệu mới. Do tập hợ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 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. Đầu tiên, hãy thêm ba phương thức mới: onDocumentAdded , onDocumentModifiedonDocumentRemoved :

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

Sau đó, gọi các phương thức mới này từ 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()
    }

Cuối cùng triển khai phương thức startListening() để đính kèm trình lắng nghe:

    fun startListening() {
        if (registration == null) {
            registration = query.addSnapshotListener(this)
        }
    }

Giờ đây, ứng dụng đã được cấu hình đầy đủ để đọc dữ liệu từ Firestore. Chạy lại ứng dụng và bạn sẽ thấy các nhà hàng bạn đã thêm ở 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 phương thức onFilter() của MainFragment.kt . Phương thức này chấp nhận một đối tượng Filters là một đối tượng trợ giúp mà chúng tôi đã tạo để thu nhận kết quả đầ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 một truy vấn từ các bộ lọc:

    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
    }

Trong đoạn mã trên, chúng tôi tạo đối tượng Query bằng cách đính kèm mệnh đề whereorderBy để khớp với các bộ lọc đã cho.

Chạy lại ứng dụng và chọn bộ lọc sau để hiển thị các nhà hàng giá rẻ phổ biến nhất:

7a67a8a400c80c50.png

Bây giờ, bạn sẽ thấy một danh sách các nhà hàng đã được 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 sẽ thêm các bài đánh giá về các nhà hàng và thêm các quy tắc 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 người dùng xếp hạng nhà hàng, chúng tôi muốn thêm đối tượng Rating mới vào 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 xếp hạng con đầ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 bộ sưu tập con, hãy gọi .collection() trên tài liệu mẹ:

val subRef = firestore.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 tại đây .

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

Việc thêm Rating vào bộ sưu tập con thích hợp chỉ yêu cầu gọi .add() , nhưng chúng tôi cũng cần cập nhật xếp hạng trung bình của đối tượng Restaurant và số lượng xếp hạng để phản ánh 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ở RestaurantDetailFragment.kt và triển khai chức năng 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
        }
    }

Hàm addRating() trả về một Task đại diện cho toàn bộ giao dịch. Trong hàm onRating() các trình nghe được thêm vào nhiệm vụ để phản hồi kết quả của giao dịch.

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

78fa16cdf8ef435a.png

Nhấn Gửi sẽ bắt đầu 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 sao chúng ta biết rằng người dùng chỉ có thể đọc và ghi đúng dữ liệu của chính mình? Cơ sở dữ liệu Firestore được bảo mật bằng tệp cấu hình được gọi là Quy tắc bảo mật .

Mở tệp firestore.rules , 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 các quy tắc này để ngăn chặn sự xâm nhập hoặc thay đổi dữ liệu không mong muốn, hãy mở tệp firestore.rules và thay thế nội dung bằng nội dung 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 tắc bảo mật, hãy truy cập tài liệu .

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 đó tại đâ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 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 bảng điều khiển Firebase, hãy chuyển đến phần Xác thực và nhấp vào Bắt đầu . Điều hướng đến tab Phương thức đăng nhập và chọn tùy chọn Email / Mật khẩu từ các nhà cung cấp Bản địa .

Bật phương thức đăng nhập Email / Mật khẩu và nhấp vào Lưu .

đăng nhập-nhà cung cấp.png

Firestore

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

Điều hướng đến phần Cơ sở dữ liệu Firestore của bảng điều khiển và nhấp vào Tạo cơ sở dữ liệu :

  1. Khi được nhắc về Quy tắc bảo mật, hãy chọn bắt đầu ở Chế độ sản xuất , chúng tôi sẽ sớm cập nhật các quy tắc đó.
  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 ý rằng việc chọn một vị trí cơ sở dữ liệu là một quyết định lâu dài 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 chọn vị trí dự án, hãy xem tài liệu .

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

Thao tác này sẽ triển khai nội dung của firestore.rules cho dự án của bạn, bạn có thể xác nhận nội dung này bằng cách điều hướng đến tab Quy tắc trong bảng đ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. Chúng có thể được tạo bằng tay trong bảng điều khiển Firebase nhưng đơn giản hơn là viết định nghĩa của chúng trong tệp firestore.indexes.json và triển khai chúng bằng Firebase CLI.

Nếu bạn mở tệp firestore.indexes.json , bạn sẽ thấy rằng các chỉ mục bắt buộc đã đượ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 các tệp use util/FirestoreInitializer.ktutil/AuthInitializer.kt chúng tôi đã định cấu hình SDK Firebase để kết nối với trình giả lập khi ở chế độ gỡ lỗi:

    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
    }

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. Tạo ứng dụng ở chế độ phát hành và chạy trên thiết bị.
  2. Tạm thời thay thế BuildConfig.DEBUG bằng false và chạy lại ứng dụng.

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 để kết nối đúng cách với sản xuất.