1. ภาพรวม
เป้าหมาย
ใน Codelab นี้ คุณจะสร้างแอปแนะนำร้านอาหารบน Android ที่ได้รับการสนับสนุนจาก Cloud Firestore คุณจะได้เรียนรู้วิธีการ:
- อ่านและเขียนข้อมูลไปยัง Firestore จากแอป Android
- ฟังการเปลี่ยนแปลงในข้อมูล Firestore แบบเรียลไทม์
- ใช้การตรวจสอบสิทธิ์ Firebase และกฎความปลอดภัยเพื่อรักษาความปลอดภัยข้อมูล Firestore
- เขียนคำสั่ง Firestore ที่ซับซ้อน
ข้อกำหนดเบื้องต้น
ก่อนที่จะเริ่ม Codelab นี้ ตรวจสอบให้แน่ใจว่าคุณมี:
- Android Studio Flamingo หรือใหม่กว่า
- โปรแกรมจำลอง Android ที่มี API 19 ขึ้นไป
- Node.js เวอร์ชัน 16 ขึ้นไป
- Java เวอร์ชัน 17 หรือสูงกว่า
2. สร้างโปรเจ็กต์ Firebase
- ลงชื่อเข้าใช้ คอนโซล Firebase ด้วยบัญชี Google ของคุณ
- ใน คอนโซล Firebase คลิก เพิ่มโครงการ
- ดังที่แสดงในภาพหน้าจอด้านล่าง ให้ป้อนชื่อโปรเจ็กต์ Firebase ของคุณ (เช่น "Friendly Eats") แล้วคลิก ดำเนินการต่อ
- คุณอาจถูกขอ���ห้เปิดใช้งาน Google Analytics เพื่อวัตถุประสงค์ของ Codelab นี้ การเลือกของคุณไม่สำคัญ
- หลังจากผ่านไปประมาณหนึ่งนาที โปรเจ็กต์ Firebase ของคุณก็จะพร้อม คลิก ดำเนินการต่อ
3. ตั้งค่าโครงการตัวอย่าง
ดาวน์โหลดรหัส
เรียกใช้คำสั่งต่อไปนี้เพื่อโคลนโค้ดตัวอย่างสำหรับ codelab นี้ สิ่งนี้จะสร้างโฟลเดอร์ชื่อ friendlyeats-android
บนเครื่องของคุณ:
$ git clone https://github.com/firebase/friendlyeats-android
หากคุณไม่มี git บนเครื่องของคุณ คุณสามารถดาวน์โหลดโค้ดได้โดยตรงจาก GitHub
เพิ่มการกำหนดค่า Firebase
- ใน คอนโซล Firebase ให้เลือก ภาพรวมโครงการ ในการนำทางด้านซ้าย คลิกปุ่ม Android เพื่อเลือกแพลตฟอร์ม เมื่อได้รับแจ้งให้ระบุชื่อแพ็คเกจ ให้ใช้
com.google.firebase.example.fireeats
- คลิก ลงทะเบียนแอป แล้วทำตามคำแนะนำเพื่อดาวน์โหลดไฟล์
google-services.json
และย้ายไปยังapp/
โฟลเดอร์ของโค้ดที่คุณเพิ่งดาวน์โหลด จากนั้นคลิก ถัดไป
นำเข้าโครงการ
เปิด Android สตูดิโอ คลิก ไฟล์ > ใหม่ > นำเข้าโครงการ และเลือกโฟลเดอร์ 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 ของคุณจะต้องเชื่อมต่อกับโปรแกรมจำลอง
เชื่อมต่อแอปกับอีมูเลเตอร์
เปิดไฟล์ util/FirestoreInitializer.kt
และ util/AuthInitializer.kt
ใน Android Studio ไฟล์เหล่านี้มีตรรกะในการเชื่อมต่อ Firebase SDK กับโปรแกรมจำลองในเครื่องที่ทำงานบนเครื่องของคุณเมื่อเริ่มต้นแอปพลิเคชัน
ในเมธอด create()
ของคลาส FirestoreInitializer
ให้ตรวจสอบโค้ดชิ้นนี้:
// Use emulators only in debug builds
if (BuildConfig.DEBUG) {
firestore.useEmulator(FIRESTORE_EMULATOR_HOST, FIRESTORE_EMULATOR_PORT)
}
เรากำลังใช้ BuildConfig
เพื่อให้แน่ใจว่าเราจะเชื่อมต่อกับโปรแกรมจำลองเมื่อแอปของเราทำงานในโหมด debug
เท่านั้น เมื่อเราคอมไพล์แอปในโหมด release
เงื่อนไขนี้จะเป็นเท็จ
เราจะเห็นว่ามีการใช้ 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 ��นเ��็บเบราว์เซอร์ของคุณ จากนั้นคลิกที่แท็บ การรับรองความถูกต้อง และคุณจะเห็นบัญชีที่คุณเพิ่งสร้างขึ้น:
เมื่อคุณเสร็จสิ้นกระบวนการลงชื่อเข้าใช้แล้ว คุณจะเห็นหน้าจอหลักของแอป:
ในไม่ช้าเราจะเพิ่มข้อมูลบางส่วนเพื่อเติมหน้าจอหลัก
6. เขียนข้อมูลไปที่ Firestore
ในส่วนนี้ เราจะเขียนข้อมูลบางส่วนลงใน Firestore เพื่อให้เราสามารถเติมข้อมูลในหน้าจอหลักที่ว่างเปล่าในปัจจุบันได้
วัตถุโมเดลหลักในแอปของเราคือร้านอาหาร (ดู model/Restaurant.kt
) ข้อมูล Firestore จะแบ่งออกเป็นเอกสาร คอลเล็กชัน และคอลเล็กชันย่อย เราจะจัดเก็บร้านอาหารแต่ละแห่งเป็นเอกสารในคอลเลกชันระดับบนสุดที่เรียกว่า "restaurants"
หากต้องการเรียนรู้เพิ่มเติมเกี่ยวกับโมเดลข้อมูล Firestore โปรดอ่านเกี่ยวกับเอกสารและคอลเล็กชันใน เอกสารประกอบ
เพื่อวัตถุประสงค์ในการสาธิต เราจะเพิ่มฟังก์ชันในแอปเพื่อสร้างร้านอาหารแบบสุ่ม 10 แห่ง เมื่อเราคลิกปุ่ม "เพิ่มรายการสุ่ม" ในเมนูเพิ่มเติม เปิดไฟล์ MainFragment.kt
และแทนที่เนื้อหาในเมธอด onAddItemsClicked()
ด้วย:
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)
}
}
มีสิ่งสำคัญบางประการที่ควรทราบเกี่ยวกับโค้ดด้าน��น:
- เราเริ่มต้นด้วยการอ้างอิงถึงคอลเลกชั่น
"restaurants"
คอลเลกชันจะถูกสร้างขึ้นโดยปริยายเมื่อมีการเพิ่มเอกสาร ดังนั้นจึงไม่จำเป็นต้องสร้างคอลเลกชันก่อนที่จะเขียนข้อมูล - สามารถสร้างเอกสารได้โดยใช้คลาสข้อมูล Kotlin ซึ่งเราใช้เพื่อสร้างเอกสารร้านอาหารแต่ละฉบับ
- เมธอด
add()
จะเพิ่มเอกสารลงในคอลเลกชันด้วย ID ที่สร้างขึ้นโดยอัตโนมัติ ดังนั้นเราจึงไม่จำเป็นต้องระบุ ID ที่ไม่ซ้ำกันสำหรับร้านอาหารแต่ละแห่ง
ตอนนี้ให้รันแอปอีกครั้งแล้วคลิกปุ่ม "เพิ่มรายการสุ่ม" ในเมนูโอเวอร์โฟลว์ (ที่มุมขวาบน) เพื่อเรียกใช้โค้ดที่คุณเพิ่งเขียน:
ตอนนี้เปิด Emulators UI โดยไปที่ http://localhost:4000 บนเว็บเบราว์เซอร์ของคุณ จากนั้นคลิกที่แท็บ Firestore แล้วคุณจะเห็นข้อมูลที่คุณเพิ่งเพิ่ม:
ข้อมูลนี้อยู่ในเครื่องของคุณ 100% ที่จริงแล้วโปรเจ็กต์จริงของคุณยังไม่มีฐานข้อมูล Firestore ด้วยซ้ำ! ซึ่งหมายความว่าสามารถทดลองแก้ไขและลบข้อมูลนี้ได้อย่างปลอดภัยโดยไม่มีผลกระทบใดๆ ตามมา
ยินดีด้วย คุณเพิ่งเขียนข้อมูลลงใน Firestore! ในขั้นตอนถัดไป เราจะได้เรียนรู้วิธีแสดงข้อมูลนี้ในแอป
7. แสดงข้อมูลจาก Firestore
ในขั้นตอนนี้ เราจะได้เรียนรู้วิธีดึงข้อมูลจาก Firestore และแสดงในแอปของเรา ขั้นตอนแรกในการอ่านข้อมูลจาก Firestore คือการสร้าง Query
เปิดไฟล์ MainFragment.kt
และเพิ่มโค้ดต่อไปนี้ที่จุดเริ่มต้นของเมธอด onViewCreated()
:
// Firestore
firestore = Firebase.firestore
// Get the 50 highest rated restaurants
query = firestore.collection("restaurants")
.orderBy("avgRating", Query.Direction.DESCENDING)
.limit(LIMIT.toLong())
ตอนนี้เราต้องการฟังคำถาม เพ��่อให้เราได้รับเอกสารที่ตรงกันทั้งหมด และได้รับแจ้งถึงการอัปเดตในอนาคตแบบเรียลไทม์ เนื่องจากเป้าหมายสุดท้ายของเราคือการผูกข้อมูลนี้กับ RecyclerView
เราจึงต้องสร้างคลาส RecyclerView.Adapter
เพื่อฟังข้อมูล
เปิดคลาส FirestoreAdapter
ซึ่งมีการใช้งานบางส่วนแล้ว ขั้นแรก ให้อะแดปเตอร์ใช้ EventListener
และกำหนดฟังก์ชัน onEvent
เพื่อให้สามารถรับการอัปเดตสำหรับการสืบค้น 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()
}
// ...
}
เมื่อโหลดครั้งแรก ผู้ฟังจะได้รับเหตุการณ์ ADDED
หนึ่งรายการสำหรับเอกสารใหม่แต่ละฉบับ เนื่องจากชุดผลลัพธ์ของแบบสอบถามเปลี่ยนแปลงไปตามกาลเวลา ผู้ฟังจะได้รับเหตุการณ์เพิ่มเติมที่มีการเปลี่ยนแปลง ตอนนี้เรามาดำเนินการ Listener ให้เสร็จสิ้น ขั้นแรกให้เพิ่มวิธีการใหม่สามวิธี: onDocumentAdded
, onDocumentModified
และ onDocumentRemoved
:
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)
}
จากนั้นเรียกวิธีการใหม่เหล่านี้จาก 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()
}
ในที่สุดก็ใช้เมธอด startListening()
เพื่อแนบ Listener:
fun startListening() {
if (registration == null) {
registration = query.addSnapshotListener(this)
}
}
ตอนนี้แอปได้รับการกำหนดค่าอย่างสมบูรณ์ให้อ่านข้อมูลจาก Firestore เรียกใช้ แอปอีกครั้งแล้วคุณจะเห็นร้านอาหารที่คุณเพิ่มไว้ในขั้นตอนก่อนหน้า:
ตอนนี้กลับไปที่ Emulator UI ในเบราว์เซอร์ของคุณและแก้ไขชื่อร้านอาหารชื่อใดชื่อหนึ่ง คุณจะเห็นการเปลี่ยนแปลงในแอปเกือบจะในทันที!
8. จัดเรียงและกรองข้อมูล
ขณะนี้แอปแสดงร้านอาหารที่ได้รับคะแนนสูงสุดจากคอลเลกชั่นทั้งหมด แต่ในแอปร้านอาหารจริง ผู้ใช้ต้องการจัดเรียงและกรองข้อมูล ตัวอย่างเช่น แอปควรแสดง "ร้านอาหารทะเลยอดนิยมในฟิลาเดลเฟีย" หรือ "พิซซ่าราคาถูกที่สุด" ได้
การคลิกแถบสีขาวที่ด้านบนของแอปจะแสดงกล่องโต้ตอบตัวกรอง ในส่วนนี้ เราจะใช้คำสั่ง Firestore เพื่อให้กล่องโต้ตอบนี้ใช้งานได้:
มาแก้ไขเมธอด onFilter()
ของ MainFragment.kt
กัน วิธีกา��นี้ยอมรับวัตถุ Filters
ซึ่งเป็นวัตถุตัวช่วยที่เราสร้างขึ้นเพื่อบันทึกผลลัพธ์ของกล่องโต้ตอบตัวกรอง เราจะเปลี่ยนวิธีนี้เพื่อสร้างแบบสอบถามจากตัวกรอง:
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
}
ในตัวอย่างด้านบน เราสร้างออบเจ็กต์ Query
โดยการแนบคำสั่ง where
และ orderBy
เพื่อให้ตรงกับตัวกรองที่กำหนด
เรียกใช้ แอปอีกครั้งและเลือกตัวกรองต่อไปนี้เพื่อแสดงร้านอาหารราคาถูกยอดนิยมที่สุด:
ตอนนี้คุณควรเห็นรายชื่อร้านอาหารที่กรองแล้วซึ่งมีเฉพาะตัวเลือกราคาต่ำ:
หากคุณมาไกลถึงขนาดนี้ ตอนนี้คุณได้สร้างแอปดูคำแนะนำร้านอาหารที่มีฟังก์ชันครบถ้วนบน Firestore แล้ว! ตอนนี้คุณสามารถจัดเรียงและกรองร้านอาหารได้แบบเรียลไทม์ ในอีกไม่กี่ส่วนถัดไป เราจะเพิ่มรีวิวร้านอาหารและเพิ่มกฎความปลอดภัยให้กับแอป
9. จัดระเบียบข้อมูลในคอลเลกชันย่อย
ในส่วนนี้ เราจะเพิ่มการให้คะแนนให้กับแอป เพื่อให้ผู้ใช้สามารถรีวิวร้านอาหารที่พวกเขาชื่นชอบ (หรือชื่นชอบน้อยที่สุด) ได้
คอลเลกชันและคอลเลกชันย่อย
จนถึงตอนนี้เราได้จัดเก็บข้อมูลร้านอาหารทั้งหมดไว้ในคอลเลกชันระดับบนสุดที่เรียกว่า "ร้านอาหาร" เมื่อผู้ใช้ให้คะแนนร้านอาหาร เราต้องการเพิ่มออบเจ็กต์ Rating
ใหม่ให้กับร้านอาหาร สำหรับงานนี้ เราจะใช้คอลเลกชันย่อย คุณสามารถนึกถึงคอลเลกชันย่อยเป็นคอลเลกชันที่แนบมากับเอกสารได้ ดังนั้นเอกสารร้านอาหารแต่ละฉบับจะมีคอลเลกชันย่อยการให้คะแนนที่เต็มไปด้วยเอกสารการให้คะแนน คอลเลกชันย่อยช่วยจัดระเบียบข้อมูลโดยไม่ทำให้เอกสารของเราบวมหรือต้องการการสืบค้นที่ซับซ้อน
หากต้องการเข้าถึงคอลเลกชันย่อย ให้เรียก .collection()
บนเอกสารหลัก:
val subRef = firestore.collection("restaurants")
.document("abc123")
.collection("ratings")
คุณสามารถเข้าถึงและสืบค้นคอลเลกชันย่อยได้เช่นเดียวกับคอลเลกชันระดับบนสุด โดยไม่มีการจำกัดขนาดหรือการเปลี่ยนแปลงประสิทธิภาพ คุณสามารถอ่านเพิ่มเติมเกี่ยวกับโมเดลข้อมูล Firestore ได้ที่นี่
การเขียนข้อมูลในธุรกรรม
การเพิ่มการให้ Rating
ให้กับคอลเลกชันย่อยที่เหมาะสมนั้นจำเป็นต้องเรียก .add()
เท่านั้น แต่เรายังต้องอัปเดตการให้คะแนนเฉลี่ยของออบเจ็กต์ Restaurant
และจำนวนการให้คะแนนเพื่อให้สะท้อนถึงข้อมูลใหม่ด้วย หากเราใช้การดำเนินการแยกกันเพื่อทำการเปลี่ยนแปลงทั้งสองนี้ จะมีเงื่อนไขการแข่งขันหลายประการที่อาจส่งผลให้ข้อมูลเก่าหรือไม่ถูกต้อง
เพื่อให้แน่ใจว่ามีการเพิ่มคะแนนอย่างเหมาะสม เราจะใช้ธุรกรรมเพื่อเพิ่มคะแนนให้กับร้านอาหาร ธุรกรรมนี้จะดำเนินการบางอย่าง:
- อ่านคะแนนปัจจุบันของร้านอาหารและคำนวณคะแนนใหม่
- เพิ่มการให้คะแนนให้กับคอลเลกชันย่อย
- อัพเดทคะแนนเฉลี่ยของร้านอาหารและจำนวนคะแนน
เปิด RestaurantDetailFragment.kt
และใช้ฟังก์ชัน 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
}
}
ฟังก์ชัน addRating()
ส่งคืน Task
ที่แสดงถึงธุรกรรมทั้งหมด ในฟังก์ชัน onRating()
Listeners จะถูกเพิ่มเข้าไปในงานเพื่อตอบสนองต่อผลลัพธ์ของธุรกรรม
ตอนนี้ ให้เรียกใช้ แอปอีกครั้งแล้วคลิกที่ร้านอาหารแห่งใดแห่งหนึ่ง ซึ่งจะแสดงหน้าจอรายละเอียดร้านอาหาร���ึ้นมา คลิกปุ่ม + เพื่อเริ่มเพิ่มบทวิจารณ์ เพิ่มบทวิจารณ์โดยเลือกดาวจำนวนหนึ่งแล้วป้อนข้อความ
การกด ปุ่มส่ง จะเป็นการเริ่มการทำธุรกรรม เมื่อธุรกรรมเสร็จสมบูรณ์ คุณจะเห็นรีวิวของคุณแสดงด้านล่างและการอัปเดตจำนวนรีวิวของร้านอาหาร:
ยินดีด้วย! ตอนนี้คุณมีแอปรีวิวร้านอาหารบนมือถือบนโซเชียลระดับท้องถิ่นที่สร้างบน 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;
}
}
}
}
กฎเหล่านี้จำกัดการเข้าถึงเพื่อให้แน่ใจว่าลูกค้าจะทำการเปลี่ยนแปลงอย่างปลอดภัยเท่านั้น ตัวอย่างเช่น การอัปเดตเอกสารร้านอาหารสามารถเปลี่ยนแปลงได้เฉพาะการให้คะแนนเท่านั้น ไม่สามารถเปลี่ยนแปลงชื่อหรือข้อมูลอื่นใดที่ไม่เปลี่ยนรูปแบบได้ สามารถสร้างการให้คะแนนได้หากรหัสผู้ใช้ตรงกับผู้ใช้ที่ลงชื่อเข้าใช้ ซึ่งป้องกันการปลอมแปลง
หากต้องการอ่านเพิ่มเติมเกี่ยวกับกฎความปลอดภัย โปรดไปที่ เอกสารประกอบ
11. บทสรุป
ตอนนี้คุณได้สร้างแอปที่มีคุณสมบัติครบถ้วนบน Firestore แล้ว คุณได้เรียนรู้เกี่ยวกับฟีเจอร์ที่สำคัญที่สุดของ Firestore ได้แก่:
- เอกสารและคอลเลกชัน
- การอ่านและการเขียนข้อมูล
- การเรียงลำดับและการกรองด้วยแบบสอบถาม
- คอลเลกชันย่อย
- การทำธุรกรรม
เรียนรู้เพิ่มเติม
หากต้องการเรียนรู้เกี่ยวกับ Firestore ต่อ���ป ต่อไปนี้เป็นจุดเริ่มต้นที่ดี:
แอปร้านอาหารใน Codelab นี้อิงตามแอปพลิเคชันตัวอย่าง "Friendly Eats" คุณสามารถเรียกดูซอร์สโค้ดของแอปนั้น ได้ที่นี่
ทางเลือก: ปรับใช้กับการใช้งานจริง
จนถึงตอนนี้แอปนี้ใช้เฉพาะ Firebase Emulator Suite เท่านั้น หากคุณต้องการเรียนรู้วิธีทำให้แอปนี้ใช้งานได้กับโปรเจ็กต์ Firebase จริง ให้ดำเนินการต่อในขั้นตอนถัดไป
12. (ไม่บังคับ) ปรับใช้แอปของคุณ
จนถึงตอนนี้แอปนี้อยู่ในเครื่องแล้ว ข้อมูลทั้งหมดอยู่ใน Firebase Emulator Suite ในส่วนนี้ คุณจะได้เรียนรู้วิธีกำหนดค่าโปรเจ็กต์ Firebase เพื่อให้แอปนี้ใช้งานได้จริง
การรับรองความถูกต้องของ Firebase
ในคอนโซล Firebase ให้ไปที่ส่วน การตรวจสอบสิทธิ์ แล้วคลิก เริ่มต้นใช้งาน ไปที่แท็บ วิธีการลงชื่อเข้าใช้ แล้วเลือกตัวเลือก อีเมล/รหัสผ่าน จาก ผู้ให้บริการระบบเดิม
เปิดใช้งานวิธีลงชื่อเข้าใช้ อีเมล/รหัสผ่าน แล้วคลิก บันทึก
ร้านดับเพลิง
สร้างฐานข้อมูล
ไปที่ส่วน ฐานข้อมูล 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
กำหนดค่าแอป
ใน���ฟล์ util/FirestoreInitializer.kt
และ util/AuthInitializer.kt
เราได้กำหนดค่า Firebase SDK เพื่อเชื่อมต่อกับโปรแกรมจำลองเมื่ออยู่ในโหมดดีบัก:
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
}
หากคุณต้องการทดสอบแอปกับโปรเจ็กต์ Firebase จริง คุณสามารถ:
- สร้างแอปในโหมดเผยแพร่และเรียกใช้บนอุปกรณ์
- แทนที่
BuildConfig.DEBUG
ด้วยfalse
ชั่วคราวแล้วเรียกใช้แอปอีกครั้ง
โปรดทราบว่าคุณอาจต้อง ออก จากระบบแอปแล้วลงชื่อเข้าใช้อีกครั้งเพื่อเชื่อมต่อกับการใช้งานจริงอย่างเหมาะสม