iT邦幫忙

2021 iThome 鐵人賽

DAY 10
1
Mobile Development

Jetpack Compose X Android Architecture X Functional Reactive Programming系列 第 10

Firebase Firestore

還記得便利貼專案做到哪了嗎?專案目前用的架構模式是 MVVM :Jetpack Compose 所做成的 View, BoardViewModel 還有 InMemoryNoteRepository ,其中間的資料更新機制是由 RxJava 來完成。但是,我們有寫任何的 subscribeOn 或者是 observeOn 嗎?完全沒有,對吧!所以目前來說,任何的程式碼,不管是 View, ViewModel 還是 Model 都是在 Main thread 上執行的。

由於之前所示範的都是在本地端執行的,所以沒有什麼太大的必要硬要去切換執行緒,如果寫了subscribeOn 或者是 observeOn 還有點多此一舉的感覺,對吧?但是今天就需要用到他們了,今天會使用 Firebase Firestore 來當作專案的資料儲存中心,在傳輸資料的過程中會需要連上網路,因此我們就需要在某些地方進行執行緒切換,才能確保程式運行無誤。

原本的 InMemoryNoteRepository 將會被 Firestore 的實作給取代,根據 SOLID principle 的 Liskov substitution principle ,在抽換實作時,不應該影響原有的行為,所以 BoardViewModel 與 View 層的所有程式碼都不會因為抽換實作而需要修改內容,這是非常重要的一件事,如果今天只是因為換了 Repository 的實作而讓 BoardViewModel 的運作機制變了,那就表示設計上是有問題的,職責不夠專一,可能就要考慮先重構,等到行為確定不會有問題時再換實作。

Firestore

FireStore 是一個有彈性、易擴展的 NoSQL 雲端資料庫,可以用來儲存還有同步客戶端以及伺服器端的資料,該資料庫的特性為:靈活的數據結構、簡易查詢、實時更新、離線支持、為擴展而設計,以下列出幾個對於便利貼 APP 來說很方便的特性:

  • 靈活的數據結構:不像關連式資料庫,該資料庫的結構非常單純,有表達多個文件概念的 Collection ,以及組合概念的 Document,最後則是最小單元的 Data 。這種結構有一個很方便的地方是,如果我們要臨時增加新的資料格式的話,其實就只要在 Document 增加新的 Data 就好,不需要做 DB migration 。
  • 實時更新:同步資料一直是一個非常困難的問題,不同手機或網頁之間有不一致的資料時,通常需要花不少間來處理,但現在有了 Firestore,我們就可以花更多心力在開發 App 上。
  • 離線支持:這也是一個非常棒的功能!為了能夠支援離線功能,很多 App 的基礎架構都包含了網路元件跟資料庫元件,這些資料的整合通常是很無聊又繁瑣,Firestore 預設就支援離線讓我們不需要自己再實作本地端的資料庫。

至於其餘特性想瞭解更多的,請查看 Firestore 官方文件:https://firebase.google.com/docs/firestore

Firebase 在官方文件中的繁體翻譯是:“火力地堡”,聽起來像是一個很厲害的防禦基地XD

建立 Firebase 專案

1. 新增專案

在 Firebase console 中增加一個新專案,並在專案名稱中輸入“StickyNote”

Screen Shot 2021-09-04 at 11.21.00 AM.png

在步驟二的時候選擇不啟用 Google Analytic,建立專案就完成了!

2. 啟用 Firebase firestore

進入專案後,你將會在左邊的側邊欄上看到“Firestore Database”,點選它

Screen Shot 2021-09-04 at 11.27.14 AM.png

接著再點擊畫面中間的“建立資料庫”

Screen Shot 2021-09-04 at 11.28.01 AM.png

  1. 選擇“以測試模式啟動”
  2. Cloud Firestore 位置 → asia-east1 (這裡是台灣,當然你想選其他位置也隨意,不是正式版選哪一個都無所謂)
  3. 點擊啟用

3. 新增應用程式

在左邊側邊欄點選“專案總覽”,然後在螢幕中間你將會看到一個 Android 小綠人的圖案:

Screen Shot 2021-09-04 at 11.36.16 AM.png

點擊 Android 小綠人之後,輸入應用程式套件名稱,以我的例子是“com.yanbin.stickynote”,其他欄位暫時都不需要填,接著下載“google-service.json”,下載完之後將檔案放到根目錄底下。

Screen Shot 2021-09-04 at 11.39.11 AM.png

使用 gradle 來新增 Firebase SDK:

apply plugin: 'com.android.application'

// Add this line
apply plugin: 'com.google.gms.google-services'

dependencies {
  // Import the Firebase BoM & firestore
  implementation platform('com.google.firebase:firebase-bom:28.4.0')
  implementation "com.google.firebase:firebase-firestore"
}

最後點擊 Android studio 上的 Sync,大功告成!

如果不想因為實驗而弄壞資料庫的話,也可以考慮這個 Codelab 中教學的使用 Firebase Emulator:https://firebase.google.com/codelabs/firestore-android#0

新增資料

接下來就試試看在 Firebase console 上新增資料吧,之後本地端就可以使用 SDK 來獲取這上面的資料,讓我們回顧一下資料格式是長怎樣的:

data class Note(
    val id: String,
    val text: String,
    val position: Position,
    val color: YBColor) {

    companion object {
        fun createRandomNote(): Note {
            val randomColorIndex = Random.nextInt(YBColor.defaultColors.size)
            val randomPosition = Position(Random.nextInt(-50, 50).toFloat(), Random.nextInt(-50, 50).toFloat())
            val randomId = UUID.randomUUID().toString()
            return Note(randomId, "Hello", randomPosition, YBColor.defaultColors[randomColorIndex])
        }
    }
}

data class Position(val x: Float, val y: Float) {

    operator fun plus(other: Position): Position {
        return Position(x + other.x, y + other.y)
    }
}

data class YBColor(
    val color: Long
) {
    companion object {
        val HotPink = YBColor(0xFFFF7EB9)
        val Aquamarine = YBColor(0xFF7AFCFF)
        val PaleCanary = YBColor(0xFFFEFF9C)
        val Gorse = YBColor(0xFFFFF740)

        val defaultColors = listOf(HotPink, Aquamarine, PaleCanary, Gorse)
    }
}

每一個 Note 會有這些資料儲存在資料庫中:id, text, position, color。心裡有個底之後,我們就先來試試看 Firestore 是如何新增資料的:

Screen Shot 2021-09-04 at 2.48.39 PM.png

點選新增集合,他要你輸入集合 ID,先在此輸入“Notes”,點選下一步。

Screen Shot 2021-09-04 at 2.50.02 PM.png

下一個出現的是新增文件,點選自動產生的 ID,這裡的每一個文件將會對應到我們的資料結構: Note 。接下來我們將每個需要的欄位一一填入:

  • id : 使用文件 ID 即可,這邊不需要為了它再建立額外的欄位
  • text:在欄位中填入 text ,類型選擇 string 不用改,至於值的部分就先填入 “Hello”
  • position:在我們的 Model 中這是一個有 x 跟 y 的資料結構,不是一個 primitive type ,在這邊要怎麼填入呢?有兩種方式:新增一個 Map 類型的欄位,然後在下一個階層中個別新增 x 跟 y 兩個類型為 string 欄位,如下圖所示。或是將 position 拉平,直接新增兩個 string 的欄位。

為什麼位置不是使用 number 而是 string 呢?其實也沒有什麼不行...真要說原因的話,就是我之前在實作時以為 number 只能使用整數,後來查文件才知道 number 也可以是小數點,就將錯就錯了...但是也不是什麼大問題,最後需要的時候再改過來就行了,這邊是容易修改的技術細節。

Screen Shot 2021-09-04 at 3.37.48 PM.png

  • color:欄位填上 color ,類型選擇 number ,值就填上 4294901660

最後我是選擇扁平的 position ,因為這樣比較單純而且在接資料時也相對簡單,新增完資料之後會像下面這樣子:

Screen Shot 2021-09-04 at 3.17.24 PM.png

SDK 跟後台設定都已完成,明天會來進行資料串接。

補充資料:


上一篇
MultiThreading and Custom extension function.
下一篇
整合 Firestore SDK 到便利貼應用程式
系列文
Jetpack Compose X Android Architecture X Functional Reactive Programming30

尚未有邦友留言

立即登入留言