เป้าหมาย
ในโค้ดแล็บนี้คุณจะสร้างแอพแนะนำร้านอาหารบน Android ที่สนับสนุนโดย Cloud Firestore คุณจะได้เรียนรู้วิธีการ:
- อ่านและเขียนข้อมูลไปยัง Firestore จากแอป Android
- รับฟังการเปลี่ยนแปลงในข้อมูล Firestore แบบเรียลไทม์
- ใช้การตรวจสอบสิทธิ์ Firebase และกฎความปลอดภัยเพื่อรักษาความปลอดภัยข้อมูล Firestore
- เขียนคำค้นหา Firestore ที่ซับซ้อน
ข้อกำหนดเบื้องต้น
ก่อนที่จะเริ่ม codelab นี้ตรวจสอบให้แน่ใจว่าคุณมี:
- Android Studio 4.0 หรือสูงกว่า
- โปรแกรมจำลอง Android
- Node.js เวอร์ชัน 10 ขึ้นไป
- Java เวอร์ชัน 8 หรือสูงกว่า
- ลงชื่อเข้าใช้ คอนโซล Firebase ด้วยบัญชี Google ของคุณ
- ใน คอนโซล Firebase ให้คลิก เพิ่มโครงการ
- ดังที่แสดงในการจับภาพหน้าจอด้านล่างป้อนชื่อโครงการ Firebase ของคุณ (ตัวอย่างเช่น "Friendly Eats") แล้วคลิก ดำเนินการต่อ
- คุณอาจถูกขอให้เปิดใช้งาน Google Analytics สำหรับวัตถุประสงค์ของรหัสโค้ดนี้การเลือกของคุณไม่สำคัญ
- หลังจากนั้นสักครู่โปรเจ็กต์ Firebase ของคุณจะพร้อมใช้งาน คลิก ดำเนินการต่อ
ดาวน์โหลดรหัส
รันคำสั่งต่อไปนี้เพื่อโคลนโค้ดตัวอย่างสำหรับ codelab นี้ สิ่งนี้จะสร้างโฟลเดอร์ชื่อ friendlyeats-android
บนเครื่องของคุณ:
$ git clone https://github.com/firebase/friendlyeats-android
หากคุณไม่มีคอมไพล์ในเครื่องคุณสามารถดาวน์โหลดโค้ดได้โดยตรงจาก GitHub
นำเข้า โครงการไปยัง Android Studio คุณอาจจะเห็นข้อผิดพลาดในการคอมไพล์หรืออาจเป็นคำเตือนเกี่ยวกับไฟล์ google-services.json
หายไป เราจะแก้ไขในส่วนถัดไป
เพิ่มการกำหนดค่า Firebase
- ใน คอนโซล Firebase ให้เลือก ภาพรวมโครงการ ในการนำทางด้านซ้าย คลิกปุ่ม Android เพื่อเลือกแพลตฟอร์ม เมื่อได้รับแจ้งชื่อแพ็กเกจให้ใช้
com.google.firebase.example.fireeats
- คลิก ลงทะเบียนแอพ และทำตามคำแนะนำเพื่อดาวน์โหลดไฟล์
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 ในเว็บเบราว์เซอร์ของคุณ จากนั้นคลิกที่แท็บการ รับรองความถูกต้อง และคุณจะเห็นบัญชีที่คุณเพิ่งสร้างขึ้น:
เมื่อคุณเสร็จสิ้นขั้นตอนการลงชื่อเข้าใช้แล้วคุณจะเห็นหน้าจอหลักของแอพ:
เร็ว ๆ นี้เราจะเพิ่มข้อมูลบางอย่างเพื่อเติมข้อมูลในหน้าจอหลัก
ในส่วนนี้เราจะเขียนข้อมูลบางส่วนไปยัง 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 ที่ไม่ซ้ำกันสำหรับร้านอาหารแต่ละแห่ง
ตอนนี้เรียกใช้แอปอีกครั้งและคลิกปุ่ม "เพิ่มรายการสุ่ม" ในเมนูรายการเพิ่มเติมเพื่อเรียกใช้รหัสที่คุณเพิ่งเขียน:
ตอนนี้เปิด Emulators UI โดยไปที่ http: // localhost: 4000 ในเว็บเบราว์เซอร์ของคุณ จากนั้นคลิกที่แท็บ Firestore และคุณจะเห็นข้อมูลที่คุณเพิ่งเพิ่ม:
ข้อมูลนี้อยู่ในเครื่องของคุณ 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 เรียกใช้ แอปอีกครั้งและคุณจะเห็นร้านอาหารที่คุณเพิ่มไว้ในขั้นตอนก่อนหน้า:
กลับไปที่ Emulator UI ในเบราว์เซอร์ของคุณและแก้ไขชื่อร้านอาหาร คุณควรเห็นการเปลี่ยนแปลงในแอปเกือบจะในทันที!
ปัจจุบันแอปแสดงร้านอาหารที่ติดอันดับต้น ๆ ทั่วทั้งคอลเลคชัน แต่ในแอปร้านอาหารจริงผู้ใช้ต้องการจัดเรียงและกรองข้อมูล ตัวอย่างเช่นแอปควรสามารถแสดง "ร้านอาหารทะเลยอดนิยมในฟิลาเดลเฟีย" หรือ "พิซซ่าราคาแพงน้อยที่สุด"
การคลิกแถบสีขาวที่ด้านบนของแอปจะแสดงกล่องโต้ตอบตัวกรอง ในส่วนนี้เราจะใช้คำสั่ง Firestore เพื่อให้กล่องโต้ตอบนี้ทำงานได้:
มาแก้ไข 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
ข้อเพื่อให้ตรงกับฟิลเตอร์ที่กำหนด
เรียกใช้ แอปอีกครั้งและเลือกตัวกรองต่อไปนี้เพื่อแสดงร้านอาหารราคาถูกยอดนิยม:
ตอนนี้คุณควรเห็นรายชื่อร้านอาหารที่กรองแล้วซึ่งมีตัวเลือกราคาต่ำเท่านั้น:
หากคุณมาถึงจุดนี้ตอนนี้คุณได้สร้างแอพแนะนำร้านอาหารที่ใช้งานได้เต็มรูปแบบบน 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()
จะถูกเพิ่มเข้าไปในงานเพื่อตอบสนองต่อผลลัพธ์ของธุรกรรม
ตอนนี้ เรียกใช้ แอปอีกครั้งและคลิกที่ร้านอาหารแห่งใดแห่งหนึ่งซึ่งจะแสดงหน้าจอรายละเอียดร้านอาหารขึ้นมา คลิกปุ่ม + เพื่อเริ่มเพิ่มบทวิจารณ์ เพิ่มบทวิจารณ์โดยเลือกดาวจำนวนหนึ่งแล้วป้อนข้อความ
การกดปุ่ม ส่ง จะเริ่มต้นการทำธุรกรรม เมื่อการทำธุรกรรมเสร็จสมบูรณ์คุณจะเห็นบทวิจารณ์ของคุณแสดงอยู่ด้านล่างและการอัปเดตจำนวนบทวิจารณ์ของร้านอาหาร:
ยินดีด้วย! ตอนนี้คุณมีแอปรีวิวร้านอาหารบนมือถือโซเชียลที่สร้างขึ้นบน 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 ให้ไปที่ส่วนการ ตรวจสอบสิทธิ์ และไปที่ แท็บผู้ให้บริการ การ ลงชื่อเข้าใช้
เปิดใช้งานวิธีการลงชื่อเข้าใช้อีเมล:
Firestore
สร้างฐานข้อมูล
ไปที่ส่วน Firestore ของคอนโซลแล้วคลิก สร้างฐานข้อมูล :
- เมื่อได้รับแจ้งเกี่ยวกับกฎความปลอดภัยเลือกที่จะเริ่มใน โหมดล็อก เราจะอัปเดตกฎเหล่านั้นในเร็ว ๆ นี้
- เลือกตำแหน่งฐานข้อมูลที่คุณต้องการใช้สำหรับแอปของคุณ โปรดทราบว่าการเลือกตำแหน่งฐานข้อมูล เป็นการ ตัดสินใจ อย่างถาวร และในการเปลี่ยนแปลงคุณจะต้องสร้างโครงการใหม่ สำหรับข้อมูลเพิ่มเติมเกี่ยวกับการเลือกที่ตั้งโครงการโปรดดู เอกสารประกอบ
ปรับใช้กฎ
ในการปรับใช้กฎความปลอดภัยที่คุณเขียนไว้ก่อนหน้านี้ให้รันคำสั่งต่อไปนี้ในไดเร็กทอรี 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 จริงคุณสามารถ:
- สร้างแอปในโหมดเผยแพร่และเรียกใช้บนอุปกรณ์
- เปลี่ยน
sUseEmulators
ชั่วคราวfalse
และเรียกใช้แอปอีกครั้ง
โปรดทราบว่าคุณอาจต้อง ออกจากระบบ แอปและลงชื่อเข้าใช้อีกครั้งเพื่อเชื่อมต่อกับเวอร์ชันที่ใช้งานจริง