สำหรับบทความนี้ก็เป็นตอนที่ 2 ของเนื้อหาที่เจ้าของบล็อกพูดใน Session : Firebase Android Codelabs ที่งาน I/O Extended Bangkok ซึ่งเนื้อหาบทความยาวพอสมควรจึงขอแบ่งออกมาเป็น 2 ตอนแทนจ้า
จากบทความที่แล้วเจ้าของบล็อกได้พูดถึงเรื่อง Analytics, Crash Reporting และ Authentication ไปแล้ว คราวนี้ก็เป็น Realtime Database กับ Remote Config กันต่อ
บทความในซีรีย์เดียวกัน
รับส่งข้อมูลง่ายๆแต่โคตรไวด้วย Firebase Realtime Database
จริงๆแล้วบริการนี้ไม่ใช่อะไรแปลกใหม่เลย เพราะมันคือบริการดั้งเดิมของ Firebase อยู่แล้ว ซึ่งเป็นบริการที่ทำให้นักพัฒนาสามารถสร้างฐานข้อมูลแบบ Realtime ได้ (ค่าในฐานข้อมูลเปลี่ยนเมื่อไร แอปฯที่เปิดใช้งานอยู่จะสามารถรับรู้ได้ทันที)
ข้ามการอธิบายไปละกัน
เริ่มจากเพิ่ม Realtime Database เข้าไปในโปรเจคผ่านหน้าต่าง Firebase Assistant เช่นเคย


ก่อนจะเริ่มโค้ด คงต้องทำความเข้าใจกับฐานข้อมูลที่เจ้าของบล็อกออกแบบไว้ใน Realtime Database ก่อนดีกว่านะ เพราะเจ้าของบล็อกออกแบบไว้แบบนี้

และต้องบอกก่อนเลยว่า ข้อมูลข้างบนนี้เจ้าของบล็อกไม่ได้ไปนั่งสร้างเตรียมไว้ใน Console เลย เพียงแค่เรียกใช้คำสั่งในโค้ดให้ตรงกับที่ต้องการ เวลาส่งข้อมูลขึ้นมาที่เซิฟเวอร์ของ Firebase มันก็จะสร้างฐานข้อมูลตามรูปแบบที่กำหนดในโค้ดให้ทันที
ดังนั้นก็มาใส่โค้ดกันเลยดีกว่า โดย Firebase Realtime Database จะใช้ชื่อคลาสว่า FirebaseDatabase ก็ประกาศแล้วกำหนดค่าให้เรียบร้อยซะ
FirebaseDatabase firebaseDatabase;
/* ... */
firebaseDatabase = FirebaseDatabase.getInstance();
ในการจัดการกับฐานข้อมูลนั้นไม่ได้เรียกใช้คำสั่งจาก FirebaseDatabase โดยตรง แต่จะเรียกใช้งานผ่านคลาสที่ชื่อว่า DatabaseReference ที่ดึงออกมาจากคลาส FirebaseDatabase อีกที
DatabaseReference rootDbRef = firebaseDatabase.getReference();
จะเห็นว่าเจ้าของบล็อกตั้งชื่อว่า rootDatabase
ทั้งนี้ก็เพราะว่า DatabaseReference ตัวนี้จะชี้ไปที่ตำแหน่งนอกสุดของฐานข้อมูล

แต่ว่าเจ้าของบล็อกต้องการชี้เข้าไปที่ global_room
ซึ่งเป็นห้องๆหนึ่งที่เจ้าของบล็อกเตรียมไว้ โดยห้องนี้จะเอาไว้คุยรวมทั้งหมด ใครก็ตามที่ล็อกอินเข้ามาในแอปฯนี้ จะสามารถคุยรวมกันได้ในห้องเดียวเลย (ไม่มีตัวอย่างการสร้างห้องแชทแยกนะ มีห้องคุยรวมแค่ห้องเดียว)
ดังนั้นเจ้าของบล็อกจะต้องใช้คำสั่ง child
เพื่อขยับให้มาชี้ที่ global_room
DatabaseReference globalRoomDbRef = rootDatabaseReference.child("global_room");

จากนั้นอยากจะเพิ่ม/ลบข้อมูล หรือทำอะไรที่เกี่ยวกับข้อมูลในฐานข้อมูลตัวนี้ ก็จะเรียกใช้งานได้ผ่าน globalRoomDbRef
แล้ว
ความสะดวกของ Realtime Database ก็คือข้อมูลที่อยากจะเพิ่ม เจ้าของบล็อกสามารถสร้าง Model Class ขึ้นมาแล้วโยนไปทั้งก้อนได้เลย แล้ว Firebase ก็จะไปสร้างฐานข้อมูลที่มีโครงสร้างตาม Model Class ที่โยนไปให้
ยกตัวอย่างเช่น
public class Message {
String text;
String user;
/* ... */
}
คลาส Message ตัวนี้เป็นคลาสที่เจ้าของบล็อกสร้างขึ้นมาเพื่อใช้เวลาที่ต้องการจะโยนข้อมูลเพิ่มเข้าไปใน Firebase นั่นเอง ดังนั้นเวลาเจ้าของบล็อกอยากจะเพิ่มข้อมูลเข้าไปในฐานข้อมูลก็แค่สร้าง Message Instance ขึ้นมา กำหนดค่าให้เรียบร้อย แล้วโยนเข้าไปในฐานข้อมูลนั่นเอง
Message message = new Message("How are you?", "[email protected]"); globalRoomDbRef.push().setValue(message);
ในการเพิ่มข้อมูลเข้าไปในฐานข้อมูลจะต้องใช้คำสั่ง push
ก่อนคำสั่ง setValue
ทุกครั้ง เพราะเป็นคำสั่งที่จะบอกให้ฐานข้อมูลรู้ว่าเป็นการเพิ่มข้อมูลใหม่เข้าไป

ถ้าเรียกใช้คำสั่ง setValue
ทันทีโดยไม่มีคำสั่ง push
ก่อน ก็จะเป็นการเขียนข้อมูลใหม่ทับเข้าไปแทนที่ของเก่า ซึ่งคงไม่ดีซักเท่าไรนัก
เดี๋ยวก่อนนะ แล้ว -KLIPPiAGA0caFKKt2x6
มาจากไหน?
จะเห็นว่าในข้อมูลแต่ละชุดจะมีค่าพวกนี้กำกับอยู่ข้างบนทุกอัน ซึ่งพวกนี้เรียกว่า Key ซึ่งเอาไว้กำกับข้อมูลแต่ละตัว ไม่ได้ระบุเป็น Index (เพราะมันเป็น Realtime การใช้ Index ระบุก็คงไม่เหมาะซักเท่าไรนัก)

ดังนั้นเวลาจะลบข้อมูลตัวไหนทิ้ง จะต้องระบุด้วย Key ทุกครั้ง
globalRoomDbRef.child("-KLIPPiAGA0caFKKt2x6").removeValue();
ส่วน Event เวลาข้อมูลมีการเปลี่ยนแปลงก็จะใช้ Listener อยู่สองแบบ คือ ValueEventListener กับ ChildEventListener ซึ่งมีรูปแบบการทำงานต่างกัน
private ValueEventListener valueEventListener = new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
/* ... */
}
@Override
public void onCancelled(DatabaseError databaseError) {
/* ... */
}
};
private ChildEventListener childEventListener = new ChildEventListener() {
@Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
/* ... */
}
@Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
/* ... */
}
@Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
/* ... */
}
@Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
/* ... */
}
@Override
public void onCancelled(DatabaseError databaseError) {
/* ... */
}
};
onDataChanged
ของ ValueEventListener จะถูกเรียกทุกครั้งที่ข้อมูลในฐานข้อมูลมีการเปลี่ยนแปลง โดยข้อมูลที่ส่งมาให้จะเป็นข้อมูลทั้งหมดของฐานข้อมูลนั้น ซึ่งคงไม่ค่อยเหมาะเท่าไรถ้าข้อมูลมีจำนวนเยอะๆ เพราะมันจะเปลืองเนตมากๆ
onChildAdded
, onChildChanged
, onChildRemoved
และ onChildMoved
นั้นจะเป็น Event ที่เกิดขึ้นกับข้อมูลตัวใดตัวหนึ่งเท่านั้น (ซึ่ง Firebase เรียกข้อมูลแต่ละตัวว่า Child) ซึ่ง Listener ตัวนี้จะช่วยประหยัดเนตกว่า ValueEventListener มากกว่า เพราะมันจะส่งข้อมูลมาเฉพาะตัวที่มีการเปลี่ยนแปลงเท่านั้น ดังน้ันเจ้าของบล็อกจึงสามารถยัดเก็บไว้เป็น Array เพื่อแสดงใน List View หรือ Recycler View ได้เลย โดยที่ทุกๆ Method นั้นจะมีการส่งคลาสที่ชื่อว่า DataSnapshot มาให้ เพื่อดึงข้อมูลจากคลาสตัวนี้
Message message = dataSnapshot.getValue(Message.class);
ซึ่งความสะดวกก็คือเจ้าของบล็อกสามารถโยนคลาส Message เข้าไปในคำสั่ง getValue
ได้เลย เพื่อให้มัน Map ข้อมูลกลับมาเป็นคลาส Message ให้ (คล้ายๆตอนใช้ GSON น่ะแหละ)
เวลาเรียกใช้งาน Listener ทั้งสองตัวนี้จะใช้คำสั่งใน Database Reference ที่ต้องการ (เพื่อให้ Listener สามารถทำงานกับข้อมูลเฉพาะบาง Child ที่ต้องการได้)
globalRoomDbRef.addValueEventListener(valueEventListener);
globalRoomDbRef.addChildEventListener(childEventListener);
และเวลาใช้งานเสร็จแล้วก็ควรเคลียร์ Listener ด้วย
globalRoomDbRef.removeEventListener(childEventListener);
globalRoomDbRef.removeEventListener(valueEventListener);
แต่ที่ต้องทำเพิ่มเติมเล็กน้อยก็คือคลาส Message เวลาเก็บอยู่บนฐานข้อมูลจะมี Key กำกับอยู่ด้วย และเวลาส่งเข้าใน ValueEventListener หรือ ChildEventListener ก็ตาม มันก็จะส่ง Key มาด้วยทุกครั้ง ดังนั้นโค้ดสำหรับคลาส Message เวลาเอาไปใช้งานก็คงจะไม่เพียงพอ เพราะควรจะเก็บค่า Key ของแต่ละตัวด้วย
ดังนั้นเจ้าของบล็อกก็เลยสร้างคลาสที่ชื่อว่า MessageData ขึ้นมาเพื่อเก็บค่า Key และ Message ไว้ในนั้นอีกทีหนึ่ง
public class MessageData {
String key;
Message message;
/* ... */
}
เพื่อไม่ให้สับสนขอสรุปแบบนี้นะครับ

เวลาส่งข้อมูลไปที่ฐานข้อมูล เจ้าของบล็อกจะสร้างคลาส Message ขึ้นมา กำหนดค่าที่ต้องการให้เรียบร้อยแล้วจึงส่งข้อมูลไป แต่เมื่อข้อมูลมีการอัพเดทจากฐานข้อมูลก็จะเอาข้อมูลมาทำเป็นคลาส MessageData (จะได้เก็บ Key ได้ด้วย) เพื่อเอาไปแสดงในแอปฯ
ซึ่งการแสดงข้อมูลใน List View หรือ Recycler View เจ้าของบล็อกก็ใช้ ChildEventListener ในการอัปเดตข้อมูลน่ะแหละ แต่ปัญหาอย่างหนึ่งก็คือ Realtime Database มันไม่มีคำสั่ง Fetch เพื่อดึงข้อมูลในครั้งแรกที่เปิดแอปฯ เพราะการเปิดแอปฯตอนแรกควรไปดึงข้อมูลจากฐานข้อมูลก่อนว่ามีข้อมูลเก่าอะไรบ้าง เพื่อเอามาแสดงในแอปฯ
แต่นั่นแหละปัญหา เพราะเจ้าของบล็อกหาคำสั่งดึงข้อมูลเพื่อแสดงข้อมูลที่มีอยู่แล้วก่อนหน้าไม่เจอ!!
ดังนั้นเจ้าของบล็อกจึงใช้ ValueEventListener สำหรับอัปเดตข้อมูลในตอนแรกเท่านั้น นอกจากนั้นก็จะใช้ ChildEventListener สำหรับข้อมูลแทน
เมื่ออัปเดตข้อมูลในตอนแรกด้วย ValueEventListener เสร็จแล้วก็จะสั่งลบ ValueEventListener ออกไปทันที และถ้าข้อมูลที่อัปเดตในตอนแรกมีข้อมูลอยู่ด้วย มันก็จะถูกส่งเข้ามาใน onChildAdded
ด้วยเช่นกัน ดังนั้นไม่จำเป็นต้องดึงข้อมูลจาก onDataChange
เลย
globalRoomDbRef.addValueEventListener(valueEventListener);
globalRoomDbRef.addChildEventListener(childEventListener);
/* ... */
private ValueEventListener valueEventListener = new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
// อยากรู้แค่ว่าอัปเดตข้อมูลตอนแอพเปิดขึ้นมาในตอนแรกเสร็จแล้วหรือยัง แล้วลบ ValueEventListener ทิ้ง
globalRoomDbRef.removeEventListener(valueEventListener);
/* ... */
}
/* ... */
};
private ChildEventListener childEventListener = new ChildEventListener() {
@Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
// ข้อมูลทุกตัวจากฐานข้อมูลจะถูกส่งมาทั้งหมดในตอนแรก (และครั้งต่อๆไปเมื่อมีการอัปเดต)
/* ... */
}
/* ... */
};
เนื่องจากเจ้าของบล็อกจะทำให้เข้าหน้า Chat Room แล้วทำการอัปเดตข้อมูลที่มีอยู่ในฐานข้อมูลทันที ซึ่งตรงนี้จะใช้ ValueEventListener ส่วนข้อมูลที่ส่งมาจากฐานข้อมูลจะอยู่ใน onChildAdded
แทน

ที่เหลือก็เป็นเรื่องของการเพิ่ม/ลบข้อมูลโดยใช้คำสั่งที่ได้อธิบายไว้แล้วในตอนแรก แล้วเมื่อข้อมูลมีการอัปเดต ก็จะรู้ได้ทันทีจาก onChildAdded
และ onChildRemoved

จากภาพข้างบน ไม่ว่าเครื่องไหนก็ตามที่ใช้งานฐานข้อมูลตัวนี้อยู่ ถ้ามีการเปลี่ยนแปลงใดๆก็ตามก็จะเกิด Event ทุกครั้ง ถึงแม้ว่าเครื่อง User 2 จะเป็นคนเพิ่มข้อมูลเข้าไปในฐานข้อมูล แต่ทั้ง User 1 และ User 2 ก็จะเกิด Event ที่ชื่อว่า onChildAdded
เหมือนกันทั้งคู่
ดังนั้นเจ้าของบล็อกจึงใช้คำสั่งอัปเดตข้อมูลใน List View หรือ Recycler View ก็ต่อเมื่อ onChildAdded
หรือ onChildRemoved
เท่านั้น กดเพิ่มข้อมูลไปแล้วอาจจะไม่ได้อัปเดตใน List View หรือ Recycler View ทันที จะอัปเดตก็ต่อเมื่อ Realtime Database ส่ง Event มาบอกเท่านั้น
ในที่สุดแอปฯตัวนี้ก็สามารถรับส่งข้อความระหว่างผู้ใช้หลายๆคนด้วยกันได้แล้ว จะเห็นว่าโค้ดที่เขียนนั้นไม่ยากเลย ใช้คำสั่งแค่ไม่กี่อย่าง แต่สิ่งที่สำคัญคือผู้ที่หลงเข้ามาอ่านต้องทำความเข้าใจในการทำงานของ Realtime Database ก่อนจะเอาไปใช้งานนะ
และที่สำคัญ Realtime Database จะเชื่อมกับ Authentication โดยอัตโนมัติ เมื่อล็อกอินใน Authentication เสร็จแล้วก็จะสามารถเข้าถึง Realtime Database ได้ทันที นั่นเป็นที่มาว่าทำไมเจ้าของบล็อกไม่ได้พูดถึงการกำหนดสิทธิในการเข้าถึงฐานข้อมูลของ Realtime Database เลย
ถึงเวลาของ Remote Config ซะที
จริงๆแล้วเนื้อหาที่เจ้าของบล็อกพูดใน Firebase Android Codelabs นั้นตั้งใจว่าจะพูดเรื่องนี้ด้วย แต่ด้วยเวลาที่จำกัดก็เลยทำให้พูดเรื่องนี้แบบเร่งรัดไปหน่อย
แต่ไม่เป็นไร มาอ่านในนี้แทนละกันเนอะ
ซึ่ง Remote Config จะมาช่วยให้นักพัฒนาสามารถกำหนดค่าต่างๆภายในแอปฯผ่าน Firebase ได้ ซึ่งหลักการของมันก็คือให้ไปเขียนโค้ดเตรียมไว้ โดยผูกกับค่าที่อยู่บน Firebase และบนหน้า Console ก็จะสามารถปรับเปลี่ยนค่าได้ตามต้องการ ดังนั้นจึงทำให้นำไปประยุกต์ใช้งานได้หลายแบบ
สำหรับค่าที่กำหนดนั้นให้นึกถึงว่ามันมีหน้าตาคล้ายกับ Shared Preference น่ะแหละ แต่ค่าที่เก็บไว้สามารถ Sync กับ Server ได้เพื่ออัปเดตค่าล่าสุดที่นักพัฒนากำหนดไว้
ก่อนอื่นก็ตั้งค่าใช้งาน Remote Config ให้เรียบร้อยซะ


การเรียกใช้งาน Remote Config ก็เหมือนกับตัวอื่นๆของ Firebase น่ะแหละ
FirebaseRemoteConfig firebaseRemoteConfig;
/* ... */
firebaseRemoteConfig = FirebaseRemoteConfig.getInstance();
และอย่างที่บอกไว้ว่ามันคล้ายกับ Shared Preference นั่นก็เพราะว่ามันเก็บเป็น XML ซึ่งของ Remote Config จะกำหนดว่าต้องไปสร้างไฟล์ XML ไว้ใน res/xml/
เพื่อกำหนดค่า Default สำหรับ Remote Config ไว้

สร้างเป็นไฟล์ชื่ออะไรก็ได้ เพราะเดี๋ยวจะต้องไปกำหนดเองในโค้ด Java อยู่ดี
<!-- remote_config_default.xml -->
<defaultMap>
<entry>
<key>is_special_user</key>
<value>false</value>
</entry>
<!-- ... -->
</defaultMap>
อันนี้คือตัวอย่างค่า XML ที่เจ้าของบล็อกเตรียมไว้ในแอปฯ มีค่าเป็น Boolean และถ้ามีค่าหลายๆตัวก็ต้องสร้าง <entry>
หลายๆตัว
พอสร้าง XML ไว้แล้วก็มากำหนดไว้ในโค้ดซะ
firebaseRemoteConfig.setDefaults(R.xml.remote_config_default);
โดยปกติแล้ว Remote Config จะกำหนดไว้ Default ว่าให้อัปเดตค่าจาก Server ทุกๆ 12 ชั่วโมง ซึ่งก็คงไม่สนุกซักเท่าไร ถ้าเจ้าของบล็อกกำลังพัฒนาแอปฯตัวนี้อยู่ ดังนั้นตัว SDK จึงสามารถกำหนดเป็น Developer Mode ชั่วคราวได้ เพื่อให้มันอัปเดตข้อมูลจาก Server โดยทันที
FirebaseRemoteConfigSettings remoteConfigSettings = new FirebaseRemoteConfigSettings.Builder()
.setDeveloperModeEnabled(true)
.build();
firebaseRemoteConfig.setConfigSettings(remoteConfigSettings);
ซึ่งตัว Remote Config ก็อาจจะไม่ได้อัปเดตข้อมูลโดยทันที แต่จะอัปเดตข้อมูลก็ต่อเมื่อถึงระยะเวลาที่กำหนดเท่านั้น แต่ถึงกระนั้นนักพัฒนาก็ต้องคอยเรียกใช้คำสั่ง fetch ทุกครั้ง แล้ว Remote Config จะไปเช็คเรื่องเวลาเอง ว่าเกินเวลาที่กำหนดไว้หรือไม่
เมื่อ Fetch แล้วพบว่ามีการอัปเดตจาก Server ถึงแม้ว่ารับค่าจาก Server มาแล้ว แต่ค่าเหล่านั้นจะยังไม่มีผลในทันที ต้องใช้คำสั่ง activateFetched
ก่อนทุกครั้ง ค่าที่อัปเดตใหม่ถึงจะมีผล ซึ่งตรงนี้เจ้าของบล็อกเข้าใจว่าทำเผื่อในกรณีที่แอปฯอยากจะให้ผู้ใช้เลือกว่าจะอัปเดตค่าบางอย่างของ Remote Config หรือไม่ เช่น ให้ผู้ใช้ยินยอมที่จะทดสอบใช้งานฟีเจอร์ใหม่ที่ยังพัฒนาไม่เสร็จหรือไม่ โดยที่ค่าจาก Remote Config จะไปเปลี่ยน Flag บางอย่างเพื่อให้ผู้ใช้คนนั้นสามารถทดลองใช้งานฟีเจอร์ใหม่ๆที่ว่าได้
firebaseRemoteConfig.fetch().addOnCompleteListener(new OnCompleteListener() {
@Override
public void onComplete(@NonNull Task task) {
if(task.isSuccessful()) {
firebaseRemoteConfig.activateFetched();
}
}
});
แต่ในกรณีนี้เจ้าของบล็อกจะสั่งให้ Fetch ข้อมูลจาก Server แล้ว กำหนดให้ค่าที่อัปเดตใหม่นั้นมีผลทันที
และก่อนจะเริ่มคำสั่งต่อไป ให้ไปเปิดหน้า Remote Config ใน Firebase Console เสียก่อน โดยเจ้าของบล็อกจะต้องเพิ่ม Parameter บน Server ให้ตรงกับที่สร้างไว้ใน XML


หลังจากเพิ่มข้อมูลหรือแก้ไขค่าเสร็จแล้วให้กดปุ่ม Publsh Changes
ด้วยทุกครั้ง ไม่เช่นนั้นค่าที่กำหนดไว้ไม่ส่งผลใดๆ
และเวลาต้องการเรียกใช้งานค่าจาก Remote Config เมื่อใดก็ตามก็แค่เรียกคำสั่งแบบนี้ได้เลย
boolean isSpecialUSer = firebaseRemoteConfig.getBoolean("is_special_user");
เหมือน Shared Preference ใช่มั้ยล่ะ!! แค่กำหนดคำสั่งให้ถูกต้องว่าค่าเหล่านั้นเป็นชนิดไหน ซึ่งในตัวอย่างเจ้าของบล็อกกำหนดเป็น Boolean และที่สำคัญก็คืออย่าลืมกำหนด Key ให้ถูกต้องด้วย (ในที่นี้คือ is_special_user
ซึ่งอิงจากที่สร้างไว้ใน Firebase Console และที่กำหนดไว้ใน XML)
เพียงเท่านี้ก็ทำให้แอปฯสามารถเปลี่ยนรูปแบบการทำงานตามต้องการได้ เพียงแค่กำหนดโค้ดเหล่านี้เตรียมไว้ และเมื่อใดที่ต้องการเปิดใช้งานก็แค่เข้าไปแก้ไขค่าที่อยู่ใน Firebase Console

เพิ่มเติม — นอกจากจะสั่งเปลี่ยนค่าโดยตรงแล้ว ยังกำหนดค่าแยกตามกลุ่มผู้ใช้ได้ตามต้องการ ซึ่งทำให้ประยุกต์ใช้งานได้อีกมากมาย ไม่ว่าจะเปิดฟีเจอร์พิเศษให้เฉพาะผู้ใช้บางคนก่อน หรือเลือกให้มีผลเฉพาะบางประเทศก็ได้


ครบแล้ววววว!!
ถึงแม้ว่าจะไม่ใช่การใช้งานบริการ Firebase ทุกตัว แต่ที่หยิบมาพูดใน Session ก็เป็นความสามารถหลักๆของ Firebase ที่ทำให้หลายๆคนรู้สึกว่ามันน่าสนใจมากๆเลยล่ะ ทั้งการใช้งานที่ง่าย โค้ดสั้น มี Assistant ที่แทบไม่ต้องทำอะไรให้ยุ่งยากเหมือนสมัยก่อน และที่สำคัญคือแทบไม่ต้องทำ Web Service เลย สามารถสร้างแอปฯแชทแบบง่ายๆโดยใช้เวลาเพียงไม่กี่นาทีเท่านั้นเอง
สำหรับ Codelabs ตัวนี้เจ้าของบล็อกก็โยนขึ้น Github ไว้เรียบร้อยแล้ว (ก่อนวันงานอีก) ก็สามารถเข้าไปดูตัวอย่างโค้ดกันได้ที่ Firebase Android Codelabs [Github]
และอย่างที่บอกไว้ในบทความตอนที่ 1 ว่า Repository ตัวนี้จะทำไว้ 2 Branch สำหรับผู้ที่หลงเข้ามาอ่านที่อยากดูโค้ดทั้งหมด และอยากจะลองเขียนใช้งาน Firebase ด้วยตัวเอง
ถ้าอยากลองเขียนเองดู ก็ให้ดึงจาก Branch ที่ชื่อว่า practice
นะ ซึ่งในนั้นก็จะใส่ TODO
ไว้ในตำแหน่งที่ต้องใส่โค้ดทั้งหมดแล้ว เมื่อเปิดโปรเจคขึ้นมาก็ไล่ดูจาก TODO
ได้เลยจ้า

แนะนำว่าให้เปิดหน้าต่าง TODO
ของ Android Studio เพราะมันจะช่วยให้สามารถหาตำแหน่งที่ต้องใส่โค้ดได้ง่ายขึ้น

ขอให้สนุกสนานไปกับ Firebase นะครับ