Google is committed to advancing racial equity for Black communities. See how.
หน้านี้ได้รับการแปลโดย Cloud Translation API
Switch to English

Cloud Firestore Android Codelab

เป้าหมาย

ในโค้ดแล็บนี้คุณจะสร้างแอพแนะนำร้านอาหารบน Android ที่สนับสนุนโดย Cloud Firestore คุณจะได้เรียนรู้วิธีการ:

  • อ่านและเขียนข้อมูลไปยัง Firestore จากแอป Android
  • รับฟังการเปลี่ยนแปลงในข้อมูล Firestore แบบเรียลไทม์
  • ใช้การตรวจสอบสิทธิ์ Firebase และกฎความปลอดภัยเพื่อรักษาความปลอดภัยข้อมูล Firestore
  • เขียนคำค้นหา Firestore ที่ซับซ้อน

ข้อกำหนดเบื้องต้น

ก่อนที่จะเริ่ม codelab นี้ตรวจสอบให้แน่ใจว่าคุณมี:

  • Android Studio 4.0 หรือสูงกว่า
  • โปรแกรมจำลอง Android
  • Node.js เวอร์ชัน 10 ขึ้นไป
  • Java เวอร์ชัน 8 หรือสูงกว่า
  1. ลงชื่อเข้าใช้ คอนโซล Firebase ด้วยบัญชี Google ของคุณ
  2. ใน คอนโซล Firebase ให้คลิก เพิ่มโครงการ
  3. ดังที่แสดงในการจับภาพหน้าจอด้านล่างป้อนชื่อโครงการ Firebase ของคุณ (ตัวอย่างเช่น "Friendly Eats") แล้วคลิก ดำเนินการต่อ

9d2f625aebcab6af.png

  1. คุณอาจถูกขอให้เปิดใช้งาน Google Analytics สำหรับวัตถุประสงค์ของรหัสโค้ดนี้การเลือกของคุณไม่สำคัญ
  2. หลังจากนั้นสักครู่โปรเจ็กต์ Firebase ของคุณจะพร้อมใช้งาน คลิก ดำเนินการต่อ

ดาวน์โหลดรหัส

รันคำสั่งต่อไปนี้เพื่อโคลนโค้ดตัวอย่างสำหรับ codelab นี้ สิ่งนี้จะสร้างโฟลเดอร์ชื่อ friendlyeats-android บนเครื่องของคุณ:

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

หากคุณไม่มีคอมไพล์ในเครื่องคุณสามารถดาวน์โหลดโค้ดได้โดยตรงจาก GitHub

นำเข้า โครงการไปยัง Android Studio คุณอาจจะเห็นข้อผิดพลาดในการคอมไพล์หรืออาจเป็นคำเตือนเกี่ยวกับไฟล์ google-services.json หายไป เราจะแก้ไขในส่วนถัดไป

เพิ่มการกำหนดค่า Firebase

  1. ใน คอนโซล Firebase ให้เลือก ภาพรวมโครงการ ในการนำทางด้านซ้าย คลิกปุ่ม Android เพื่อเลือกแพลตฟอร์ม เมื่อได้รับแจ้งชื่อแพ็กเกจให้ใช้ com.google.firebase.example.fireeats

73d151ed16016421.png

  1. คลิก ลงทะเบียนแอพ และทำตามคำแนะนำเพื่อดาวน์โหลดไฟล์ google-services.json นั้นย้ายไปไว้ใน app/ โฟลเดอร์ของโค้ดตัวอย่าง จากนั้นคลิก ถัดไป

ใน codelab นี้คุณจะใช้ Firebase Emulator Suite เพื่อจำลอง Cloud Firestore และบริการอื่น ๆ ของ Firebase ในเครื่อง สิ่งนี้ให้สภาพแวดล้อมการพัฒนาในท้องถิ่นที่ปลอดภัยรวดเร็วและฟรีเพื่อสร้างแอปของคุณ

ติดตั้ง Firebase CLI

ก่อนอื่นคุณจะต้องติดตั้ง Firebase CLI วิธีที่ง่ายที่สุดคือใช้ npm :

npm install -g firebase-tools

หากคุณไม่มี npm หรือพบข้อผิดพลาดโปรดอ่าน คำแนะนำ ในการ ติดตั้ง เพื่อรับไบนารีแบบสแตนด์อโลนสำหรับแพลตฟอร์มของคุณ

เมื่อคุณติดตั้ง CLI แล้วการเรียกใช้ firebase --version ควรรายงานเวอร์ชัน 9.0.0 ขึ้นไป:

$ firebase --version
9.0.0

เข้าสู่ระบบ

เรียกใช้การ firebase login เพื่อเชื่อมต่อ CLI กับบัญชี Google ของคุณ การดำเนินการนี้จะเปิดหน้าต่างเบราว์เซอร์ใหม่เพื่อทำขั้นตอนการเข้าสู่ระบบให้เสร็จสิ้น อย่าลืมเลือกบัญชีเดียวกับที่คุณใช้ในการสร้างโครงการ Firebase ก่อนหน้านี้

จากภายในโฟลเดอร์ friendlyeats-android เรียก firebase use --add เพื่อเชื่อมต่อโปรเจ็กต์ในพื้นที่ของคุณกับโปรเจ็กต์ Firebase ทำตามคำแนะนำเพื่อเลือกโครงการที่คุณสร้างไว้ก่อนหน้านี้และหากระบบขอให้เลือกนามแฝงให้ป้อน default

ตอนนี้ถึงเวลาเรียกใช้ Firebase Emulator Suite และแอป Android FriendlyEats เป็นครั้งแรก

เรียกใช้โปรแกรมจำลอง

ในเทอร์มินัลของคุณจากภายในไดเรกทอรี friendlyeats-android เรียกใช้ firebase emulators:start Firebase firebase emulators:start เพื่อเริ่ม Firebase Emulators คุณควรเห็นบันทึกดังนี้:

$ 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 ส่วนที่เหลือแอพ Android ของคุณจะต้องเชื่อมต่อกับอีมูเลเตอร์

เชื่อมต่อแอพกับอีมูเลเตอร์

เปิดไฟล์ 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 ดังนั้นเราจึงมั่นใจว่าเราจะเชื่อมต่อกับโปรแกรมจำลอง Firestore เสมอเมื่อทำงานในโหมด debug

เรียกใช้แอป

หากคุณเพิ่มไฟล์ google-services.json อย่างถูกต้องโปรเจ็กต์ควรคอมไพล์แล้ว ใน Android Studio ให้คลิก สร้าง > สร้างโครงการใหม่ และตรวจสอบให้แน่ใจว่าไม่มีข้อผิดพลาดเหลืออยู่

ใน Android Studio เรียกใช้ แอปบนโปรแกรมจำลอง Android ของคุณ ในตอนแรกคุณจะเห็นหน้าจอ "ลงชื่อเข้าใช้" คุณสามารถใช้อีเมลและรหัสผ่านใดก็ได้เพื่อลงชื่อเข้าใช้แอป กระบวนการลงชื่อเข้าใช้นี้กำลังเชื่อมต่อกับโปรแกรมจำลองการตรวจสอบสิทธิ์ Firebase ดังนั้นจึงไม่มีการส่งข้อมูลรับรองจริง

ตอนนี้เปิด Emulators UI โดยไปที่ http: // localhost: 4000 ในเว็บเบราว์เซอร์ของคุณ จากนั้นคลิกที่แท็บการ รับรองความถูกต้อง และคุณจะเห็นบัญชีที่คุณเพิ่งสร้างขึ้น:

Firebase Auth Emulator

เมื่อคุณเสร็จสิ้นขั้นตอนการลงชื่อเข้าใช้แล้วคุณจะเห็นหน้าจอหลักของแอพ:

de06424023ffb4b9.png

เร็ว ๆ นี้เราจะเพิ่มข้อมูลบางอย่างเพื่อเติมข้อมูลในหน้าจอหลัก

ในส่วนนี้เราจะเขียนข้อมูลบางส่วนไปยัง 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" คอลเล็กชันถูกสร้างขึ้นโดยปริยายเมื่อมีการเพิ่มเอกสารดังนั้นจึงไม่จำเป็นต้องสร้างคอลเล็กชันก่อนที่จะเขียนข้อมูล
  • เอกสารสามารถสร้างได้โดยใช้ POJO ซึ่งเราใช้ในการสร้างเอกสารร้านอาหารแต่ละรายการ
  • วิธีการ add() จะเพิ่มเอกสารลงในคอลเล็กชันด้วย ID ที่สร้างขึ้นโดยอัตโนมัติดังนั้นเราจึงไม่จำเป็นต้องระบุ ID ที่ไม่ซ้ำกันสำหรับร้านอาหารแต่ละแห่ง

ตอนนี้เรียกใช้แอปอีกครั้งและคลิกปุ่ม "เพิ่มรายการสุ่ม" ในเมนูรายการเพิ่มเติมเพื่อเรียกใช้รหัสที่คุณเพิ่งเขียน:

95691e9b71ba55e3.png

ตอนนี้เปิด Emulators UI โดยไปที่ http: // localhost: 4000 ในเว็บเบราว์เซอร์ของคุณ จากนั้นคลิกที่แท็บ Firestore และคุณจะเห็นข้อมูลที่คุณเพิ่งเพิ่ม:

Firebase Auth Emulator

ข้อมูลนี้อยู่ในเครื่องของคุณ 100% อันที่จริงโครงการจริงของคุณยังไม่มีฐานข้อมูล Firestore ด้วยซ้ำ! ซึ่งหมายความว่าสามารถทดลองแก้ไขและลบข้อมูลนี้ได้อย่างปลอดภัยโดยไม่มีผล

ขอแสดงความยินดีคุณเพิ่งเขียนข้อมูลถึง Firestore! ในขั้นตอนต่อไปเราจะเรียนรู้วิธีแสดงข้อมูลนี้ในแอป

ในขั้นตอนนี้เราจะเรียนรู้วิธีดึงข้อมูลจาก Firestore และแสดงในแอปของเรา ขั้นตอนแรกในการอ่านข้อมูลจาก Firestore คือการสร้าง Query แก้ไข 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 ซึ่งได้นำไปใช้แล้วบางส่วน ขั้นแรกให้อะแด็ปเตอร์ใช้ EventListener และกำหนดฟังก์ชัน onEvent เพื่อให้สามารถรับการอัปเดตแบบสอบถาม 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 สำหรับเอกสารใหม่แต่ละฉบับ เนื่องจากชุดผลลัพธ์ของการสืบค้นเปลี่ยนแปลงตลอดเวลาผู้ฟังจะได้รับเหตุการณ์เพิ่มเติมที่มีการเปลี่ยนแปลง ตอนนี้เรามาดำเนินการกับ Listener ให้เสร็จสิ้น ขั้นแรกให้เพิ่มวิธีการใหม่สามวิธี: 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() เพื่อแนบ Listener:

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

ตอนนี้แอปได้รับการกำหนดค่าอย่างสมบูรณ์เพื่ออ่านข้อมูลจาก Firestore เรียกใช้ แอปอีกครั้งและคุณจะเห็นร้านอาหารที่คุณเพิ่มไว้ในขั้นตอนก่อนหน้า:

9e45f40faefce5d0.png

กลับไปที่ Emulator UI ในเบราว์เซอร์ของคุณและแก้ไขชื่อร้านอาหาร คุณควรเห็นการเปลี่ยนแปลงในแอปเกือบจะในทันที!

ปัจจุบันแอปแสดงร้านอาหารที่ติดอันดับต้น ๆ ทั่วทั้งคอลเลคชัน แต่ในแอปร้านอาหารจริงผู้ใช้ต้องการจัดเรียงและกรองข้อมูล ตัวอย่างเช่นแอปควรสามารถแสดง "ร้านอาหารทะเลยอดนิยมในฟิลาเดลเฟีย" หรือ "พิซซ่าราคาแพงน้อยที่สุด"

การคลิกแถบสีขาวที่ด้านบนของแอปจะแสดงกล่องโต้ตอบตัวกรอง ในส่วนนี้เราจะใช้คำสั่ง Firestore เพื่อให้กล่องโต้ตอบนี้ทำงานได้:

67898572a35672a5.png

มาแก้ไข onFilter() ของ MainActivity.java วิธีนี้ยอมรับออบเจ็กต์ Filters ซึ่งเป็นอ็อบเจ็กต์ตัวช่วยที่เราสร้างขึ้นเพื่อจับเอาท์พุทของกล่องโต้ตอบตัวกรอง เราจะเปลี่ยนวิธีนี้เพื่อสร้างแบบสอบถามจากตัวกรอง:

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

ในตัวอย่างข้างต้นที่เราสร้าง Query วัตถุโดยติด where และ orderBy ข้อเพื่อให้ตรงกับฟิลเตอร์ที่กำหนด

เรียกใช้ แอปอีกครั้งและเลือกตัวกรองต่อไปนี้เพื่อแสดงร้านอาหารราคาถูกยอดนิยม:

7a67a8a400c80c50.png

ตอนนี้คุณควรเห็นรายชื่อร้านอาหารที่กรองแล้วซึ่งมีตัวเลือกราคาต่ำเท่านั้น:

a670188398c3c59.png

หากคุณมาถึงจุดนี้ตอนนี้คุณได้สร้างแอพแนะนำร้านอาหารที่ใช้งานได้เต็มรูปแบบบน Firestore แล้ว! ตอนนี้คุณสามารถจัดเรียงและกรองร้านอาหารได้แบบเรียลไทม์ ในสองสามส่วนถัดไปเราจะโพสต์บทวิจารณ์และความปลอดภัยไปยังแอป

ในส่วนนี้เราจะเพิ่มการให้คะแนนให้กับแอปเพื่อให้ผู้ใช้สามารถตรวจสอบร้านอาหารที่ชื่นชอบ (หรือที่ชอบน้อยที่สุด) ได้

คอลเล็กชันและคอลเล็กชันย่อย

จนถึงขณะนี้เราได้จัดเก็บข้อมูลร้านอาหารทั้งหมดไว้ในคอลเล็กชันระดับบนสุดที่เรียกว่า "ร้านอาหาร" เมื่อผู้ใช้ให้คะแนนร้านอาหารเราต้องการเพิ่มวัตถุการ Rating ใหม่ 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

การกดปุ่ม ส่ง จะเริ่มต้นการทำธุรกรรม เมื่อการทำธุรกรรมเสร็จสมบูรณ์คุณจะเห็นบทวิจารณ์ของคุณแสดงอยู่ด้านล่างและการอัปเดตจำนวนบทวิจารณ์ของร้านอาหาร:

f9e670f40bd615b0.png

ยินดีด้วย! ตอนนี้คุณมีแอปรีวิวร้านอาหารบนมือถือโซเชียลที่สร้างขึ้นบน Cloud Firestore แล้ว ฉันได้ยินว่าสิ่งเหล่านี้เป็นที่นิยมอย่างมากในทุกวันนี้

จนถึงตอนนี้เรายังไม่ได้พิจารณาถึงความปลอดภัยของแอปพลิเคชันนี้ เราจะรู้ได้อย่างไรว่าผู้ใช้สามารถอ่านและเขียนข้อมูลของตัวเองที่ถูกต้องเท่านั้น ฐานข้อมูล Firestore ได้รับการรักษาความปลอดภัยโดยไฟล์คอนฟิกูเรชันที่เรียกว่า Security Rules

เปิดไฟล์ 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;
      }
    }
  }
}

กฎเหล่านี้ จำกัด การเข้าถึงเพื่อให้แน่ใจว่าลูกค้าทำการเปลี่ยนแปลงที่ปลอดภัยเท่านั้น ตัวอย่างเช่นการอัปเดตเอกสารร้านอาหารสามารถเปลี่ยนการให้คะแนนเท่านั้นไม่ใช่ชื่อหรือข้อมูลอื่น ๆ ที่ไม่เปลี่ยนรูป สามารถสร้างการให้คะแนนได้ก็ต่อเมื่อ ID ผู้ใช้ตรงกับผู้ใช้ที่ลงชื่อเข้าใช้ซึ่งป้องกันการปลอมแปลง

หากต้องการอ่านเพิ่มเติมเกี่ยวกับกฎความปลอดภัยโปรดไป ที่เอกสาร

ตอนนี้คุณได้สร้างแอพที่มีคุณสมบัติครบถ้วนที่ด้านบนของ Firestore แล้ว คุณได้เรียนรู้เกี่ยวกับคุณสมบัติที่สำคัญที่สุดของ Firestore ได้แก่ :

  • เอกสารและคอลเล็กชัน
  • การอ่านและเขียนข้อมูล
  • การเรียงลำดับและการกรองด้วยแบบสอบถาม
  • คอลเลกชันย่อย
  • ธุรกรรม

เรียนรู้เพิ่มเติม

หากต้องการเรียนรู้เกี่ยวกับ Firestore ต่อไปนี่คือจุดเริ่มต้นที่ดี:

แอปร้านอาหารใน codelab นี้ใช้ตัวอย่างแอปพลิเคชัน "Friendly Eats" คุณสามารถเรียกดูซอร์สโค้ดของแอพนั้นได้ที่ นี่

ทางเลือก: ปรับใช้กับการผลิต

จนถึงตอนนี้แอปนี้ใช้เฉพาะ Firebase Emulator Suite เท่านั้น หากคุณต้องการเรียนรู้วิธีทำให้แอปนี้ใช้งานได้กับโปรเจ็กต์ Firebase จริงให้ทำตามขั้นตอนต่อไป

จนถึงตอนนี้แอปนี้อยู่ในพื้นที่ทั้งหมดข้อมูลทั้งหมดจะอยู่ใน Firebase Emulator Suite ในส่วนนี้คุณจะได้เรียนรู้วิธีกำหนดค่าโปรเจ็กต์ Firebase เพื่อให้แอปนี้ใช้งานได้จริง

การตรวจสอบสิทธิ์ Firebase

ใน Firebase Consle ให้ไปที่ส่วนการ ตรวจสอบสิทธิ์ และไปที่ แท็บผู้ให้บริการ การ ลงชื่อเข้าใช้

เปิดใช้งานวิธีการลงชื่อเข้าใช้อีเมล:

334ef7f6ff4da4ce.png

Firestore

สร้างฐานข้อมูล

ไปที่ส่วน Firestore ของคอนโซลแล้วคลิก สร้างฐานข้อมูล :

  1. เมื่อได้รับแจ้งเกี่ยวกับกฎความปลอดภัยเลือกที่จะเริ่มใน โหมดล็อก เราจะอัปเดตกฎเหล่านั้นในเร็ว ๆ นี้
  2. เลือกตำแหน่งฐานข้อมูลที่คุณต้องการใช้สำหรับแอปของคุณ โปรดทราบว่าการเลือกตำแหน่งฐานข้อมูล เป็นการ ตัดสินใจ อย่างถาวร และในการเปลี่ยนแปลงคุณจะต้องสร้างโครงการใหม่ สำหรับข้อมูลเพิ่มเติมเกี่ยวกับการเลือกที่ตั้งโครงการโปรดดู เอกสารประกอบ

ปรับใช้กฎ

ในการปรับใช้กฎความปลอดภัยที่คุณเขียนไว้ก่อนหน้านี้ให้รันคำสั่งต่อไปนี้ในไดเร็กทอรี codelab:

$ firebase deploy --only firestore:rules

สิ่งนี้จะปรับใช้เนื้อหาของ firestore.rules กับโปรเจ็กต์ของคุณซึ่งคุณสามารถยืนยันได้โดยไปที่แท็บ กฎ ในคอนโซล

ปรับใช้ดัชนี

แอป FriendlyEats มีการเรียงลำดับและการกรองที่ซับซ้อนซึ่งต้องใช้ดัชนีผสมที่กำหนดเองจำนวนมาก สิ่งเหล่านี้สามารถสร้างได้ด้วยมือในคอนโซล Firebase แต่จะง่ายกว่าในการเขียนคำจำกัดความในไฟล์ firestore.indexes.json และปรับใช้โดยใช้ Firebase CLI

หากคุณเปิดไฟล์ firestore.indexes.json คุณจะเห็นว่ามีการจัดเตรียมดัชนีที่ต้องการไว้แล้ว:

{
  "indexes": [
    {
      "collectionId": "restaurants",
      "queryScope": "COLLECTION",
      "fields": [
        { "fieldPath": "city", "mode": "ASCENDING" },
        { "fieldPath": "avgRating", "mode": "DESCENDING" }
      ]
    },
    {
      "collectionId": "restaurants",
      "queryScope": "COLLECTION",
      "fields": [
        { "fieldPath": "category", "mode": "ASCENDING" },
        { "fieldPath": "avgRating", "mode": "DESCENDING" }
      ]
    },
    {
      "collectionId": "restaurants",
      "queryScope": "COLLECTION",
      "fields": [
        { "fieldPath": "price", "mode": "ASCENDING" },
        { "fieldPath": "avgRating", "mode": "DESCENDING" }
      ]
    },
    {
      "collectionId": "restaurants",
      "queryScope": "COLLECTION",
      "fields": [
        { "fieldPath": "city", "mode": "ASCENDING" },
        { "fieldPath": "numRatings", "mode": "DESCENDING" }
      ]
    },
    {
      "collectionId": "restaurants",
      "queryScope": "COLLECTION",
      "fields": [
        { "fieldPath": "category", "mode": "ASCENDING" },
        { "fieldPath": "numRatings", "mode": "DESCENDING" }
      ]
    },
    {
      "collectionId": "restaurants",
      "queryScope": "COLLECTION",
      "fields": [
        { "fieldPath": "price", "mode": "ASCENDING" },
        { "fieldPath": "numRatings", "mode": "DESCENDING" }
      ]
    },
    {
      "collectionId": "restaurants",
      "queryScope": "COLLECTION",
      "fields": [
        { "fieldPath": "city", "mode": "ASCENDING" },
        { "fieldPath": "price", "mode": "ASCENDING" }
      ]
    },
    {
      "collectionId": "restaurants",
      "fields": [
        { "fieldPath": "category", "mode": "ASCENDING" },
        { "fieldPath": "price", "mode": "ASCENDING" }
      ]
    }
  ],
  "fieldOverrides": []
}

ในการปรับใช้ดัชนีเหล่านี้ให้รันคำสั่งต่อไปนี้:

$ firebase deploy --only firestore:indexes

โปรดทราบว่าการสร้างดัชนีไม่ได้เกิดขึ้นในทันทีคุณสามารถตรวจสอบความคืบหน้าได้ในคอนโซล Firebase

กำหนดค่าแอป

ในคลาส FirebaseUtil เรากำหนดค่า Firebase SDK ให้เชื่อมต่อกับอีมูเลเตอร์เมื่ออยู่ในโหมดแก้ไขข้อบกพร่อง:

public class FirebaseUtil {

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

    // ...
}

หากคุณต้องการทดสอบแอปของคุณกับโครงการ Firebase จริงคุณสามารถ:

  1. สร้างแอปในโหมดเผยแพร่และเรียกใช้บนอุปกรณ์
  2. เปลี่ยน sUseEmulators ชั่วคราว false และเรียกใช้แอปอีกครั้ง

โปรดทราบว่าคุณอาจต้อง ออกจากระบบ แอปและลงชื่อเข้าใช้อีกครั้งเพื่อเชื่อมต่อกับเวอร์ชันที่ใช้งานจริง