1. 事前準備
在先前的程式碼研究室中,我們已說明如何使用 Room (資料庫抽象層) 將資料儲存在 SQLite 資料庫中。本程式碼研究室會介紹 Jetpack DataStore。DataStore 以 Kotlin 協同程式和 Flow 為基礎而設計,共提供兩種不同的實作方式,一種是專門儲存類型物件的 Proto DataStore,另一種則是專門儲存鍵/值組合的 Preferences DataStore。
本程式碼研究室會說明如何使用 Preferences DataStore,Proto DataStore 則不在本程式碼研究室的說明範圍內。
必要條件
- 您熟悉 Android 架構元件
ViewModel、LiveData和Flow,也瞭解如何使用ViewModelProvider.Factory將ViewModel執行個體化。 - 您熟悉並行的基礎知識。
- 您瞭解如何使用協同程式來處理長時間執行的工作。
課程內容
- DataStore 是什麼?您應使用 DataStore 的原因及時機為何?
- 如何將 Preference DataStore 新增至應用程式。
軟硬體需求
- Words 應用程式的範例程式碼 (與先前程式碼研究室中的 Words 應用程式解決方案程式碼相同)。
- 已安裝 Android Studio 的電腦。
下載本程式碼研究室的範例程式碼
在本程式碼研究室中,您將會從先前的解決方案程式碼擴充 Word 應用程式的功能。範例程式碼可能包含您在先前程式碼研究室中也熟悉的程式碼。
如要從 GitHub 取得本程式碼研究室的程式碼,並在 Android Studio 中開啟,請按照下列步驟操作:
- 啟動 Android Studio。
- 在「Welcome to Android Studio」視窗中,按一下「Get from VCS」。

- 在「Get from Version Control」對話方塊中,確認您已為「Version Control」選取「Git」。

- 將提供的程式碼網址貼到「URL」方塊中。
- 您也可以將「Directory」變更為與建議預設值不同的內容。

- 按一下「Clone」。Android Studio 會開始擷取程式碼。
- 等待 Android Studio 開啟。
- 在程式碼研究室的範例程式碼、應用程式或解決方案程式碼中,選取正確的模組。

- 按一下「Run」按鈕
,即可建構並執行程式碼。
2. 入門應用程式總覽
Words 應用程式包含兩個畫面:第一個畫面會顯示使用者可選取的字母,第二個畫面則會顯示開頭為所選字母的字詞清單。
|
|
這個應用程式可讓使用者透過選單選項,切換為以清單或格狀版面配置來顯示字母。
|
|
- 下載範例程式碼,然後在 Android Studio 中開啟並執行應用程式。系統會以線性版面配置顯示字母。
- 輕觸右上角的選單選項。版面配置會切換為格狀版面配置。
- 結束應用程式並重新啟動。您可以在 Android Studio 中使用「Stop ‘app'」(停止「應用程式」)
和「Run ‘app'」(執行「應用程式」)
選項。請注意,重新啟動應用程式後,字母會以線性版面配置顯示,而不是格狀。
|
|
|
請注意,系統不會保留使用者的選擇。本程式碼研究室會說明如何修正此問題。
建構項目
- 在本程式碼研究室中,您會瞭解如何使用 Preferences DataStore,保留 DataStore 中的版面配置設定。
3. Preferences DataStore 簡介
Preferences DataStore 適合用於簡單的小型資料集,例如儲存登入詳細資料、深色模式設定、字型大小等等。DataStore 不適用於複雜的資料集,例如線上雜貨店的商品目錄清單或學生資料庫。如果需要儲存大型或複雜的資料集,建議您使用 Room 而非 DataStore。
使用 Jetpack DataStore 程式庫就能建立簡單、安全且非同步的 API,可用來儲存資料。其提供兩種不同的導入方式:Preferences DataStore 和 Proto DataStore。雖然 Preferences DataStore 和 Proto DataStore 都能儲存資料,但卻使用不同的做法:
- Preferences DataStore 可依據鍵來存取和儲存資料,而不必事先界定結構定義 (資料庫模型)。
- Proto DataStore 使用通訊協定緩衝區來界定結構定義。使用通訊協定緩衝區 (或 Protobufs) 可讓您保留強類型資料。與 XML 和其他類似的資料格式相比,Protobufs 更快更簡單,而且更清晰明確。
Room 與 Datastore 的比較:適用時機
如果您的應用程式需要以 SQL 等結構化格式儲存大型/複雜的資料,建議您使用 Room。不過,如果您只想儲存簡單或少許資料,且這些資料可以儲存在鍵/值組合中,建議您使用 DataStore。
Proto DataStore 與 Preferences DataStore 的比較:使用時機
Proto DataStore 類型安全有效,但需要設定。如果您的應用程式資料夠簡單,可以儲存在鍵/值組合中,那麼易於設定的 Preferences DataStore 則較為適合。
將 Preferences DataStore 新增為依附元件
要將 DataStore 整合至應用程式,第一步是將其新增為依附元件。
- 在
build.gradle(Module: Words.app)中新增下列依附元件:
implementation "androidx.datastore:datastore-preferences:1.0.0"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
4. 建立 Preferences DataStore
- 新增名為
data的套件,並在其中建立名為SettingsDataStore的 Kotlin 類別。 - 在類型
Context的SettingsDataStore類別中加入建構函式參數。
class SettingsDataStore(context: Context) {}
- 在
SettingsDataStore類別之外,宣告名為LAYOUT_PREFERENCES_NAME的private const val,並為其指派字串值layout_preferences。這是你在下一步要執行個體化的 Preferences Datastore 名稱。
private const val LAYOUT_PREFERENCES_NAME = "layout_preferences"
- 請在類別外使用
preferencesDataStore委托來建立DataStore執行個體。由於您使用 Preferences Datastore,因此需要將Preferences傳遞做為資料儲存庫類型。此外,請將name資料儲存庫設為LAYOUT_PREFERENCES_NAME。
完成的程式碼如下:
private const val LAYOUT_PREFERENCES_NAME = "layout_preferences"
// Create a DataStore instance using the preferencesDataStore delegate, with the Context as
// receiver.
private val Context.dataStore : DataStore<Preferences> by preferencesDataStore(
name = LAYOUT_PREFERENCES_NAME
)
5. 實作 SettingsDataStore 類別
如前所述,Preferences DataStore 會以鍵/值組合的形式儲存資料。在這個步驟中,您會定義儲存版面配置設定所需的鍵,也會定義要寫入和讀取 Preferences DataStore 的函式。
鍵類型函式
有別於 Room,Preferences DataStore 並不會使用預先定義的結構定義,而是使用對應的鍵類型函式,來定義您儲存在 DataStore<Preferences> 執行個體中每個值的鍵。舉例來說,如要定義 int 值的鍵,請使用 intPreferencesKey();要定義 string 值的鍵,則使用 stringPreferencesKey()。整體來說,這些函式名稱的前置字串會與所儲存鍵的資料類型相同。
在 data\SettingsDataStore 類別中實作以下內容:
- 如要導入
SettingsDataStore類別,首先請建立用於儲存布林值的鍵,該布林值會指定使用者設定是否屬於線性版面配置。建立名為IS_LINEAR_LAYOUT_MANAGER的private類別屬性,並使用booleanPreferencesKey()(傳入is_linear_layout_manager鍵名稱做為函式參數) 來初始化。
private val IS_LINEAR_LAYOUT_MANAGER = booleanPreferencesKey("is_linear_layout_manager")
寫入 Preferences DataStore
現在,請開始使用鍵,並將布林值版面配置設定儲存在 DataStore 中。Preferences DataStore 提供 edit() 暫停函式,可以交易形式更新 DataStore 中的資料。函式的轉換參數接受程式碼區塊,您可以視需要更新值。轉換區塊的所有程式碼皆視為單一交易。原理上,交易作業會移至 Dispacter.IO 底下,因此在呼叫 edit() 函式時,別忘了將函式設為 suspend。
- 建立一個名為
saveLayoutToPreferencesStore()的suspend函式,該函式採用以下兩個參數:版面配置設定布林值和Context。
suspend fun saveLayoutToPreferencesStore(isLinearLayoutManager: Boolean, context: Context) {
}
- 實作上述函式,呼叫
dataStore.edit(),並傳遞程式碼區塊以儲存新的值。
suspend fun saveLayoutToPreferencesStore(isLinearLayoutManager: Boolean, context: Context) {
context.dataStore.edit { preferences ->
preferences[IS_LINEAR_LAYOUT_MANAGER] = isLinearLayoutManager
}
}
從 Preferences DataStore 讀取
Preferences DataStore 會公開儲存在 Flow<Preferences> 的資料,只要偏好設定有所變更,該程式碼就會發出資料。您不想公開整個 Preferences 物件,只需公開 Boolean 值即可。因此,我們會對應 Flow<Preferences>,並取得您所需的 Boolean 值。
- 公開根據
dataStore.data: Flow<Preferences>建構的preferenceFlow: Flow<UserPreferences>,進行對應以擷取Boolean偏好設定。由於 Datastore 在首次執行時沒有任何內容,因此系統會預設傳回true。
val preferenceFlow: Flow<Boolean> = context.dataStore.data
.map { preferences ->
// On the first run of the app, we will use LinearLayoutManager by default
preferences[IS_LINEAR_LAYOUT_MANAGER] ?: true
}
- 如果下列匯入項目未自動匯入,請新增以下資訊:
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
例外狀況處理
DataStore 從檔案讀取及寫入資料,系統可能會在存取資料時出現 IOExceptions。您可以使用 catch() 運算子來擷取例外狀況,以處理這些問題。
- 若在讀取資料時發生錯誤,SharedPreference DataStore 會擲回
IOException。在preferenceFlow宣告中,請在map()之前使用catch()運算子擷取IOException,並發出emptyPreferences()。為求簡單,我們預計此處不會出現其他類型的例外情形;如果出現其他類型的例外狀況,請重新擲回該例外狀況。
val preferenceFlow: Flow<Boolean> = context.dataStore.data
.catch {
if (it is IOException) {
it.printStackTrace()
emit(emptyPreferences())
} else {
throw it
}
}
.map { preferences ->
// On the first run of the app, we will use LinearLayoutManager by default
preferences[IS_LINEAR_LAYOUT_MANAGER] ?: true
}
你現在可以使用 data\SettingsDataStore 類別了!
6. 使用 SettingsDataStore 類別
在下一個工作中,您將會在 LetterListFragment 類別中使用 SettingsDataStore。您要將觀察程式附加到版面配置設定,並據此更新使用者介面。
請在 LetterListFragment 中採取下列步驟:
- 宣告稱為
SettingsDataStore且類型為SettingsDataStore的private類別變數。由於您後將會初始化這個變數,因此請將其設為lateinit。
private lateinit var SettingsDataStore: SettingsDataStore
- 在
onViewCreated()函式的末尾,請初始化新變數,然後將requireContext()傳遞至SettingsDataStore建構函式。
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
...
// Initialize SettingsDataStore
SettingsDataStore = SettingsDataStore(requireContext())
}
讀取及觀察資料
- 在
LetterListFragment的onViewCreated()方法中,於SettingsDataStore初始化底下,使用asLiveData()將preferenceFlow轉換為Livedata。請附加一個觀察程式,並傳遞到viewLifecycleOwner做為擁有者。
SettingsDataStore.preferenceFlow.asLiveData().observe(viewLifecycleOwner, { })
- 在觀察程式內,將新的版面配置設定指派給
isLinearLayoutManager變數。呼叫chooseLayout()函式以更新 RecyclerView 版面配置。
SettingsDataStore.preferenceFlow.asLiveData().observe(viewLifecycleOwner, { value ->
isLinearLayoutManager = value
chooseLayout()
})
完成的 onViewCreated() 函式應如下所示:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
recyclerView = binding.recyclerView
// Initialize SettingsDataStore
SettingsDataStore = SettingsDataStore(requireContext())
SettingsDataStore.preferenceFlow.asLiveData().observe(viewLifecycleOwner, { value ->
isLinearLayoutManager = value
chooseLayout()
})
}
將版面配置設定寫入 DataStore
最後一個步驟則是在使用者輕觸選單選項時,將版面配置設定寫入 Preferences DataStore。您應以同步方式在協同程式中將資料寫入 Preferences DataStore。如要在片段中執行此操作,請使用名為 LifecycleScope 的 CoroutineScope。
LifecycleScope
生命週期感知元件 (如片段) 為應用程式中的邏輯範圍以及與 LiveData 的互通層提供一流支援。系統會為每個 Lifecycle 物件定義 LifecycleScope。Lifecycle 擁有者遭到刪除時,系統就會取消此範圍內啟動的所有協同程式。
- 在
LetterListFragment的onOptionsItemSelected()函式內,於R.id.action_switch_layout案件的結尾,使用lifecycleScope來啟動協同程式。在launch區塊內,呼叫saveLayoutToPreferencesStore()以傳遞isLinearLayoutManager和context。
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_switch_layout -> {
...
// Launch a coroutine and write the layout setting in the preference Datastore
lifecycleScope.launch {
SettingsDataStore.saveLayoutToPreferencesStore(isLinearLayoutManager, requireContext())
}
...
return true
}
- 執行應用程式。按一下選單選項來變更應用程式的版面配置。
|
|
- 現在,請測試 Preferences DataStore 的持續性。將應用程式版面配置變更為格狀版面配置。結束應用程式並重新啟動 (您可以在 Android Studio 中使用「Stop ‘app'」(停止「應用程式」)
和 「Run ‘app'」(執行「應用程式」)
選項)。

重新啟動應用程式後,字母現在會以格狀版面配置顯示,而不是線性版面配置。您的應用程式已成功儲存使用者選取的版面配置!
請注意,雖然字母現在會以格狀版面配置顯示,但選單圖示未正確更新。我們接下來會說明如何解決這個問題。
7. 修正選單圖示錯誤
選單圖示錯誤,原因在於在 onViewCreated() 中,RecyclerView 版面配置的更新依據是版面配置設定而而不是選單圖示。只要在更新 RecyclerView 版面配置時同時重畫選單,即可解決這個問題。
重畫選項選單
建立選單後,系統就不會多此一舉地為每個頁框重畫相同的選單。invalidateOptionsMenu() 函式會指示 Android 重畫選項選單。
變更「選項」選單的內容 (例如新增選單項目、刪除項目或是變更選單文字或圖示) 時,您可以呼叫這個函式。在本例中,選單圖示已變更。呼叫此方法就會宣告「選項」選單已變更,並應重新建立選單。下次需要顯示時選項選單時,就會呼叫 onCreateOptionsMenu(android.view.Menu) 方法。
- 在
LetterListFragment中的onViewCreated()內,於preferenceFlow觀察程式結尾,呼叫chooseLayout()的底下,透過對activity呼叫invalidateOptionsMenu()來重畫選單。
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
...
SettingsDataStore.preferenceFlow.asLiveData().observe(viewLifecycleOwner, { value ->
...
// Redraw the menu
activity?.invalidateOptionsMenu()
})
}
- 再次執行應用程式,並變更版面配置。
- 結束應用程式並重新啟動。請注意,選單圖示現在已正確更新。

恭喜!您已成功將 Preferences DataStore 新增至應用程式,以便儲存使用者的選擇。
8. 解決方案程式碼
本程式碼研究室的解決方案程式碼位於下方顯示的專案和模組中。
9. 總結
- DataStore 提供採用 Kotlin 協同程式和 Flow 的完全非同步 API,可確保資料的一致性。
- Jetpack DataStore 是一項資料儲存解決方案,可讓您使用通訊協定緩衝區來儲存鍵/值組合或類型物件。
- DataStore 提供兩種實作方式:Preferences DataStore 和 Proto DataStore。
- Preferences DataStore 不會使用預先定義的結構定義。
- Preferences DataStore 使用對應的鍵類型函式,定義每個需要儲存在
DataStore<Preferences>執行個體中的值。舉例來說,想定義int值的鍵,請使用intPreferencesKey()。 - Preferences DataStore 提供
edit()函式,可以交易形式更新DataStore中的資料。

