Android 架构组件 之 Data Store(5) - P

2022-11-08  本文已影响0人  行走中的3卡

这里通过代码,比较容易理解.
文章末尾有完整的代码链接.

一、 数据层

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

上一篇下一篇

猜你喜欢

热点阅读