美文网首页
Android 架构组件 之 Data Store(5) - P

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

作者: 行走中的3卡 | 来源:发表于2022-11-08 10:20 被阅读0次

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

    一、 数据层

    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

    相关文章

      网友评论

          本文标题:Android 架构组件 之 Data Store(5) - P

          本文链接:https://www.haomeiwen.com/subject/rqaytdtx.html