1. ภาพรวม
เป้าหมาย
ใน Codelab นี้ คุณจะสร้างแอปแนะนำร้านอาหารบน Android ซึ่งสนับสนุนโดย Cloud Firestore คุณจะได้เรียนรู้วิธี:
- อ่านและเขียนข้อมูลไปยัง Firestore จากแอพ Android
- ฟังการเปลี่ยนแปลงในข้อมูล Firestore แบบเรียลไทม์
- ใช้การตรวจสอบสิทธิ์ Firebase และกฎความปลอดภัยเพื่อรักษาความปลอดภัยข้อมูล Firestore
- เขียนแบบสอบถาม Firestore ที่ซับซ้อน
ข้อกำหนดเบื้องต้น
ก่อนเริ่ม Codelab นี้ ตรวจสอบให้แน่ใจว่าคุณมี:
- Android Studio 4.0 หรือสูงกว่า
- โปรแกรมจำลอง Android ที่มี API 19 หรือสูงกว่า
- Node.js เวอร์ชัน 10 ขึ้นไป
- Java เวอร์ชัน 8 ขึ้นไป
2. สร้างโปรเจ็กต์ Firebase
- ลงชื่อเข้าใช้ คอนโซล Firebase ด้วยบัญชี Google ของคุณ
- ใน คอนโซล Firebase คลิก เพิ่มโครงการ
- ตามที่แสดงในภาพหน้าจอด้านล่าง ให้ป้อนชื่อโปรเจ็กต์ Firebase ของคุณ (เช่น "Friendly Eats") แล้วคลิก Continue
- คุณอาจถูกขอให้เปิดใช้งาน Google Analytics เพื่อจุดประสงค์ของ Codelab นี้ การเลือกของคุณไม่สำคัญ
- อีกสักครู่ โปรเจ็กต์ Firebase ของคุณจะพร้อม คลิก ดำเนินการ ต่อ
3. ตั้งค่าตัวอย่างโครงการ
ดาวน์โหลดรหัส
เรียกใช้คำสั่งต่อไปนี้เพื่อโคลนโค้ดตัวอย่างสำหรับ codelab นี้ สิ่งนี้จะสร้างโฟลเดอร์ชื่อ friendlyeats-android
บนเครื่องของคุณ:
$ git clone https://github.com/firebase/friendlyeats-android
หากคุณไม่มีคอมไพล์ในเครื่อง คุณสามารถดาวน์โหลดโค้ดได้โดยตรงจาก GitHub
เพิ่มการกำหนดค่า Firebase
- ใน คอนโซล Firebase เลือก ภาพรวมโปรเจ็ กต์ในการนำทางด้านซ้าย คลิกปุ่ม Android เพื่อเลือกแพลตฟอร์ม เมื่อระบบถามชื่อแพ็คเกจ ให้ใช้
com.google.firebase.example.fireeats
- คลิก ลงทะเบียนแอป และทำตามคำแนะนำเพื่อดาวน์โหลดไฟล์
google-services.json
และย้ายไปยังapp/
โฟลเดอร์ของโค้ดที่คุณเพิ่งดาวน์โหลด จากนั้นคลิก ถัดไป
นำเข้าโครงการ
เปิด Android Studio คลิก ไฟล์ > ใหม่ > นำเข้าโครงการ และเลือกโฟลเดอร์ friendlyeats-android
4. ตั้งค่า Firebase Emulators
ใน Codelab นี้ คุณจะใช้ Firebase Emulator Suite เพื่อจำลอง Cloud Firestore และบริการ Firebase อื่นๆ ในเครื่อง ซึ่งมอบสภาพแวดล้อมการพัฒนาในพื้นที่ที่ปลอดภัย รวดเร็ว และไม่มีค่าใช้จ่ายเพื่อสร้างแอปของคุณ
ติดตั้ง Firebase CLI
ก่อนอื่น คุณจะต้องติดตั้ง Firebase CLI หากคุณใช้ macOS หรือ Linux คุณสามารถเรียกใช้คำสั่ง cURL ต่อไปนี้:
curl -sL https://firebase.tools | bash
หากคุณใช้ Windows โปรดอ่าน คำแนะนำในการติดตั้ง เพื่อรับไบนารีแบบสแตนด์อโลนหรือติดตั้งผ่าน npm
เมื่อคุณติดตั้ง CLI แล้ว การเรียกใช้ firebase --version
ควรรายงานเวอร์ชัน 9.0.0
ขึ้นไป:
$ firebase --version 9.0.0
เข้าสู่ระบบ
เรียกใช้การ firebase login
เพื่อเชื่อมต่อ CLI กับบัญชี Google ของคุณ การดำเนินการนี้จะเปิดหน้าต่างเบราว์เซอร์ใหม่เพื่อให้กระบวนการเข้าสู่ระบบเสร็จสมบูรณ์ อย่าลืมเลือกบัญชีเดียวกับที่ใช้สร้างโปรเจ็กต์ Firebase ก่อนหน้านี้
เชื่อมโยงโครงการของคุณ
จากภายในโฟลเดอร์ friendlyeats-android
ให้เรียก firebase use --add
เพื่อเชื่อมต่อโปรเจ็กต์ในพื้นที่ของคุณกับโปรเจ็กต์ Firebase ทำตามคำแนะนำเพื่อเลือกโครงการที่คุณสร้างไว้ก่อนหน้านี้ และหากระบบขอให้เลือกนามแฝงให้ป้อน default
5. เรียกใช้แอพ
ตอนนี้ได้เวลาเรียกใช้ Firebase Emulator Suite และแอพ FriendlyEats Android เป็นครั้งแรก
เรียกใช้โปรแกรมจำลอง
ในเทอร์มินัลของคุณจากภายในไดเร็กทอรี friendlyeats-android
ให้เรียกใช้ 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 ของคุณจะต้องเชื่อมต่อกับอีมูเลเตอร์
เชื่อมต่อแอพกับ Emulators
เปิดไฟล์ 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 emulator ในเครื่อง ตลอดทั้งแอป เราจะใช้ FirebaseUtil.getFirestore()
เพื่อเข้าถึงอินสแตนซ์ของ FirebaseFirestore
ดังนั้นเราจึงมั่นใจว่าเรากำลังเชื่อมต่อกับโปรแกรมจำลอง Firestore เมื่อทำงานในโหมด debug
เรียกใช้แอพ
หากคุณเพิ่มไฟล์ google-services.json
อย่างถูกต้อง ตอนนี้โปรเจ็กต์ควรคอมไพล์แล้ว ใน Android Studio คลิก Build > Rebuild Project และตรวจสอบให้แน่ใจว่าไม่มีข้อผิดพลาดเหลืออยู่
ใน Android Studio เรียกใช้ แอปบนโปรแกรมจำลอง Android ของคุณ ในตอนแรก คุณจะพบกับหน้าจอ "ลงชื่อเข้าใช้" คุณสามารถใช้อีเมลและรหัสผ่านใดก็ได้เพื่อลงชื่อเข้าใช้แอป กระบวนการลงชื่อเข้าใช้นี้กำลังเชื่อมต่อกับโปรแกรมจำลองการตรวจสอบสิทธิ์ Firebase ดังนั้นจึงไม่มีการส่งข้อมูลรับรองที่แท้จริง
ตอนนี้เปิด Emulators UI โดยไปที่ http://localhost:4000 ในเว็บเบราว์เซอร์ของคุณ จากนั้นคลิกที่แท็บ Authentication และคุณจะเห็นบัญชีที่คุณเพิ่งสร้างขึ้น:
เมื่อคุณเสร็จสิ้นกระบวนการลงชื่อเข้าใช้ คุณจะเห็นหน้าจอหลักของแอป:
ในไม่ช้าเราจะเพิ่มข้อมูลบางส่วนเพื่อเติมหน้าจอหลัก
6. เขียนข้อมูลไปยัง Firestore
ในส่วนนี้ เราจะเขียนข้อมูลบางส่วนไปที่ Firestore เพื่อให้เราสามารถเติมหน้าจอหลักที่ว่างเปล่าในปัจจุบันได้
วัตถุโมเดลหลักในแอพของเราคือร้านอาหาร (ดู model/Restaurant.java
) ข้อมูล Firestore แบ่งออกเป็นเอกสาร คอลเล็กชัน และคอลเล็กชันย่อย เราจะจัดเก็บร้านอาหารแต่ละแห่งเป็นเอกสารในคอลเลกชั่นระดับบนสุดที่เรียกว่า "restaurants"
หากต้องการเรียนรู้เพิ่มเติมเกี่ยวกับโมเดลข้อมูล Firestore โปรดอ่านเกี่ยวกับเอกสารและคอลเล็กชันใน เอกสารประกอบ
เพื่อจุดประสงค์ในการสาธิต เราจะเพิ่มฟังก์ชันการทำงานในแอปเพื่อสร้างร้านอาหารสุ่มสิบร้านเมื่อเราคลิกปุ่ม "เพิ่มรายการสุ่ม" ในเมนูรายการเพิ่มเติม เปิดไฟล์ MainActivity.java
และแทนที่เนื้อหาใน onAddItemsClicked()
ด้วย:
private void onAddItemsClicked() {
// Get a reference to the restaurants collection
CollectionReference restaurants = mFirestore.collection("restaurants");
for (int i = 0; i < 10; i++) {
// Get a random Restaurant POJO
Restaurant restaurant = RestaurantUtil.getRandom(this);
// Add a new document to the restaurants collection
restaurants.add(restaurant);
}
}
มีสิ่งสำคัญบางประการที่ควรทราบเกี่ยวกับโค้ดด้านบนนี้:
- เราเริ่มต้นด้วยการอ้างอิงถึงคอลเล็กชัน
"restaurants"
คอลเลกชันจะถูกสร้างขึ้นโดยนัยเมื่อมีการเพิ่มเอกสาร ดังนั้นจึงไม่จำเป็นต้องสร้างคอลเลกชันก่อนที่จะเขียนข้อมูล - เอกสารสามารถสร้างได้โดยใช้ POJO (Plain Old Java Object) ซึ่งเราใช้เพื่อสร้างเอกสารร้านอาหารแต่ละฉบับ
- เมธอด
add()
จะเพิ่มเอกสารไปยังคอลเลกชั่นด้วย ID ที่สร้างขึ้นโดยอัตโนมัติ ดังนั้นเราจึงไม่จำเป็นต้องระบุ ID เฉพาะสำหรับร้านอาหารแต่ละแห่ง
เรียกใช้แอปอีกครั้งแล้วคลิกปุ่ม "เพิ่มรายการสุ่ม" ในเมนูรายการเพิ่มเติม (ที่มุมบนขวา) เพื่อเรียกใช้โค้ดที่คุณเพิ่งเขียน:
ตอนนี้เปิด Emulators UI โดยไปที่ http://localhost:4000 ในเว็บเบราว์เซอร์ของคุณ จากนั้นคลิกที่แท็บ Firestore และคุณจะเห็นข้อมูลที่คุณเพิ่งเพิ่ม:
ข้อมูลนี้เป็นข้อมูลภายในเครื่องของคุณ 100% อันที่จริง โครงการจริงของคุณยังไม่มีฐานข้อมูล Firestore! ซึ่งหมายความว่าสามารถทดลองแก้ไขและลบข้อมูลนี้ได้อย่างปลอดภัยโดยไม่เป็นผล
ขอแสดงความยินดี คุณเพิ่งเขียนข้อมูลไปยัง Firestore! ในขั้นตอนต่อไป เราจะเรียนรู้วิธีแสดงข้อมูลนี้ในแอป
7. แสดงข้อมูลจาก Firestore
ในขั้นตอนนี้ เราจะเรียนรู้วิธีดึงข้อมูลจาก Firestore และแสดงในแอพของเรา ขั้นตอนแรกในการอ่านข้อมูลจาก Firestore คือการสร้าง Query
เปิดไฟล์ MainActivity.java
และเพิ่มโค้ดต่อไปนี้ที่จุดเริ่มต้นของ onCreate()
:
mFirestore = FirebaseUtil.getFirestore();
// Get the 50 highest rated restaurants
mQuery = mFirestore.collection("restaurants")
.orderBy("avgRating", Query.Direction.DESCENDING)
.limit(LIMIT);
ตอนนี้ เราต้องการฟังการสืบค้น เพื่อให้เราได้รับเอกสารที่ตรงกันทั้งหมดและได้รับแจ้งการอัปเดตในอนาคตแบบเรียลไทม์ เนื่องจากเป้าหมายสุดท้ายของเราคือผูกข้อมูลนี้กับ RecyclerView
เราจึงต้องสร้างคลาส RecyclerView.Adapter
เพื่อรับฟังข้อมูล
เปิดคลาส FirestoreAdapter
ซึ่งมีการใช้งานบางส่วนแล้ว ขั้นแรก ให้อแด็ปเตอร์ใช้ 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
หนึ่งรายการสำหรับเอกสารใหม่แต่ละฉบับ เนื่องจากชุดผลลัพธ์ของแบบสอบถามเปลี่ยนแปลงเมื่อเวลาผ่านไป ผู้ฟังจะได้รับเหตุการณ์เพิ่มเติมที่มีการเปลี่ยนแปลง ตอนนี้เรามาเสร็จสิ้นการใช้ผู้ฟัง ขั้นแรกให้เพิ่มวิธีการใหม่สามวิธี: 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 ในเบราว์เซอร์ของคุณและแก้ไขหนึ่งในชื่อร้านอาหาร คุณควรเห็นการเปลี่ยนแปลงในแอปเกือบจะในทันที!
8. จัดเรียงและกรองข้อมูล
ปัจจุบันแอปแสดงร้านอาหารที่มีคะแนนสูงสุดทั่วทั้งคอลเลกชัน แต่ในแอปร้านอาหารจริง ผู้ใช้ต้องการจัดเรียงและกรองข้อมูล ตัวอย่างเช่น แอปควรจะสามารถแสดง "ร้านอาหารทะเลยอดนิยมในฟิลาเดลเฟีย" หรือ "พิซซ่าราคาถูกที่สุด"
การคลิกแถบสีขาวที่ด้านบนของแอปจะแสดงกล่องโต้ตอบตัวกรอง ในส่วนนี้ เราจะใช้การสืบค้น Firestore เพื่อทำให้กล่องโต้ตอบนี้ใช้งานได้:
มาแก้ไข onFilter()
ของ MainActivity.java
กัน เมธอดนี้ยอมรับออบเจ็กต์ Filters
ซึ่งเป็นออบเจ็กต์ตัวช่วยที่เราสร้างขึ้นเพื่อดักจับเอาต์พุตของกล่องโต้ตอบตัวกรอง เราจะเปลี่ยนวิธีนี้เพื่อสร้างแบบสอบถามจากตัวกรอง:
@Override
public void onFilter(Filters filters) {
// Construct basic query
Query query = mFirestore.collection("restaurants");
// Category (equality filter)
if (filters.hasCategory()) {
query = query.whereEqualTo("category", filters.getCategory());
}
// City (equality filter)
if (filters.hasCity()) {
query = query.whereEqualTo("city", filters.getCity());
}
// Price (equality filter)
if (filters.hasPrice()) {
query = query.whereEqualTo("price", filters.getPrice());
}
// Sort by (orderBy with direction)
if (filters.hasSortBy()) {
query = query.orderBy(filters.getSortBy(), filters.getSortDirection());
}
// Limit items
query = query.limit(LIMIT);
// Update the query
mQuery = query;
mAdapter.setQuery(query);
// Set header
mCurrentSearchView.setText(Html.fromHtml(filters.getSearchDescription(this)));
mCurrentSortByView.setText(filters.getOrderDescription(this));
// Save filters
mViewModel.setFilters(filters);
}
ในตัวอย่างด้านบน เราสร้างออบเจ็กต์ Query
โดยแนบคำสั่ง where
และ orderBy
เพื่อให้ตรงกับตัวกรองที่กำหนด
เปิด แอปอีกครั้งและเลือกตัวกรองต่อไปนี้เพื่อแสดงร้านอาหารราคาถูกยอดนิยม:
ตอนนี้คุณควรเห็นรายชื่อร้านอาหารที่กรองแล้วซึ่งมีเพียงตัวเลือกราคาต่ำ:
หากคุณมาไกลถึงขนาดนี้ แสดงว่าคุณได้สร้างแอปดูคำแนะนำร้านอาหารที่ทำงานได้อย่างสมบูรณ์บน Firestore แล้ว! คุณสามารถจัดเรียงและกรองร้านอาหารแบบเรียลไทม์ได้แล้ว ในส่วนถัดไป เราจะเพิ่มรีวิวร้านอาหารและเพิ่มกฎความปลอดภัยให้กับแอป
9. จัดระเบียบข้อมูลในคอลเลกชันย่อย
ในส่วนนี้ เราจะเพิ่มการให้คะแนนให้กับแอปเพื่อให้ผู้ใช้สามารถรีวิวร้านอาหารที่พวกเขาชื่นชอบ (หรือร้านโปรดน้อยที่สุด) ได้
คอลเลกชั่นและคอลเลกชั่นย่อย
จนถึงตอนนี้ เราได้จัดเก็บข้อมูลร้านอาหารทั้งหมดในคอลเล็กชันระดับบนสุดที่เรียกว่า "ร้านอาหาร" เมื่อผู้ใช้ให้คะแนนร้านอาหาร เราต้องการเพิ่มวัตถุการ Rating
ใหม่ให้กับร้านอาหาร สำหรับงานนี้ เราจะใช้คอลเล็กชันย่อย คุณสามารถนึกถึงคอลเล็กชันย่อยเป็นคอลเล็กชันที่แนบกับเอกสารได้ ดังนั้นเอกสารร้านอาหารแต่ละฉบับจะมีการรวบรวมย่อยการให้คะแนนที่เต็มไปด้วยเอกสารการให้คะแนน คอลเล็กชันย่อยช่วยจัดระเบียบข้อมูลโดยไม่ทำให้เอกสารของเราบวมหรือต้องการการสืบค้นที่ซับซ้อน
ในการเข้าถึงคอลเลกชันย่อย ให้เรียก . .collection()
ในเอกสารหลัก:
CollectionReference subRef = mFirestore.collection("restaurants")
.document("abc123")
.collection("ratings");
คุณสามารถเข้าถึงและสอบถามคอลเล็กชันย่อยได้เหมือนกับคอลเล็กชันระดับบนสุด ไม่มีการจำกัดขนาดหรือการเปลี่ยนแปลงประสิทธิภาพ คุณสามารถอ่านเพิ่มเติมเกี่ยวกับโมเดลข้อมูล Firestore ได้ที่ นี่
การเขียนข้อมูลในการทำธุรกรรม
การเพิ่มเรต Rating
ให้กับคอลเลกชั่นย่อยที่เหมาะสมนั้นต้องการเพียงแค่การเรียก .add()
เท่านั้น แต่เรายังต้องอัปเดตคะแนนเฉลี่ยของออบเจกต์ Restaurant
และจำนวนการให้คะแนนเพื่อสะท้อนข้อมูลใหม่ หากเราใช้การดำเนินการแยกกันเพื่อทำการเปลี่ยนแปลงทั้งสองนี้มีเงื่อนไขการแข่งขันหลายอย่างที่อาจส่งผลให้ข้อมูลเก่าหรือไม่ถูกต้อง
เพื่อให้แน่ใจว่ามีการให้คะแนนอย่างถูกต้อง เราจะใช้ธุรกรรมเพื่อเพิ่มการให้คะแนนให้กับร้านอาหาร ธุรกรรมนี้จะดำเนินการบางอย่าง:
- อ่านคะแนนปัจจุบันของร้านอาหารและคำนวณใหม่
- เพิ่มคะแนนให้กับคอลเลกชันย่อย
- อัพเดทเรตติ้งเฉลี่ยของร้านและจำนวนเรตติ้ง
เปิด RestaurantDetailActivity.java
และใช้ฟังก์ชัน addRating
:
private Task<Void> addRating(final DocumentReference restaurantRef,
final Rating rating) {
// Create reference for new rating, for use inside the transaction
final DocumentReference ratingRef = restaurantRef.collection("ratings")
.document();
// In a transaction, add the new rating and update the aggregate totals
return mFirestore.runTransaction(new Transaction.Function<Void>() {
@Override
public Void apply(Transaction transaction)
throws FirebaseFirestoreException {
Restaurant restaurant = transaction.get(restaurantRef)
.toObject(Restaurant.class);
// Compute new number of ratings
int newNumRatings = restaurant.getNumRatings() + 1;
// Compute new average rating
double oldRatingTotal = restaurant.getAvgRating() *
restaurant.getNumRatings();
double newAvgRating = (oldRatingTotal + rating.getRating()) /
newNumRatings;
// Set new restaurant info
restaurant.setNumRatings(newNumRatings);
restaurant.setAvgRating(newAvgRating);
// Commit to Firestore
transaction.set(restaurantRef, restaurant);
transaction.set(ratingRef, rating);
return null;
}
});
}
addRating()
ส่งคืน Task
ที่แสดงถึงธุรกรรมทั้งหมด ใน onRating()
ตัวฟังจะถูกเพิ่มไปยังงานเพื่อตอบสนองต่อผลลัพธ์ของธุรกรรม
เรียกใช้ แอปอีกครั้งแล้วคลิกหนึ่งในร้านอาหาร ซึ่งจะแสดงหน้าจอรายละเอียดร้านอาหารขึ้นมา คลิกปุ่ม + เพื่อเริ่มเพิ่มรีวิว เพิ่มบทวิจารณ์โดยเลือกจำนวนดาวและป้อนข้อความ
การกดปุ่ม ส่ง จะเป็นการเปิดการทำธุรกรรม เมื่อธุรกรรมเสร็จสมบูรณ์ คุณจะเห็นรีวิวของคุณแสดงอยู่ด้านล่างและการอัปเดตจำนวนรีวิวของร้านอาหาร:
ยินดีด้วย! ตอนนี้คุณมีแอปรีวิวร้านอาหารบนมือถือในโซเชียล ท้องถิ่นและสร้างขึ้นบน Cloud Firestore ฉันได้ยินมาว่าพวกเขาเป็นที่นิยมมากในทุกวันนี้
10. รักษาความปลอดภัยข้อมูลของคุณ
จนถึงตอนนี้ เรายังไม่ได้พิจารณาความปลอดภัยของแอปพลิเคชันนี้ เราจะทราบได้อย่างไรว่าผู้ใช้สามารถอ่านและเขียนข้อมูลของตนเองได้อย่างถูกต้องเท่านั้น ฐานข้อมูล 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 ผู้ใช้ตรงกับผู้ใช้ที่ลงชื่อเข้าใช้ ซึ่งจะป้องกันการปลอมแปลง
หากต้องการอ่านเพิ่มเติมเกี่ยวกับกฎความปลอดภัย โปรดไป ที่เอกสารประกอบ
11. บทสรุป
ตอนนี้คุณได้สร้างแอพที่มีคุณสมบัติครบถ้วนบน Firestore แล้ว คุณได้เรียนรู้เกี่ยวกับคุณสมบัติที่สำคัญที่สุดของ Firestore ได้แก่:
- เอกสารและการเรียกเก็บเงิน
- การอ่านและการเขียนข้อมูล
- การเรียงลำดับและการกรองด้วยข้อความค้นหา
- คอลเลกชั่นย่อย
- ธุรกรรม
เรียนรู้เพิ่มเติม
หากต้องการเรียนรู้เกี่ยวกับ Firestore ต่อไป ต่อไปนี้คือจุดเริ่มต้นที่ดี:
แอปร้านอาหารใน Codelab นี้อิงตามแอปพลิเคชันตัวอย่าง "Friendly Eats" คุณสามารถเรียกดูซอร์สโค้ดสำหรับแอปนั้นได้ ที่นี่
ไม่บังคับ: ปรับใช้กับการผลิต
จนถึงตอนนี้ แอปนี้ใช้เฉพาะ Firebase Emulator Suite เท่านั้น หากคุณต้องการเรียนรู้วิธีทำให้แอปนี้ใช้งานได้กับโปรเจ็กต์ Firebase จริง ให้ไปยังขั้นตอนถัดไป
12. (ไม่บังคับ) ทำให้แอปของคุณใช้งานได้
จนถึงตอนนี้ แอปนี้อยู่ในเครื่องทั้งหมดแล้ว ข้อมูลทั้งหมดอยู่ใน Firebase Emulator Suite ในส่วนนี้ คุณจะได้เรียนรู้วิธีกำหนดค่าโปรเจ็กต์ Firebase เพื่อให้แอปนี้ใช้งานได้จริง
การตรวจสอบสิทธิ์ Firebase
ในคอนโซล Firebase ให้ไปที่ส่วนการ รับรองความถูกต้อง แล้วคลิก เริ่มต้น ไปที่แท็บ วิธีการลงชื่อเข้าใช้ แล้วเลือกตัวเลือก อีเมล/รหัสผ่าน จาก ผู้ให้บริการดั้งเดิม
เปิดใช้งานวิธีการลงชื่อเข้าใช้ด้วย อีเมล/รหัสผ่าน แล้วคลิก บันทึก
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
ชั่วคราวและเรียกใช้แอปอีกครั้ง
โปรดทราบว่าคุณอาจต้อง ออกจาก ระบบแอปและลงชื่อเข้าใช้อีกครั้งเพื่อเชื่อมต่อกับการผลิตอย่างถูกต้อง