Android 架构组件 之 Data Store(5) - P
这里通过代码,比较容易理解.
文章末尾有完整的代码链接.
一、 数据层
1. 任务Task
可以定义成一个 data class.
enum class TaskPriority {
HIGH, MEDIUM, LOW
}
data class Task(
val name: String,
val deadline: Date,
val priority: TaskPriority,
val completed: Boolean = false
)
2. 任务存储库
作为一个单例,并且这里直接模拟生成数据。
实际上应该要从 Database获取.
object TasksRepository {
private val simpleDateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.US)
// In a real app, this would be coming from a data source like a database
val tasks = flowOf(
listOf(
Task(
name = "Open codelab",
deadline = simpleDateFormat.parse("2020-07-03")!!,
priority = TaskPriority.LOW,
completed = true
),
Task(
name = "Import project",
deadline = simpleDateFormat.parse("2020-04-03")!!,
priority = TaskPriority.MEDIUM,
completed = true
),
Task(
name = "Check out the code", deadline = simpleDateFormat.parse("2020-05-03")!!,
priority = TaskPriority.LOW
),
Task(
name = "Read about DataStore", deadline = simpleDateFormat.parse("2020-06-03")!!,
priority = TaskPriority.HIGH
),
Task(
name = "Implement each step",
deadline = Date(),
priority = TaskPriority.MEDIUM
),
Task(
name = "Understand how to use DataStore",
deadline = simpleDateFormat.parse("2020-04-03")!!,
priority = TaskPriority.HIGH
),
Task(
name = "Understand how to migrate to DataStore",
deadline = Date(),
priority = TaskPriority.HIGH
)
)
)
}
注意,tasks 是通过 flowOf 生成一个流对象.
3. 用户配置存储库
主要用于存储用户配置.
//{@Remove 使用UserPreferences 代替
/*private const val USER_PREFERENCES_NAME = "user_preferences"*/
//private const val SORT_ORDER_KEY = "sort_order" //@Remove}
//{@add
data class UserPreferences(
val showCompleted: Boolean,
val sortOrder: SortOrder
)
//@add}
enum class SortOrder {
NONE,
BY_DEADLINE,
BY_PRIORITY,
BY_DEADLINE_AND_PRIORITY
}
/**
* Class that handles saving and retrieving user preferences
*/
class UserPreferencesRepository(
private val dataStore: DataStore<Preferences>/*,
context: Context*/ //{@remove context and sharedPreferences}
) { //{@modify - remove singleton}
//{@remove context and sharedPreferences
/* private val sharedPreferences =
context.applicationContext.getSharedPreferences(USER_PREFERENCES_NAME, Context.MODE_PRIVATE)*/
//@remove context and sharedPreferences}
//{@Remove - 直接从 Preferences 参数获取 currentOrder, 因此可以删除
// Keep the sort order as a stream of changes
/* private val _sortOrderFlow = MutableStateFlow(sortOrder)
val sortOrderFlow: StateFlow<SortOrder> = _sortOrderFlow*/
//@Remove}
//{@Remove - 直接从 Preferences 参数获取 currentOrder, 因此可以删除
/**
* Get the sort order. By default, sort order is None.
*/
/* private val sortOrder: SortOrder
get() {
val order = sharedPreferences.getString(SORT_ORDER_KEY, SortOrder.NONE.name)
return SortOrder.valueOf(order ?: SortOrder.NONE.name)
}*/
//@Remove}
//{@add - 从 Preferences DataStore 读取数据
private object PreferenceKeys {
val SHOW_COMPLETED = booleanPreferencesKey("show_completed")
val SORT_ORDER = stringPreferencesKey("sort_order")
}
val userPreferencesFlow: Flow<UserPreferences> = dataStore.data
.catch { exception -> //处理读取数据时的异常
if (exception is IOException) {
emit(emptyPreferences())
} else {
throw exception // 其它类型的异常,重新抛出
}
}.map { preferences ->
//从 DataStore 获取 sort order
val sortOrder = SortOrder.valueOf(
preferences[PreferenceKeys.SORT_ORDER] ?: SortOrder.NONE.name
)
val showCompleted = preferences[PreferenceKeys.SHOW_COMPLETED] ?: false
UserPreferences(showCompleted, sortOrder)
}
//@add}
//{@add - 将数据写入 Preferences DataStore - edit 函数需要使用 suspend 标记挂起
suspend fun updateShowCompleted(showCompleted: Boolean) {
dataStore.edit { preferences ->
preferences[PreferenceKeys.SHOW_COMPLETED] = showCompleted
}
}
//@add}
//{@modify - 更新为使用 dataStore.edit() 的 suspend 函数,
suspend fun enableSortByDeadline(enable: Boolean) {
// edit 保证以 事务方式 进行 数据更新. 确保同步更新顺序,不会有冲突
dataStore.edit{ preferences ->
val currentOrder = SortOrder.valueOf(
preferences[PreferenceKeys.SORT_ORDER] ?: SortOrder.NONE.name
)
val newSortOrder =
if (enable) {
if (currentOrder == SortOrder.BY_PRIORITY) {
SortOrder.BY_DEADLINE_AND_PRIORITY
} else {
SortOrder.BY_DEADLINE
}
} else {
if (currentOrder == SortOrder.BY_DEADLINE_AND_PRIORITY) {
SortOrder.BY_PRIORITY
} else {
SortOrder.NONE
}
}
preferences[PreferenceKeys.SORT_ORDER] = newSortOrder.name
}
}
//@modify}
//{@modify - 更新为使用 dataStore.edit() 的 suspend 函数。
suspend fun enableSortByPriority(enable: Boolean) {
dataStore.edit { preferences ->
val currentOrder = SortOrder.valueOf(
preferences[PreferenceKeys.SORT_ORDER] ?: SortOrder.NONE.name
)
val newSortOrder =
if (enable) {
if (currentOrder == SortOrder.BY_DEADLINE) {
SortOrder.BY_DEADLINE_AND_PRIORITY
} else {
SortOrder.BY_PRIORITY
}
} else {
if (currentOrder == SortOrder.BY_DEADLINE_AND_PRIORITY) {
SortOrder.BY_DEADLINE
} else {
SortOrder.NONE
}
}
preferences[PreferenceKeys.SORT_ORDER] = newSortOrder.name
}
}
//@modify}
//{@Remove - 直接从 Preferences 参数获取 currentOrder, 因此可以删除
/* private fun updateSortOrder(sortOrder: SortOrder) {
sharedPreferences.edit {
putString(SORT_ORDER_KEY, sortOrder.name)
}
}*/
//@Remove}
//{@remove -
/* companion object {
@Volatile
private var INSTANCE: UserPreferencesRepository? = null
fun getInstance(context: Context): UserPreferencesRepository {
return INSTANCE ?: synchronized(this) {
INSTANCE?.let {
return it
}
val instance = UserPreferencesRepository(context)
INSTANCE = instance
instance
}
}
}*/
//@remove}
}
分析:
(1) 代码中,有部分注释掉或者 @remove标记的,表示的是使用SharePreferences 方案时的代码.
(2) @add @modify 标记的部分, 表示的是 使用 DataStore 方案
(3) 可以看出, Preferences DataStore, 其实就是在 DataStore 里,组合了Preferences
并且 datastore 库做了兼容,非常方便使用.
二、 UI层
1. RecyclerView.ViewHolder
**
* Holder for a task item in the tasks list
*/
class TaskViewHolder(
private val binding: TaskViewItemBinding
) : RecyclerView.ViewHolder(binding.root) {
// Format date as: Apr 6, 2020
private val dateFormat = SimpleDateFormat("MMM d, yyyy", Locale.US)
/**
* Bind the task to the UI elements
*/
fun bind(todo: Task) {
binding.task.text = todo.name
setTaskPriority(todo)
binding.deadline.text = dateFormat.format(todo.deadline)
// if a task was completed, show it grayed out
val color = if (todo.completed) {
R.color.greyAlpha
} else {
R.color.white
}
itemView.setBackgroundColor(
ContextCompat.getColor(
itemView.context,
color
)
)
}
private fun setTaskPriority(todo: Task) {
binding.priority.text = itemView.context.resources.getString(
R.string.priority_value,
todo.priority.name
)
// set the priority color based on the task priority
val textColor = when (todo.priority) {
TaskPriority.HIGH -> R.color.red
TaskPriority.MEDIUM -> R.color.yellow
TaskPriority.LOW -> R.color.green
}
binding.priority.setTextColor(ContextCompat.getColor(itemView.context, textColor))
}
companion object {
fun create(parent: ViewGroup): TaskViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.task_view_item, parent, false)
val binding = TaskViewItemBinding.bind(view)
return TaskViewHolder(binding)
}
}
}
并非重点.
2. Adapter
class TasksAdapter : ListAdapter<Task, TaskViewHolder>(TASKS_COMPARATOR) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TaskViewHolder {
return TaskViewHolder.create(parent)
}
override fun onBindViewHolder(holder: TaskViewHolder, position: Int) {
val repoItem = getItem(position)
if (repoItem != null) {
holder.bind(repoItem)
}
}
companion object {
private val TASKS_COMPARATOR = object : DiffUtil.ItemCallback<Task>() {
override fun areItemsTheSame(oldItem: Task, newItem: Task): Boolean =
oldItem.name == newItem.name
override fun areContentsTheSame(oldItem: Task, newItem: Task): Boolean =
oldItem == newItem
}
}
}
并非重点.
3. ViewModel
data class TasksUiModel(
val tasks: List<Task>,
val showCompleted: Boolean,
val sortOrder: SortOrder
)
class TasksViewModel(
repository: TasksRepository,
private val userPreferencesRepository: UserPreferencesRepository
) : ViewModel() {
//{@Remove - 使用 DataStore 提供的 Flow<UserPreference>, 它存储了 show_completed 和 sort_order标志
// Keep the show completed filter as a stream of changes
/* private val showCompletedFlow = MutableStateFlow(false)
// Keep the sort order as a stream of changes
private val sortOrderFlow = userPreferencesRepository.sortOrderFlow*/
//@Remove}
//{@add 获取 userPreferencesFlow
private val userPreferencesFlow = userPreferencesRepository.userPreferencesFlow
//@add}
// Every time the sort order, the show completed filter or the list of tasks emit,
// we should recreate the list of tasks
private val tasksUiModelFlow = combine(
repository.tasks,
/* showCompletedFlow,
sortOrderFlow*/ //{@modify 使用 userPreferencesFlow代替
userPreferencesFlow
) { tasks: List<Task>, userPreferences: UserPreferences ->
return@combine TasksUiModel(
tasks = filterSortTasks(tasks,
userPreferences.showCompleted,
userPreferences.sortOrder),
showCompleted = userPreferences.showCompleted,
sortOrder = userPreferences.sortOrder
//@modify to use userPreferences}
)
}
val tasksUiModel = tasksUiModelFlow.asLiveData()
private fun filterSortTasks(
tasks: List<Task>,
showCompleted: Boolean,
sortOrder: SortOrder
): List<Task> {
// filter the tasks
val filteredTasks = if (showCompleted) {
tasks
} else {
tasks.filter { !it.completed }
}
// sort the tasks
return when (sortOrder) {
SortOrder.NONE -> filteredTasks
SortOrder.BY_DEADLINE -> filteredTasks.sortedByDescending { it.deadline }
SortOrder.BY_PRIORITY -> filteredTasks.sortedBy { it.priority }
SortOrder.BY_DEADLINE_AND_PRIORITY -> filteredTasks.sortedWith(
compareByDescending<Task> { it.deadline }.thenBy { it.priority }
)
}
}
fun showCompletedTasks(show: Boolean) {
//@{modify 使用新的函数 - 启用协程
// showCompletedFlow.value = show
viewModelScope.launch {
userPreferencesRepository.updateShowCompleted(show)
}
//@modify}
}
fun enableSortByDeadline(enable: Boolean) {
//@{modify 使用新的函数 - 启用协程
viewModelScope.launch {
userPreferencesRepository.enableSortByDeadline(enable)
}
}
fun enableSortByPriority(enable: Boolean) {
//@{modify 使用新的函数 - 启用协程
viewModelScope.launch {
userPreferencesRepository.enableSortByPriority(enable)
}
//@modify}
}
}
class TasksViewModelFactory(
private val repository: TasksRepository,
private val userPreferencesRepository: UserPreferencesRepository
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(TasksViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return TasksViewModel(repository, userPreferencesRepository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
分析:
(1) 根据架构指南,ViewModel 持有存储库的引用,获取数据 并 提供给 UI.
(2) 构造函数传递 TasksRepository、UserPreferencesRepository 对象,进而创建 TasksViewModel对象
(3) 封装数据成 TasksUiModel 对象,通过 combine 整理数据, 然后将flow 转换成 liveData 给UI监听使用.
(4) 通过协程实现更新 用户配置 (userPreferencesRepository.xxx)
4. MainActivity
private const val USER_PREFERENCES_NAME = "user_preferences"
//{@add - 创建 DataStore
// 委托,确保有一个DataStore 实例在应用中.
private val Context.dataStore by preferencesDataStore(
name = USER_PREFERENCES_NAME,
produceMigrations = { context -> // 从 SharedPreferences 迁移 到 DataStore, 根据name
listOf(SharedPreferencesMigration(context, USER_PREFERENCES_NAME))
}
)
//@add}
class TasksActivity : AppCompatActivity() {
private lateinit var binding: ActivityTasksBinding
private val adapter = TasksAdapter()
private lateinit var viewModel: TasksViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityTasksBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
viewModel = ViewModelProvider(
this,
TasksViewModelFactory(
TasksRepository,
UserPreferencesRepository(dataStore/*, this*/) //{@remove context
)
).get(TasksViewModel::class.java)
setupRecyclerView()
setupFilterListeners(viewModel)
setupSort()
viewModel.tasksUiModel.observe(this) { tasksUiModel ->
adapter.submitList(tasksUiModel.tasks)
updateSort(tasksUiModel.sortOrder)
binding.showCompletedSwitch.isChecked = tasksUiModel.showCompleted
}
}
private fun setupFilterListeners(viewModel: TasksViewModel) {
binding.showCompletedSwitch.setOnCheckedChangeListener { _, checked ->
viewModel.showCompletedTasks(checked)
}
}
private fun setupRecyclerView() {
// add dividers between RecyclerView's row items
val decoration = DividerItemDecoration(this, DividerItemDecoration.VERTICAL)
binding.list.addItemDecoration(decoration)
binding.list.adapter = adapter
}
private fun setupSort() {
binding.sortDeadline.setOnCheckedChangeListener { _, checked ->
viewModel.enableSortByDeadline(checked)
}
binding.sortPriority.setOnCheckedChangeListener { _, checked ->
viewModel.enableSortByPriority(checked)
}
}
private fun updateSort(sortOrder: SortOrder) {
binding.sortDeadline.isChecked =
sortOrder == SortOrder.BY_DEADLINE || sortOrder == SortOrder.BY_DEADLINE_AND_PRIORITY
binding.sortPriority.isChecked =
sortOrder == SortOrder.BY_PRIORITY || sortOrder == SortOrder.BY_DEADLINE_AND_PRIORITY
}
}
分析:
(1) 创建 DataStore 实例
(2) 通过datastore对象 和 存储库对象 创建ViewModel
(3) 通过监听 viewModel 提供的数据接口, 动态更新UI
附录:
官方样例讲解:https://developer.android.com/codelabs/android-preferences-datastore#0
示例代码:https://github.com/googlecodelabs/android-datastore/tree/preferences_datastore