“Flutter” x “Firebase (Cloud Firestore)”
{Intro}
สวัสดีครับบ บทความนี้เป็นบทความแรกในหัวข้อ Flutter x Firebase วันนี้ผมจะมาแชร์วิธีการเชื่อมต่อ Firebase กับ Flutter เข้าด้วยกัน ซึ่งจะเชื่อมไปที่ Service ชื่อ Cloud Firestore ซึ่งเป็น Database ของ Firebase นั้นเองครับ (Services อื่นๆ เดียวผมจะค่อยๆเขียนออกมาทีละอันนะคับบ) ถ้าพร้อมแล้วไปเริ่มกันเลย Lets Goooo.
{Before Developing}
ก่อนอื่นเลยสิ่งที่เราต้องมีก่อน นั้นก็คือ
- Firebase Project
- Flutter
จบ………… เดียวๆๆๆๆ 5555+ บทความนี้เป็นบทความแรก ขออธิบายแต่ละส่วนแบบละเอียดๆก่อนนะครับ
- เตรียม Flutter Project
0. Install Flutter ใน Local Machine
Follow Step ในลิงค์นี้ได้เลยครับ
โดย Flutter ผมจะใช้ Version ตามนี้เลยครับ
1. สร้าง Project Flutter ขึ้นมา 1 Project
2. (สำหรับ cloud_firestore) ลง Dependency ชื่อ "cloud_firestore"
Version ล่าสุด (ดูจากในลิงค์ได้เลยครับว่าตอนนี้ล่าสุดเป็นเท่าไหร)
ไปที่ไฟล์ pubspec.ymal
และเพิ่ม cloud_firestore:
ใน dependencies
3. ไปเอา Bundle Id (iOS) และ Package Name (Android) มา
IOS:
เปิดไฟล์ที่ชื่อ Runner.xcworkspace
ใน Folder ios ขึ้นมาด้วย Xcode
Android:
ไปที่ไฟล์ build.gradle (ระดับแอพ)
(android/app/build.gradle)
ตอนนี้เราจะได้ว่า:
- bundleId: com.example.cloudFirestore
- applicationId: com.example.cloud_firestore
จดข้อมูลส่วนนี้ไว้ก่อนครับ เรากำลังจะได้ใช้มันในอีกไม่ช้า
- เตรียม Firebase Project
0) ไปที่ Firebase Console
0.5) (Optional) เปิด Project ใหม่
0.5.1) (สำหรับ Cloud Firestore) เปิด Cloud Firestore Database ใน Firebase
ให้เราตั้งค่าเป็น Production Mode และเลือก Cloud Firestore Location (ผมจะเลือกเป็น asia-east2 (Hongkong)) และ Done.
1) เชื่อมต่อ IOS และ Android App เข้ากับ Firebase
ขั้นตอนนี้เป็นการเชื่อม App ของเราเข้ากับ Firebase ซึ่งจะทำการเชื่อมเพียงครั้งแรกครั้งเดียว และ App เราจะสามารถเข้าใช้งาน Firebase Services ได้ทั้งหมดเลย
iOS:
เปิด iOS App ใน Firebase โดยไปที่ Project Overview และเลือก iOS
หรือไปที่หน้า Project Setting และเลื่อนลงมาล่างสุดจะเจอกับปุ่ม Add App
กรอก BundleId ของ iOS ที่เราไปเอามาในขั้นตอนที่แล้วในหัวข้อ bundleId ที่เหลือเป็น Optional ครับ จะกรอกหรือไม่ก็ได้ จากนั้นกด Register App ได้เลยครับ
หลังจากนั้น Firebase จะสร้างไฟล์ที่ชื่อ GoogleServices-Info.plist
ขึ้นมาให้กับเรา ไฟล์นี้ทำหน้าที่เป็นระบุว่า App ของเรามีการเชื่อมต่อกับ Firebase Project ไหน + เป็น Key ในการเชื่อมต่อกับ Firebase ซึ่งหากมีการเปลี่ยน bundleId ภายหลัง ต้องมาทำการสร้างไฟล์นี้และติดตั้งไฟล์นี้ใหม่ด้วยครับ ให้เราทำการ Download ลงมาเลย
จากนั้นไปเปิดไฟล์ Runner.xcworkspace
ขึ้นมาด้วย X-Code
และให้เราทำการลากไฟล์ GoogleService-Info.plist
เข้ามาไว้ใน Folder ชื่อRunner
อย่าลืมเลือก Copy items if needed ด้วยนะครับ เพื่อให้มัน Copy ไฟล์นี้เข้ามาใน XCode Project
คราวนี้กลับมาที่ Firebase กด Next ข้ามไปที่หัวข้อที่ 5 ได้เลยครับ หัวข้อที่ 3 และ 4 ถ้าเราลง Dependency cloud_firestore ไปแล้วก็ถือว่าเสร็จสิ้นแล้วครับ
ซึ่งในหัวข้อที่ 5 นี้ Firebase จะรอสัญญาณการติดต่อจาก App ของเรา ให้เราลอง Run Project ของเราไปที่ iOS (Uninstall App อันเก่าจากอุปกรณ์ของเราก่อน และรันให้มันติดตั้ง App ใหม่อีกรอบครับ)
หากการติดตั้ง GoogleService-Info.plist
ของเราถูกต้อง Firebase จะได้รับสัญญาณจาก App ของเราและจะเป็นการเสร็จสิ้นขั้นตอนการติดตั้ง Firebase บน iOS ครับ
Note: ขั้นตอนการ Verify นี้อาจใช้เวลาในระหว่างที่ Firebase Update การเชื่อมต่อกับ App เรา หากใช้เวลานานเกินไปให้ลองตรวจสอบว่าเราทำทุกอย่างถูกต้องหรือไม่ หรือ ลอง Uninstall App และ flutter clean
และเริ่มรัน Project ใหม่อีกทีครับ
Android:
มาที่ฝั่งของ Android กันบ้าง อันดับแรกไปเพิ่ม App ที่ Firebase เหมือน iOS เลยครับ
จากนั้นใส่ applicationId ที่เราไปเอามาในขั้นตอนที่แล้วในช่อง packageName ครับ ที่เหลือก็เป็น Optional ครับ และกด Register App
จากนั้น Firebase จะสร้างไฟล์ชื่อ google-services.json
ขึ้นมาให้กับเรา หน้าที่ไฟล์นี้เหมือนกับใน iOS เลยครับ คือบอกว่า App เราต้องติดต่อกับ Firebase Project ไหน + เป็น Key ในการเชื่อมต่อกับ Firebase
ให้เรา Download ลงมาและเปิด Folder ของ Android โดยให้เราเอาไฟล์ google-services.json
เข้าไปวางที่Folder android > app
จากนั้นกลับไปไฟล์ build.gradle
ที่ android > build.bradle และให้เราทำการเพิ่ม บรรทัดนี้เข้าไป ในหัวข้อ dependencies (สามารถทำตามขั้นตอนใน Firebase ขั้นตอนที่ 3)
classpath 'com.google.gms:google-services:4.3.3'
และไปที่ build.gradle
ระดับ app ( android > app > build.gradle ) ให้เพิ่มคำสั่งนี้เข้าไปในบรรทัดล่างสุด
apply plugin: 'com.google.gms.google-services'
และเพิ่ม บรรทัดนี้เข้าไปในหัวข้อ dependencies
implementation 'com.google.firebase:firebase-analytics:17.2.2'
จากนั้นกลับไปที่ Firebase และกดต่อมาในขั้นตอนที่ 4 (หากตัวเลข Version ในขั้นตอนที่ 3 เปลี่ยนไป สามารถเปลี่ยนได้ตาม Version ล่าสุดเลยคับบ)
ขั้นตอนนี้ Firebase จะรอการติดต่อจาก App ของเรา ให้เรารัน App ของเราไปที่ Android ครับ
หากการติดตั้งถูกต้องทุกอย่าง Firebase จะได้รับสัญญาณจาก App ของเรา เป็นอันจบการติดตั้งบน Android ครับ
Note: ขั้นตอนการ Verify นี้อาจใช้เวลาในระหว่างที่ Firebase Update การเชื่อมต่อกับ App เรา หากใช้เวลานานเกินไปให้ลองตรวจสอบว่าเราทำทุกอย่างถูกต้องหรือไม่ หรือ ลอง Uninstall App และ flutter clean
และเริ่มรัน Project ใหม่อีกทีครับ
Note: บางครั้งที่เรา Build อาจเกิด Error ขึ้น ตรงนี้แก้ไขตามอาการครับ เพราะ Environment แต่ละคนไม่เหมือนกัน ในบทความนี้ ผมจะเอาบาง Error ที่ผมเจอมาบ่อยๆ พร้อมกับ วิธีแก้มาเขียนไว้ครับ
Err-1) Cannot fit requested classed in a single dex file
Solution)
ให้เราทำการ เพิ่ม บรรทัดนี้ ลงไปใน build.gradle
ระดับ app (android > app > build.gradle)
multiDexEnabled true
{Start Developing}
Concept ของ App Demo นี้ ผมจะทำเป็นแอพที่สามารถเพิ่มข้อมูล(ข้อมูลสินค้า) เข้าใน Firestore และ ดึงข้อมูลลงมาแสดงได้ทั้งแบบ Realtime และ Onetime ถ้าพร้อมแล้วมาเริ่มกันเลย
Note: แนะนำให้โหลดโค๊ดและดูไปพร้อมกับบทความนี้ได้เลยครับ โค๊ดอยู่ใน Github แล้วครับบ
0) ก่อนอื่นมาเตรียม Cloud Firestore ให้พร้อมกันก่อนดีกว่า
สิ่งที่ต้องทำคือสร้าง Colleciton ชื่อ catalog ขึ้นมาใน Firestore เพื่อไว้รับข้อมูลสินค้าทั้งหมดที่จะเกิดขึ้น
1) เริ่มกันที่ส่วน เพิ่มข้อมูลเข้า Firestore (เพิ่มสินค้า)
เราสามารถเพิ่มข้อมูลเข้า Firestore ด้วยการใช้คำสั่งนี้ครับ .setData()
Firestore.instance.collection("").document("").setData({ "...": "...", "...": "..."
}).then((data) { ...}).catchError((e) { ...});
หลักการของ .setData()
จะเหมือนกับ .set()
ใน NodeJS ครับ ถ้า Document Id นี้ยังไม่มี มันจะสร้าง Document มาใหม่ครับ แต่ถ้า Document นี้มีอยู่แล้ว มันจะเข้าไปแก้ไขข้อมูลใน Document เป็น Field ที่มีการสั่ง Set ใหม่แทนครับ
เนื่องจาก .setData()
จะ Return ค่าที่เป็น Future ออกมา (Future ก็คล้ายๆกับ Promise ใน Javascript ครับ) เราสามารถที่จะใช้ .then()
และ .catchError()
เพื่อ Handle สิ่งต่างๆที่จะเกิดขึ้นภายหลังจากส่งคำสั่งได้ด้วยคับบ
UI สำหรับ Add เบื้องต้นครับบ
ซึ่งถ้าทำตามผมมาตลอด พอลองกดปุ๊บก็จะ Crash ทั้นที 5555+
Error PERMISSION_DENIED เกิดเพราะ เรายังไม่ได้ทำการตั้ง Database Rules เพื่ออนุญาติให้ App ของเราเข้าไปเอาข้อมูลจาก Firestore ครับ ให้เรากลับไปที่ Firebase > Firestore อีกครั้ง และไปที่ Tab Rules
ซึ่งในบทความผมจะ Allow ให้มีการ read,write จากทุกที่ไปก่อนเพื่อความสะดวกในการทำงาน แต่ ผมไม่แนะนำให้ใช้ตลอด เนื่องจากทุกคนสามารถเข้ามาแก้ไข Database ของเราได้เลย โดยเราสามารถทำให้ปลอดภัยมากยิ่งขึ้นด้วยการใช้ Firebase Authentication ร่วมด้วยกัน ซึ่งผมจะมาเขียนบทความเกี่ยวกับ Flutter x Firebase Authentication เร็วๆนี้ครับ
ลองดูเพิ่มเติมเกี่ยวกับ Security Rules ของ Cloud Firestore ได้ใน วีดีโอนี้ครับ
คราวนี้ลอง Run อีกที ก็ไม่ติดปัญหาแล้วคับบบ 👍👍👍
2) คราวนี้มา ดึงข้อมูลจาก Firestore ลงมากันดีกว่า
การดึงข้อมูลลงมาจาก Firestore นั้นจะมีอยู่หลายแบบตามนี้เลยครับ
- ดึงแค่ 1 Document
เราจะใช้คำสั่ง .get()
ในการดึงข้อมูลลงมาครับ
ซึ่งก็มันจะ Return ค่า Future<DocumentSnapshot> กลับมาให้เรา ก็จัดการ handle เป็น .then()
และ .catchError()
ให้เรียบร้อยครับ
Firestore.instance.collection("").document("").get().then((value) { ...}).catchError((e) { ...});
ซึ่งเราจะสามารถ Decode DocumentSnapshot ได้ตามนี้เลยครับ
Note: อ้างอิงค์จากคำสั่งด้านบนนะครับ
value.data["index"]
มันจะ Return Map ของ Data ทั้งหมดใน Document นี้ออกมาครับ โดยเราสามารถเลือกว่าจะเอาข้อมูลอะไรได้ด้วยการเอา Index ของข้อมูลนั้นใส่แทนตรง index ได้เลยครับvalue.documentID
อันนี้จะ Return String เป็น Document ID ที่เราไปดึงมาครับvalue.exists
จะ Return bool ว่า Document ที่เราเรียก มีอยู่ใน Collection นั้นหรือไม่ (True = อยู่, False = ไม่อยู่)
2. ดึงทั้ง Collection
เราจะใช้ .getDocument()
เพื่อดึงข้อมูลทั้ง Collection นั้นลงมา
จะ Return ค่าเป็น Future<QuerySnapshot> มาครับ
Firestore.instance.collection("").getDocuments().then((value) { ...}).catchError((e) { ...});
ซึ่งเราจะสามารถ Decode QuerySnapshot ได้ตามนี้เลยครับ
Note: อ้างอิงค์จากคำสั่งด้านบนนะครับ
คำสั่งหลักๆก็จะมี
value.documents.forEach((docSnapshot) {})
เนื่องจากเรา Query มาทั้ง Collection เลย ทำให้เมื่อเราเรียก value.documents จะได้ออกมาเป็น List ดังนั้นการนำค่าออกจึงต้อง.forEach
มันออกมา ซึ่งใน หลังจาก forEach แล้วเราจะได้ค่า DocumentSnapshot ออกมา วิธีการ Decode อยู่ด้านบนครับvalue.documents.isEmpty
เราสามารถตรวจสอบได้ว่าใน QuerySnapshot นี้มีอย่างน้อย 1 Document หรือไม่ (True= มี ≥ 1 , False = 0)value.documents.length
จะ Return จำนวนข้อมูลทั้งหมดที่เราได้มาใน QuerySnapshot นี้
Note: value.documents
จะ Return ค่าที่เป็น List ของ DocumentSnapshot ออกมา ดังนั้นคำสั่งทั้งหมดที่ใช้กับค่าที่เป็น List ก็จะสามารถใช้ต่อจากคำสั่งนี้ได้เช่นกัน
3. ดึงลงมาแบบมีเงื่อนไข (ค้นหา )
Firestore.instance.collection("").where("Field", /*Condition*/).getDocuments().then((value) {
...}).catchError((e) { ...});
จะ Return ค่าเป็น Future<QuerySnapshot> มาครับ สามารถอ่านได้ในหัวข้อที่ 2 เลยคับบ
โดยที่ Condition เราสามารถใส้ได้ตามนี้ครับ
isEqualto
: รับค่า DynamicisLessThen
: รับค่า DynamicisLessThanOrEqualTo
: รับค่า DynamicisGreaterThan
: รับค่า DynamicisGreaterThanOrEqualTo
: รับค่า DynamicarrayContains
: รับค่า DynamicarrayContainsAny
: รับค่า List<dynamic>whereIn
: รับค่า List<dynamic>isNull
: รับค่า Bool
ตัวอย่าง ในการส่ง Query ก็จะเป็น
Firestore.instance.collection("SuperSecret")
.where("isSecret", isEqualTo: false)
.where("secret_name", isEqualTo: "ABCD")
.getDocuments()
สั่งเกตุว่าสามารถใช้ .where()
ได้มากกว่า 1 ครั้งเพื่อกำหนด Condition มากกว่า 1 ได้ด้วยเช่นกัน
กลับมาที่ App Demo ของเรา เราต้องทำหน้าแรกที่เอาไว้แสดงสินค้าทั้งหมดให้กับ user ได้เลือกสินค้าของเราที่มี ดังนั้นผมต้องเอาข้อมูลทั้งหมดใน Collection catalog มาแสดงในหน้านี้
ซึ่ง Firestore ก็มีความสามารถนึงที่สุดยอดมากๆ นั้นก็คือการทำ Streaming Data แบบ Realtime และใน Flutter ก็มีสิ่งที่เรียกว่า StreamBuilder ที่เอาไว้ทำเรื่อง Streaming ค่า Realtime พอเอาทั้งสองมารวมกันทำให้เราสามารถที่จะ Stream ค่าบน Firestore ลงมาใน Flutter เมื่อมีการ Update ใน Firestore เเอพของเราก็จะได้รับกันอัพเดทไปพร้อมกัน
ตัวอย่างโค๊ดการใช้งาน StreamBuilder ครับ สามารถไปวางไว้ใน Body ได้เลย เนื่องจากเป็น Widget นั้งเองครับ ซึ่งผมจะให้มันสร้างเป็น List ของ Card ที่ Stream ค่าจาก Firestore ลงมา
ต่อไปก็ทำให้ เมื่อกดที่ Card ของเราก็ให้มีหน้านึงขั้นมาแสดงรายละเอียดสินค้าของเราซักหน่อย โดยผมจะใช้การ Query ใหม่ เพื่อเป็นตัวอย่างครับ โดยผมจะเริ่มเรียกข้อมูลตอนอยู่ใน initState เลย โค๊ดก็จะได้ตามนี้เลยครับ
โอเค มาดูผลลัพท์ทั้งหมดของเรากันดีกว่า
โค๊ดตัวอย่างในบทความนี้อยู่ใน Github ของผมเลยครับ
{Outro}
จบไปแล้วนะครับ สำหรับบทความแนะนำการเชื่อมต่อ Flutter App กับ Firebase (Cloud Firestore) ซึ่งอยู่ในหัวข้อ Flutter x Firebase อนาคตผมจะมาเขียนเกี่ยวกับ Services อื่นๆของ Firebase ให้อีกแน่นอน Services เค้ามีเยอะจริงๆ เขียนอันเดียวไม่ไหวแน่ๆ 5555+
ถ้าชอบบทความนี้อย่าลืมกด Clap และกด Follow ผมด้วยนะคับบ ^^
สุดท้ายนี้
ม้าที่ว่าแน่แล้วก็ยังแพ้ลา เพราะ ลาไปก่อน..……..
ขอบคุณที่เข้ามาอ่านบทความนี้ แล้วพบกันในบทความถัดไปครับ,
ต้นน้ำ