在 Kotlin 序列化中使用 DataStore

咱們以前已經 分享Proto DataStore 和 Preferences DataStore 的使用方法。這兩個 DataStore 版本都會在後臺使用 Protos 對數據進行序列化。您也可使用 Kotlin 序列化,結合使用 DataStore 與自定義數據類。這有助於減小樣板代碼,且無需學習或依賴於 Protobuf 庫,同時仍能夠爲數據提供架構。html

您須要完成如下幾項操做:android

  • 定義數據類
  • 確保您的數據類不可變
  • 使用 Kotlin 序列化實現 DataStore 序列化器
  • 開始使用

定義數據類

Kotlin 數據類 很是適合與 DataStore 結合使用,這是由於它們可以與 Kotlin 序列化無縫協做。DataStore 會依賴數據類自動生成的 equalshashCode。數據類也會生成便於調試和更新數據的 toStringcopy 函數。git

/* Copyright 2021 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 */

data class UserPreferences(
    val showCompleted: Boolean,
    val sortOrder: SortOrder
)

確保您的數據類不可變

確保您的數據類不可變是很是重要的,這是由於 DataStore 沒法兼容可變類型。結合使用可變類型與 DataStore 會致使難以捕獲的錯誤和競爭條件。數據類並不是必定不可變。github

Vars 是可變的,因此您應使用 vals 代替:json

/* Copyright 2021 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 */

data class MyData(
-    var num: Int
+    val num: Int
)
-  myObj.num = 5  // Fails to compile when num is val
+  val newObj = myObj.copy(num = 5)

數組是可變的,因此您不該將其公開。數組

/* Copyright 2021 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 */

data class MyData(
-    var num: IntArray
)
-  myObj.num = 5 // This would mutate your object

即便將只讀列表用做數據類的一部分,該數據類也仍爲可變的。您應考慮改用 不可變/持久化集合:安全

/* Copyright 2021 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 */

data class MyData(
-    val nums: List<Int>
+    val nums: PersistentList<Int>
)

-  val myInts = mutableListOf(1, 2, 3, 4)
-  val myObj = MyData(myInts)
-  myInts.add(5) // Fails to compile with PersistentList, but mutates with List
+  val newData = myObj.copy(
+      nums = myObj.nums.mutate { it += 5 } // Mutate returns a new PersistentList
+  )

將可變類型用做數據類的一部分會令數據類變爲可變狀態。您不該採起上述作法,反而要確保全部內容都是不可變類型。架構

/* Copyright 2021 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 */
 
data class MyData(
-    val mutableType: MutableType
)
 
-  val myType = MutableType()
-  val myObj = MyData(myType)
-  myType.mutate()

實現 DataStore 序列化器

Kotlin 序列化支持包括 JSON 和協議緩衝區在內的 多種格式。我將在此處使用 JSON,由於它十分常見、易於使用且會以明文形式進行存儲,便於調試。Protobuf 也是一個不錯的選擇,由於它規模更小、速度更快且兼容 protobuf-liteide

要使用 Kotlin 序列化讀取數據類並將其寫入 JSON,您須要使用 @Serializable 註釋數據類並使用 Json.decodeFromString<YourType>(string)Json.encodeToString(data)。如下是帶有 UserPreferences 的示例:函數

/* Copyright 2021 Google LLC.  
    SPDX-License-Identifier: Apache-2.0 */
 
 @Serializable
 data class UserPreferences(
     val showCompleted: Boolean = false,
     val sortOrder: SortOrder = SortOrder.None
 )
 
object UserPreferencesSerializer : Serializer<UserPreferences> {
  override val defaultValue = UserPreferences()

  override suspend fun readFrom(input: InputStream): UserPreferences {
    try {
      return Json.decodeFromString(
        UserPreferences.serializer(), input.readBytes().decodeToString())
    } catch (serialization: SerializationException) {
      throw CorruptionException("Unable to read UserPrefs", serialization)
    }
  }

  override suspend fun writeTo(t: UserPreferences, output: OutputStream) {
    output.write(Json.encodeToString(UserPreferences.serializer(), t).encodeToByteArray())
  }
}

⚠️ 將 Parcelables 與 DataStore 一塊兒使用並不安全,由於不一樣 Android 版本之間的數據格式可能會有所變化。

使用序列化器

在您構建時,將您建立的序列化器傳遞到 DataStore:

/* Copyright 2021 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 */

val Context.dataStore by dataStore("my_file.json", serializer = UserPreferencesSerializer)

其讀取數據看起來與使用 protos 進行讀取同樣:

/* Copyright 2021 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 */

suspend fun getShowCompleted(): Boolean {
  context.dataStore.data.first().showCompleted
}

您可使用生成的 .copy() 函數更新數據:

/* Copyright 2021 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 */

suspend fun setShowCompleted(newShowCompleted: Boolean) {
  // This will leave the sortOrder value untouched:
  context.dataStore.updateData { it.copy(newShowCompleted = showCompleted) }
}

總結

結合使用 DataStore 與 Kotlin 序列化和數據類可減小樣板文件並有助於簡化代碼,但您必須多加當心,避免由於可變性而引起錯誤。您只需定義數據類和實現序列化器便可。快來動手嘗試一下吧!

如要詳細瞭解 DataStore,您能夠查看咱們的 文檔 並得到一些使用 Proto DataStorePreferences DataStore Codelab 的實踐經驗。

相關文章
相關標籤/搜索