iT邦幫忙

2026 iThome 鐵人賽

DAY 26
0
Software Development

Android 鐵人賽: 天命最高 - 陪伴大家一步步打造屬於自己的app系列 第 26

Android 雲端資料庫初體驗:Firebase Realtime Database 資料寫入與 App 串接

  • 分享至 

  • xImage
  •  

Day 26|Android 雲端資料庫初體驗:Firebase Realtime Database 資料寫入與 App 串接

前一天我們已經練習了 RecyclerView 與 Room,讓 Android App 可以把資料儲存在手機本機。

到了 Day 26,資料準備「上雲端」了。

今天要正式接觸 Firebase,並使用 Firebase Realtime Database,讓 Android App 不再只有單機資料,而是可以把資料寫進雲端資料庫。

今天的重點:
Android App → Firebase SDK → Realtime Database → 雲端資料

參考資料:Firebase 官方網站


☁️ 為什麼需要 Firebase?

前面使用 Room 時,資料基本上是:

Android App
     │
     ▼
   Room
     │
     ▼
 SQLite
     │
     ▼
 手機本機

這種方式很適合:

  • 離線資料
  • App 設定
  • 快取
  • 個人裝置上的資料

但假設今天做的是:

智慧水杯墊
飲料點餐系統
會員系統
IoT 裝置
多人共用資料

如果資料只存在手機裡,就會遇到一個問題:

換一支手機,原本的資料怎麼辦?

因此我們開始需要「雲端資料庫」。

Firebase 是 Google 提供的 App 開發平台,其中包含 Authentication、Realtime Database、Cloud Firestore、Cloud Storage、Cloud Messaging 等服務。

參考資料:Firebase 官方文件


一、先建立 Firebase 專案

今天第一件事情,就是進入 Firebase Console。

Firebase Console

從畫面可以看到 Firebase Console 中已經存在數個 Firebase 專案,例如:

new-firebase-2024
FirebaseAPP
FabTest
BitLinkWeb

也可以從:

建立新的 Firebase 專案

開始建立新的 Firebase Project。

Firebase Console:

Firebase Console


二、今天要使用 Realtime Database

Firebase 提供不只一種資料庫。

今天我們先使用:

Firebase Realtime Database

它是一個雲端 NoSQL 資料庫。

與傳統 SQL 資料庫不同,它的資料結構比較接近:

JSON Tree

例如今天程式建立的資料,可以想成:

class_2
│
├── 1
│   ├── name  : AAAA
│   ├── email : aaa@gmail.com
│   ├── phone : 11111
│   └── sex   : Female
│
├── 2
│   ├── name  : BBBB
│   ├── email : bbb@gmail.com
│   └── phone : 22222
│
├── 3
│   ├── name  : CCCC
│   ├── email : ccc@gmail.com
│   └── phone : 33333
│
└── 4
    ├── name  : Mary
    ├── email : mary@gmail.com
    └── phone : 44444

這也是今天程式碼最重要的觀念之一。

參考資料:Firebase Realtime Database 官方文件


三、Android 取得 FirebaseDatabase

今天的 Firebase 範例首先宣告:

private lateinit var dataRef: DatabaseReference
private lateinit var fbControl: FirebaseDatabase

接著在 onCreate() 中:

fbControl = FirebaseDatabase.getInstance()
Log.d("main", "fbControl = $fbControl")

FirebaseDatabase.getInstance() 可以取得 Firebase Realtime Database 的實例。

所以可以先把它理解成:

FirebaseDatabase
       │
       ▼
取得 Firebase Realtime Database
       │
       ▼
準備讀寫雲端資料

這是 Android App 與 Firebase Database 建立操作入口的重要一步。

參考資料:Firebase Android Realtime Database 使用說明


四、DatabaseReference 是什麼?

接下來:

dataRef = fbControl.getReference().child("class_2")

這裡取得的是:

DatabaseReference

可以把 DatabaseReference 想成:

「Firebase 資料庫裡某一個位置的參考位置。」

例如:

Root
 │
 └── class_2

程式:

fbControl.getReference()

代表資料庫根節點。

再接:

.child("class_2")

就會指向:

/class_2

因此:

dataRef

現在就是指向 class_2DatabaseReference

參考資料:DatabaseReference API


五、第一筆 Firebase 資料

今天最直觀的寫法是:

dataRef.child("1").child("name").setValue("AAAA")
dataRef.child("1").child("email").setValue("aaa@gmail.com")
dataRef.child("1").child("phone").setValue("11111")

拆開看就非常容易理解。

第一行:

dataRef.child("1").child("name").setValue("AAAA")

相當於:

class_2
   │
   └── 1
       │
       └── name
           │
           └── AAAA

第二行:

dataRef.child("1").child("email").setValue("aaa@gmail.com")

結果:

class_2
   │
   └── 1
       ├── name  : AAAA
       └── email : aaa@gmail.com

第三行再加入:

dataRef.child("1").child("phone").setValue("11111")

最後就形成:

class_2
   │
   └── 1
       ├── name  : AAAA
       ├── email : aaa@gmail.com
       └── phone : 11111

也就是說:

child()

負責往下一層節點走。

而:

setValue()

負責把值寫進該位置。

參考資料:Firebase Android 讀寫資料


六、一次建立多筆資料

今天程式接著建立三組資料:

dataRef.child("1").child("name").setValue("AAAA")
dataRef.child("1").child("email").setValue("aaa@gmail.com")
dataRef.child("1").child("phone").setValue("11111")

dataRef.child("2").child("name").setValue("BBBB")
dataRef.child("2").child("email").setValue("bbb@gmail.com")
dataRef.child("2").child("phone").setValue("22222")

dataRef.child("3").child("name").setValue("CCCC")
dataRef.child("3").child("email").setValue("ccc@gmail.com")
dataRef.child("3").child("phone").setValue("33333")

這樣 Firebase 的結構就會變成:

class_2
│
├── 1
│   ├── name  : AAAA
│   ├── email : aaa@gmail.com
│   └── phone : 11111
│
├── 2
│   ├── name  : BBBB
│   ├── email : bbb@gmail.com
│   └── phone : 22222
│
└── 3
    ├── name  : CCCC
    ├── email : ccc@gmail.com
    └── phone : 33333

這就是 NoSQL 樹狀資料結構非常直觀的地方。

參考資料:Firebase Database 資料結構說明


七、已經存在的資料還可以增加欄位

接下來有一行很值得注意:

dataRef.child("1").child("sex").setValue("Female")

原本:

1
├── name
├── email
└── phone

現在又增加:

1
├── name
├── email
├── phone
└── sex

也就是:

1
├── name  : AAAA
├── email : aaa@gmail.com
├── phone : 11111
└── sex   : Female

從這裡可以開始感受到 Firebase Realtime Database 與傳統關聯式資料庫操作方式的差異。

參考資料:Firebase Realtime Database 資料結構


八、使用 Map 一次寫入一整筆資料

前面的寫法是:

.child("name").setValue(...)
.child("email").setValue(...)
.child("phone").setValue(...)

但是如果一筆資料有很多欄位,一行一行寫會比較麻煩。

所以今天又用了另一種方法:

val data = mutableMapOf<String, String>()

data["name"] = "Mary"
data["email"] = "mary@gmail.com"
data["phone"] = "44444"

dataRef.child("4").setValue(data)

這次不是逐欄寫入,而是先建立:

Map

內容:

name  → Mary
email → mary@gmail.com
phone → 44444

最後:

dataRef.child("4").setValue(data)

一次把整個 Map 寫進:

class_2/4

結果就是:

4
├── name  : Mary
├── email : mary@gmail.com
└── phone : 44444

這比逐欄呼叫 setValue() 更接近實際 App 的資料處理方式。

參考資料:Firebase 寫入資料官方說明


九、從 RecyclerView 到 Firebase

其實今天的內容並不是突然跳到另一個完全無關的東西。

前面的 RecyclerView 練習已經把資料整理成:

MutableList<MutableMap<String, Any>>

例如飲料資料:

data.put("name", nameArray[i])
data.put("price", priceArray[i])
data.put("pic", picArray.getResourceId(i, R.drawable.blacktea))
data.put("qty", 0)

再透過 RecyclerView 顯示:

飲料名稱
價格
圖片
數量

而 Adapter 中還加入:

+ → 數量 +1
- → 數量 -1

最後結帳時:

sum += qty * price

把選購項目及總價整理出來。

因此現在開始學 Firebase,下一步其實就非常清楚:

RecyclerView
     │
     ▼
使用者選商品
     │
     ▼
修改 qty
     │
     ▼
按下結帳
     │
     ▼
產生訂單資料
     │
     ▼
Firebase Realtime Database

參考資料:RecyclerView 官方文件


十、RecyclerView 的另一種寫法:Data Class

今天的資料裡還有另一個 RecyclerView 範例。

先建立:

data class Flower(
    val name: String,
    val imageResourceId: Int
)

這比:

MutableMap<String, Any>

更具有明確的資料型別。

接著 FlowerAdapter 接收:

private val flowerList: List<Flower>

並且在:

override fun onBindViewHolder(
    holder: FlowerViewHolder,
    position: Int
)

取得:

val flower = flowerList[position]

然後:

holder.nameTextView.text = flower.name
holder.picImageView.setImageResource(flower.imageResourceId)

最後加入點擊 Callback:

holder.itemView.setOnClickListener {
    onItemClick(flower)
}

這就是很典型的:

Data Class
    ↓
List<Flower>
    ↓
RecyclerView.Adapter
    ↓
ViewHolder
    ↓
畫面

架構。

參考資料:Kotlin Data Classes 官方文件


十一、RecyclerView 的 Callback

另一份範例則使用:

val adapterClick: (MutableMap<String, Any>) -> Unit

在 Adapter 裡:

holder.itemView.setOnClickListener {
    Log.d("main", "it")
    adapterClick(data)
}

然後回到 Activity:

val adapter = MyAdapter(this, dataList) { itemData ->
    Log.d("main", "main click = $itemData")

    textViewName.text = itemData.get("name").toString()
    imageViewPic.setImageResource(itemData.get("pic") as Int)
}

這裡其實就是之前學過的 Lambda 真正開始運用在 Android UI。

流程:

使用者點 RecyclerView Item
             │
             ▼
         Adapter
             │
             ▼
       adapterClick()
             │
             ▼
         MainActivity
             │
       ┌─────┴─────┐
       ▼           ▼
   修改文字      修改圖片

這也是為什麼前面學 Kotlin Lambda 並不是只為了考語法,進到 Android 後會一直看到它。

參考資料:Kotlin Lambda 官方文件


十二、Intent 資料傳遞也串起來了

今天的資料中還保留了之前的飲料點餐範例。

MainActivity 選擇飲料後:

val intent = Intent(this, SelectActivity::class.java)
intent.putExtra("selected_drink", selectedDrinkName)
launcher.launch(intent)

進入 SelectActivity

第二個 Activity 接收:

val selectedDrink = intent.getStringExtra("selected_drink")
edDrink.setText(selectedDrink)

使用者設定:

飲料
數量
甜度
冰塊

再用 Bundle 回傳:

val intent = Intent()
val bundle = Bundle()

bundle.putString("drink", drink)
bundle.putString("quantity", quantity)
bundle.putString("sugar", sugar)
bundle.putString("ice", ice)

intent.putExtras(bundle)

setResult(Activity.RESULT_OK, intent)
finish()

MainActivity 再透過:

registerForActivityResult(
    ActivityResultContracts.StartActivityForResult()
)

接收結果並計算:

val quantity = quantityStr.toIntOrNull() ?: 0
val total = quantity * selectedDrinkPrice

最後顯示:

飲料名稱
數量
甜度
冰塊
總價

參考資料:Android Activity Result API


十三、Day 26 到底串起了哪些東西?

一路寫到今天,會發現之前學過的東西其實開始連起來了:

Kotlin
 │
 ├── Array
 ├── List
 ├── Map
 ├── Data Class
 └── Lambda
       │
       ▼
Android
 │
 ├── Activity
 ├── Intent
 ├── Bundle
 ├── ListView
 └── RecyclerView
       │
       ▼
資料處理
 │
 ├── Room
 └── SQLite
       │
       ▼
今天
 │
 ▼
Firebase
 │
 ▼
Realtime Database
 │
 ▼
Cloud

前面看似一個一個獨立的小練習,到了 Firebase 開始可以慢慢組成真正的 App。

參考資料:Firebase Android 開發文件


十四、今天 Firebase 的核心程式

把今天最重要的程式濃縮起來,就是:

// 取得 Firebase Realtime Database
fbControl = FirebaseDatabase.getInstance()

// 取得 class_2 節點
dataRef = fbControl.getReference().child("class_2")

// 寫入第一筆資料
dataRef.child("1").child("name").setValue("AAAA")
dataRef.child("1").child("email").setValue("aaa@gmail.com")
dataRef.child("1").child("phone").setValue("11111")

// 寫入第二筆資料
dataRef.child("2").child("name").setValue("BBBB")
dataRef.child("2").child("email").setValue("bbb@gmail.com")
dataRef.child("2").child("phone").setValue("22222")

// 寫入第三筆資料
dataRef.child("3").child("name").setValue("CCCC")
dataRef.child("3").child("email").setValue("ccc@gmail.com")
dataRef.child("3").child("phone").setValue("33333")

// 在既有節點增加 sex
dataRef.child("1").child("sex").setValue("Female")

// 使用 Map 建立第四筆資料
val data = mutableMapOf<String, String>()

data["name"] = "Mary"
data["email"] = "mary@gmail.com"
data["phone"] = "44444"

// 一次寫入整筆資料
dataRef.child("4").setValue(data)

這段先掌握四個關鍵字:

FirebaseDatabase
DatabaseReference
child()
setValue()

Firebase Realtime Database 最基本的資料寫入就已經建立起來了。

參考資料:Firebase Realtime Database Android 讀寫資料


十五、Firebase 與 Room 的差異

經過 Day 25 與 Day 26,剛好可以做一個簡單比較:

比較 Room Firebase Realtime Database
資料位置 手機本機 雲端
底層概念 SQLite NoSQL JSON Tree
網路 可完全離線 主要用於同步雲端資料
多裝置 需另外設計同步 適合跨裝置同步
Android 整合 Jetpack Firebase SDK
常見用途 本機資料、快取 即時資料、多人共享、IoT

所以實際專案不一定是:

Room OR Firebase

也可能是:

        Android App
          /     \
         /       \
      Room      Firebase
       │           │
    本機資料      雲端資料

兩者其實可以一起使用。

參考資料:Android Room 官方文件Firebase Realtime Database


十六、今天完成的資料流程

最後把 Day 26 整理成一張流程:

┌─────────────────────┐
│     Android App     │
└──────────┬──────────┘
           │
           ▼
┌─────────────────────┐
│ FirebaseDatabase    │
│ .getInstance()      │
└──────────┬──────────┘
           │
           ▼
┌─────────────────────┐
│ DatabaseReference   │
│ child("class_2")    │
└──────────┬──────────┘
           │
           ▼
┌─────────────────────┐
│ child("1")          │
│ child("name")       │
│ setValue("AAAA")    │
└──────────┬──────────┘
           │
           ▼
☁️ Firebase Realtime Database
           │
           ▼
class_2
├── 1
│   ├── name
│   ├── email
│   ├── phone
│   └── sex
├── 2
├── 3
└── 4

從今天開始,我們的 Android App 已經不再侷限於手機本機。

資料正式上雲端了。

參考資料:Firebase Realtime Database 官方文件


Day 26 小結

今天主要完成三個階段的整合:

第一階段:RecyclerView

資料 → Adapter → ViewHolder → 畫面

第二階段:Intent

MainActivity
      ↓
SelectActivity
      ↓
Bundle
      ↓
MainActivity

第三階段:Firebase

Android
   ↓
Firebase SDK
   ↓
DatabaseReference
   ↓
setValue()
   ↓
Realtime Database

其中今天真正需要記住的 Firebase 基礎語法就是:

FirebaseDatabase.getInstance()

getReference()

child()

setValue()

前一天我們讓資料留在手機裡,今天則跨出了重要的一步:

從 Local Database 走向 Cloud Database。

接下來就可以繼續挑戰 Firebase 資料讀取、修改、刪除與即時監聽,讓目前只有「寫入」能力的 Android App,真正完成雲端 CRUD。

參考資料:Firebase Android 官方文件


上一篇
RecyclerView × Room:Android 清單顯示與本機資料庫 CRUD
下一篇
RecyclerView 進階實戰:從商品清單到飲料點餐系統
系列文
Android 鐵人賽: 天命最高 - 陪伴大家一步步打造屬於自己的app30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言