首先來看看如何取用 Firebase SDK 的服務:
val firestore = FirebaseFirestore.getInstance()
要取用 firestore 的服務非常簡單,只要呼叫 getInstance()
就好,也不用自己再去寫一次 Singleton pattern 的雙重鎖定,寫了只是浪費時間,Firebase 已經幫我們寫好了,相關實作可以在原始碼中找到:
// FirebaseFirestore.java
@NonNull
public static FirebaseFirestore getInstance() {
FirebaseApp app = FirebaseApp.getInstance();
if (app == null) {
throw new IllegalStateException("You must call FirebaseApp.initializeApp first.");
}
return getInstance(app, DatabaseId.DEFAULT_DATABASE_ID);
}
// FirebaseApp.java
@NonNull
public static FirebaseApp getInstance() {
synchronized (LOCK) {
FirebaseApp defaultApp = INSTANCES.get(DEFAULT_APP_NAME);
if (defaultApp == null) {
throw new IllegalStateException(
"Default FirebaseApp is not initialized in this "
+ "process "
+ ProcessUtils.getMyProcessName()
+ ". Make sure to call "
+ "FirebaseApp.initializeApp(Context) first.");
}
return defaultApp;
}
}
基於我們之前所新增的資料,那時候新增了一個 "Notes" 的 Collection,所以現在我們要想辦法使用 firestore api 來查詢這些資料,其查詢方式也非常簡單:
private val query = firestore.collection(COLLECTION_NOTES)
.limit(100)
Firestore 是使用類似 builder pattern 的方式來組合出查詢,以上述的查詢為例,我們指定了要查詢 COLLECTION_NOTES
這個 Collection ,而且數量限制 100 筆,組合出查詢之後,接著就是使用該查詢來獲取資料:
query.addSnapshotListener { result, e ->
result?.let { onSnapshotUpdated(it) }
}
addSnapshotListener
可以讓我們隨時都收到最新的資訊,只要有新的更新,這個 Listener 就會再呼叫一次。其中所有更新的資訊都在 result
裡面,如果發生錯誤,result
就會是空值,錯誤的內容將會在 e
得知,下圖的說明為 Firestore 的源碼:
Firestore 轉換資料有兩種方式,第一個是使用反射幫你轉換成 Model,這機制跟 Gson 是一樣的,第二個是自己寫轉換資料的邏輯,可以想像成是自己使用 JsonObject 跟 JsonArray 來做反序列化。
Gson 是一個很常使用於 JSON 資料轉換的函式庫,其特點是只要定義好了 Model ,而且這 Model 的格式跟 JSON 是可以一對一互相對映的,Gson 就可以使用反射的機制幫我們產生 runtime model ,專案也可以因此大大減少樣板程式碼(Boilerplate code)。
最後我選擇了後者,自己寫轉換資料格式的邏輯,原因如下:
Note
,這樣算下來,開發的時間反而還變長了。下面程式碼是方案二的實作:
private fun onSnapshotUpdated(snapshot: QuerySnapshot) {
val allNotes = snapshot
.map { document -> documentToNotes(document) }
// Use allNotes as an Observable event
}
private fun documentToNotes(document: QueryDocumentSnapshot): Note {
val data: Map<String, Any> = document.data
val text = data[FIELD_TEXT] as String
val color = YBColor(data[FIELD_COLOR] as Long)
val positionX = data[FIELD_POSITION_X] as String? ?: "0"
val positionY = data[FIELD_POSITION_Y] as String? ?: "0"
val position = Position(positionX.toFloat(), positionY.toFloat())
return Note(document.id, text, position, color)
}
附上今天專案中所用到的所有常數:
companion object {
const val COLLECTION_NOTES = "Notes"
const val FIELD_TEXT = "text"
const val FIELD_COLOR = "color"
const val FIELD_POSITION_X = "positionX"
const val FIELD_POSITION_Y = "positionY"
}
修改資料非常簡單,只要將每個欄位都儲存到 map 結構中,再呼叫 set 即可,:
private fun setNoteDocument(note: Note) {
val noteData = hashMapOf(
FIELD_TEXT to note.text,
FIELD_COLOR to note.color.color,
FIELD_POSITION_X to note.position.x.toString(),
FIELD_POSITION_Y to note.position.y.toString()
)
firestore.collection(COLLECTION_NOTES)
.document(note.id)
.set(noteData)
}
而且很方便的是,如果該 id 不存在,Firestore 就會自動幫我們建立一個新的 Document。
由於已經完成大部分實作,其餘的部分只剩下 RxJava 的整合,這裡使用的是 BehaviorSubject 來接收以及發送資料:
class FirebaseNoteRepository: NoteRepository {
private val firestore = FirebaseFirestore.getInstance()
private val notesSubject = BehaviorSubject.createDefault(emptyList<Note>())
private val query = firestore.collection(COLLECTION_NOTES)
.limit(100)
init {
query.addSnapshotListener { result, e ->
result?.let { onSnapshotUpdated(it) }
}
}
override fun getAllNotes(): Observable<List<Note>> {
return notesSubject.hide()
}
override fun putNote(note: Note) {
setNoteDocument(note)
}
private fun onSnapshotUpdated(snapshot: QuerySnapshot) {
val allNotes = snapshot
.map { document -> documentToNotes(document) }
notesSubject.onNext(allNotes)
}
private fun setNoteDocument(note: Note) {
val noteData = hashMapOf(
FIELD_TEXT to note.text,
FIELD_COLOR to note.color.color,
FIELD_POSITION_X to note.position.x.toString(),
FIELD_POSITION_Y to note.position.y.toString()
)
firestore.collection(COLLECTION_NOTES)
.document(note.id)
.set(noteData)
}
private fun documentToNotes(document: QueryDocumentSnapshot): Note {
val data: Map<String, Any> = document.data
val text = data[FIELD_TEXT] as String
val color = YBColor(data[FIELD_COLOR] as Long)
val positionX = data[FIELD_POSITION_X] as String? ?: "0"
val positionY = data[FIELD_POSITION_Y] as String? ?: "0"
val position = Position(positionX.toFloat(), positionY.toFloat())
return Note(document.id, text, position, color)
}
companion object {
const val COLLECTION_NOTES = "Notes"
const val FIELD_TEXT = "text"
const val FIELD_COLOR = "color"
const val FIELD_POSITION_X = "positionX"
const val FIELD_POSITION_Y = "positionY"
}
}
串完了 Firestore SDK ,我們就來實際跑看看吧!程式運行的結果如下:
移動的方式好奇怪!怎麼會抖來抖去的呢?到底發生了什麼事呢?我在這裡先賣個關子,大家可以猜猜看,答案明天揭曉!