DataStore

536 阅读2分钟

目前好像就在1.0.0版本了

最新版本查看

使用文档

Preferences DataStore 和 Proto DataStore

DataStore 提供两种不同的实现:Preferences DataStore 和 Proto DataStore。

  • Preferences DataStore 使用键存储和访问数据。此实现不需要预定义的架构,也不确保类型安全。
  • Proto DataStore 将数据作为自定义数据类型的实例进行存储。此实现要求您使用协议缓冲区来定义架构,但可以确保类型安全。

1. Preferences DataStore

implementation("androidx.datastore:datastore-preferences:1.0.0")

开始使用,先创建扩展对象,

// At the top level of your kotlin file:
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")

这个存储虽说也是一对一,不是这里的键不是以前的string,它是一个类Preferences.Key,但是了,不用担心,系统都给安排好了,如下,你要存储啥类型的数据,就用下边的方法即可,直接返回给你要的Key

@JvmName("intKey")
public fun intPreferencesKey(name: String): Preferences.Key<Int> = Preferences.Key(name)

@JvmName("doubleKey")
public fun doublePreferencesKey(name: String): Preferences.Key<Double> = Preferences.Key(name)

@JvmName("stringKey")
public fun stringPreferencesKey(name: String): Preferences.Key<String> = Preferences.Key(name)

@JvmName("booleanKey")
public fun booleanPreferencesKey(name: String): Preferences.Key<Boolean> = Preferences.Key(name)

@JvmName("floatKey")
public fun floatPreferencesKey(name: String): Preferences.Key<Float> = Preferences.Key(name)

@JvmName("longKey")
public fun longPreferencesKey(name: String): Preferences.Key<Long> = Preferences.Key(name)

@JvmName("stringSetKey")
public fun stringSetPreferencesKey(name: String): Preferences.Key<Set<String>> =
    Preferences.Key(name)

读数据

val EXAMPLE_COUNTER = intPreferencesKey("example_counter")
val exampleCounterFlow: Flow<Int> = context.dataStore.data
  .map { preferences ->
    // No type safety.
    preferences[EXAMPLE_COUNTER] ?: 0
}

写数据

suspend fun incrementCounter() {
  context.dataStore.edit { settings ->
    val currentCounterValue = settings[EXAMPLE_COUNTER] ?: 0
    settings[EXAMPLE_COUNTER] = currentCounterValue + 1
  }
}

下边演示下使用compose动态修改主题颜色的代码,如下是自定义theme的代码

private val LightThemeColors = lightColors(
    primary = Red700,
    primaryVariant = Red900,
    onPrimary = Color.White,
    secondary = Red700,
    secondaryVariant = Red900,
    onSecondary = Color.White,
    error = Red800,
    onBackground = Color.Black,

    )
private val LightThemeColors2 = lightColors(
    primary = Blue700,
    primaryVariant = Blue900,
    onPrimary = Color.White,
    secondary = Blue700,
    secondaryVariant = Blue900,
    onSecondary = Color.White,
    error = Blue800,
    onBackground = Color.Black,

    )

private val DarkThemeColors = darkColors(primary = Red300,
    primaryVariant = Red700,
    onPrimary = Color.Black,
    secondary = Red300,
    onSecondary = Color.Black,
    error = Red200,
    onBackground = Color.White)

val Context.dataStore by preferencesDataStore("mytheme")
    //默认的主题themeid为0,另外一套为1
val themeKey: Preferences.Key<Int>
    get() = intPreferencesKey("mythemekey")

@Composable
fun JetnewsTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) {

    val context = LocalContext.current
    val theme = remember {
        context.dataStore.data.map { preferences ->
            preferences[themeKey] ?: 0
        }
    }
    val mytheme = theme.collectAsState(initial = 0).value
    val lightTheme = if (mytheme == 0) LightThemeColors else LightThemeColors2
    
    MaterialTheme(colors = if (darkTheme) DarkThemeColors else lightTheme,
        typography = JetnewsTypography,
        shapes = JetnewsShapes,
        content = content)
}

下边是修改的地方,弄了两个button,点击修改主题

Column(Modifier
    .padding(it)
    .fillMaxSize(), verticalArrangement = Arrangement.Center) {
   
    val scope = rememberCoroutineScope()
    val context = LocalContext.current
    var theme by remember {
        val theme = context.dataStore.data.map { preferences ->
            preferences[themeKey] ?: 0
        }
        mutableStateOf(theme)
    }

    var mytheme = theme.collectAsState(initial = 0).value
    Text(text = "current theme $mytheme")
    Row {
        RadioButton(selected = mytheme == 0, onClick = {
            scope.launch {
                context.dataStore.edit { preferences ->
                    preferences[themeKey] = 0
                }
            }
        })
        Text(text = "default theme 0")

    }
    Row {
        RadioButton(selected = mytheme == 1, onClick = {
            scope.launch {
                context.dataStore.edit { preferences ->
                    preferences[themeKey] = 1
                }
            }
        })
        Text(text = "theme 1")
    }
}

2. Proto DataStore

implementation("androidx.datastore:datastore:1.0.0")

2.1 数据的定义
首先需要在下边的目录 app/src/main/proto/ 下添加一个xxx.proto文件
proto语法 developers.google.com/protocol-bu…
android studio 可以安装一个插件,这样方便书写proto文件,你新建一个proto后缀的文件,会自动提示你要不要安装的,点安装就可以了.比如

syntax = "proto3";

option java_package = "com.example.application";
option java_multiple_files = true;

message SearchRequest { 
    string query = 1; 
    int32 page_number = 2; 
    int32 result_per_page = 3; }

当然了,这个proto文件需要编译的,这样才会生成message对应的java类
window系统下,用android studio
参考这里:这个比较早

或者参考 codelab,建议用这个
简单记录下需要添加的内容

plugins {
    ...
    id "com.google.protobuf" version "0.8.17"
}

dependencies {
    implementation  "androidx.datastore:datastore:1.0.0"
    implementation  "com.google.protobuf:protobuf-javalite:3.19.4"
    ...
}

protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:3.19.4"
    }

    // Generates the java Protobuf-lite code for the Protobufs in this project. See
    // https://github.com/google/protobuf-gradle-plugin#customizing-protobuf-compilation
    // for more information.
    generateProtoTasks {
        all().each { task ->
            task.builtins {
                java {
                    option 'lite'
                }
            }
        }
    }
 
// 默认生成目录 $buildDir/generated/source/proto 通过 generatedFilesBaseDir 改变生成位置
//generatedFilesBaseDir = "$projectDir/src/main"

}

sync一下,完事rebuild,就可以在build目录下看到生成的class类了,如下图

image.png

message对应的类已经生成了,下边就可以继续了

创建 Proto DataStore 来存储类型化对象涉及两个步骤

  1. 定义一个实现 Serializer<T> 的类,其中 T 是 proto 文件中定义的类型。此序列化器类会告知 DataStore 如何读取和写入您的数据类型。请务必为该序列化器添加默认值,以便在尚未创建任何文件时使用。
  2. 使用由 dataStore 创建的属性委托来创建 DataStore<T> 的实例,其中 T 是在 proto 文件中定义的类型。在您的 Kotlin 文件顶层调用该实例一次,便可在应用的所有其余部分通过此属性委托访问该实例。filename 参数会告知 DataStore 使用哪个文件存储数据,而 serializer 参数会告知 DataStore 第 1 步中定义的序列化器类的名称

createDataStore 除了需要知道文件名字,还需要一个Serialize对象,其实自动生成的那几个java文件里把序列化反序列化都写好了,如下,直接调用方法即可parseFrom,writeTo
如下,先写个message类对应的序列化类

import androidx.datastore.core.Serializer
import com.example.jetnews.SearchRequest
import java.io.InputStream
import java.io.OutputStream

object SearchRequestSerialize :Serializer<SearchRequest>{
    override val defaultValue: SearchRequest
        get() = SearchRequest.getDefaultInstance()

    override suspend fun readFrom(input: InputStream): SearchRequest {
        return  SearchRequest.parseFrom(input)
    }

    override suspend fun writeTo(t: SearchRequest, output: OutputStream) {
        t.writeTo(output)
    }
}

接着创建dataStore

val Context.requestDataStore: DataStore<SearchRequest> by dataStore(
    fileName = "requestData.pb",
    serializer = SearchRequestSerialize
)

现在就可以存取SearchRequest数据了,下边演示了如何存取

@Composable
fun testProtoData() {
    val scope = rememberCoroutineScope()
    val context = LocalContext.current
    val flowRequest by remember {
        mutableStateOf(context.requestDataStore.data)
    }
    val request = flowRequest.collectAsState(initial = SearchRequest.getDefaultInstance()).value
    
    Text(text = "request data: ${request}")
    
    val page = request.pageNumber
    
    Button(onClick = {
        scope.launch {
            context.requestDataStore.updateData { searchRequest ->
                searchRequest.toBuilder().setPageNumber(page + 1).setQuery(page.toString()).setResultPerPage(page*2).build()
            }
        }
    }) {
        Text(text = "change request data,page +1")
    }
}

end.