如何隐藏或不显示基于布尔值的房间数据库中的项目



我有一个Todo应用程序,我想隐藏(这基本上意味着不显示)基于其完成状态的任务(在文本上划线)。然而,我所遵循的hideCompleted任务实现不工作,但排序和搜索正在工作,我说这是因为我把所有的实现在一个单一的查询,并使它们与stateFlow一起工作,但隐藏不工作。这是我的代码。好的,我说的不工作是指它取消选中任务旁边的复选框,而不是隐藏它们。

First My Model类

import androidx.room.Entity
import androidx.room.PrimaryKey
import java.util.*
/** Our Model class. This class will represent our database table **/

@Entity(tableName = "todo_table")
data class Todo(
@PrimaryKey (autoGenerate = true) // here "Room" will autoGenerate the id for us 
instead of assigning a randomUUID value
val id : Int = 0,
var title : String = "",
var date : Date = Date(),
var time : Date = Date(),
var todoCheckBox : Boolean = false
)

然后我的刀。只有两个sort(按日期和按名称)函数可以直接从Dao访问。其他的通过存储库。

import androidx.room.*
import com.bignerdranch.android.to_dolist.model.Todo
import kotlinx.coroutines.flow.Flow
/**
*  This will be our DAO file where we will be update, delete and add Todos to our 
database so it contains the methods used for accessing the database
*/
@Dao
interface TodoDao {
// function to hold all out queries and will be executed based on our sortOrder
fun getAllTasks(query : String, sortOrder: SortOrder, hideCompleted: Boolean) : Flow<List<Todo>> =
when(sortOrder) {
SortOrder.BY_DATE -> getTasksSortedByDateCreated(query, hideCompleted)
SortOrder.BY_NAME -> getTasksSortedByName(query, hideCompleted)
}

@Query("SELECT * FROM todo_table WHERE (todoCheckBox != :hideCompleted OR todoCheckBox = 0) AND title LIKE '%' || :searchQueryText || '%'  ORDER BY title COLLATE NOCASE")
fun getTasksSortedByName(searchQueryText : String, hideCompleted : Boolean): Flow<List<Todo>>
@Query("SELECT * FROM todo_table WHERE (todoCheckBox != :hideCompleted OR todoCheckBox = 0) AND title LIKE '%' || :searchQueryText || '%'  ORDER BY time ASC")
fun getTasksSortedByDateCreated(searchQueryText : String, hideCompleted : Boolean): Flow<List<Todo>>
// onConflict will ignore any known conflicts, in this case will remove duplicate "Todos" with the same name
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun addTodo(todo: Todo)
@Query("DELETE FROM todo_table WHERE id IN (:idList)")
suspend fun deleteSelectedTasks(idList : Long)
@Query("DELETE FROM todo_table")
suspend fun deleteAllTasks()
}

我的ViewModel(我直接从Dao调用排序函数)

import android.app.Application
import androidx.lifecycle.*
import com.bignerdranch.android.to_dolist.model.Todo
import com.bignerdranch.android.to_dolist.repository.TodoRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.launch
/** Our AndroidViewModel. This AndroidViewModel holds reference to our Application context. **/
class TodoViewModel(application: Application) : AndroidViewModel(application) {
/**
*  NOTE! : "Context" are needed to instantiate a database that is why we are using 
an AndroidViewModel in this case because it holds reference to an
*  Application context. And if I remember correctly, it will start as the "Application" starts.
**/
private val repository : TodoRepository
private val userDao = TodoDatabase.getDatabase(application).todoDao()
init {
// having access to our TodoDao from our database
val userDao = TodoDatabase.getDatabase(application).todoDao()
repository = TodoRepository(userDao)
}

val searchQuery = MutableStateFlow("")
val sortOrder = MutableStateFlow(SortOrder.BY_DATE) // adding BY_DATE to make the 
lists sorted by date as default
val hideCompleted = MutableStateFlow(false)

/**
*  The combine function here is a an object in the flow library that is used too 
combine the most recent values of a flow, so if one value changes it will
*  automatically return the latest values of the other flows. This is done so that the three flows will work in harmony.
*/
private val tasksFlow = combine(
searchQuery,
sortOrder,
hideCompleted
) { query, sortOrder, hideCompleted -> // LAMBDA
Triple(query, sortOrder, hideCompleted)
// flatMapLatest gets triggered when any of this flows changes and then passes it to the query to be executed.
}.flatMapLatest { (query, sortOrder, hideCompleted) ->
userDao.getAllTasks(query, sortOrder, hideCompleted)
}
val tasks = tasksFlow.asLiveData()

// All functions using coroutines objects indicates that whatever is in it should run in a background thread
fun addTodo(todo : Todo) {
viewModelScope.launch(Dispatchers.IO) {
repository.addTodo(todo)
}
}
fun deleteSelectedTasks(idList: Long) {
viewModelScope.launch(Dispatchers.IO) {
repository.delSelectedTasks(idList)
}
}
fun deleteAllTasks() {
viewModelScope.launch(Dispatchers.IO) {
repository.delAllTasks()
}
}
}
enum class SortOrder { BY_DATE, BY_NAME }

my Fragment

import android.annotation.SuppressLint
import android.app.AlertDialog
import android.os.Bundle
import android.util.Log
import android.view.*
import android.widget.Toast 
import androidx.appcompat.widget.SearchView
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bignerdranch.android.to_dolist.databinding.FragmentListBinding
import com.bignerdranch.android.to_dolist.R
import com.bignerdranch.android.to_dolist.data.SortOrder
import com.bignerdranch.android.to_dolist.data.TodoViewModel
import com.bignerdranch.android.to_dolist.model.Todo
import com.bignerdranch.android.to_dolist.utils.onQueryTextChanged
private const val TAG = "ListFragment"
class ListFragment : Fragment() {
private var _binding : FragmentListBinding? = null
private val binding get() = _binding!!
lateinit var mTodoViewModel: TodoViewModel
private lateinit var recyclerView: RecyclerView
private val adapter = ListAdapter()  // getting reference to our ListAdapter
private var todosList = emptyList<Todo>()

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout for this fragment with ViewBinding style
_binding = FragmentListBinding.inflate(inflater, container, false)
// this tells our activity/fragment that we have a menu_item it should respond to it.
setHasOptionsMenu(true)
recyclerView = binding.recyclerViewTodo
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(requireContext())

/**
*  updates our recyclerView with the new "observed" changes in our database through our adapter
*/
// TodoViewModel
mTodoViewModel = ViewModelProvider(this)[TodoViewModel::class.java]
mTodoViewModel.tasks.observe(viewLifecycleOwner) { todos ->
adapter.setData(todos)
todosList = todos
}
// Add Task Button
binding.fbAdd.setOnClickListener {
findNavController().navigate(R.id.action_listFragment_to_addFragment)
}
return binding.root
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.fragment_list, menu)
val search = menu.findItem(R.id.todo_search)
val searchView = search.actionView as SearchView
searchView.onQueryTextChanged { querySearch ->
mTodoViewModel.searchQuery.value = querySearch
}
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when(item.itemId)  {
R.id.sort_by_name -> {
mTodoViewModel.sortOrder.value = SortOrder.BY_NAME
true
}
R.id.sort_by_date -> {
mTodoViewModel.sortOrder.value = SortOrder.BY_DATE
true
}
R.id.todo_hide_completed -> {
item.isChecked = !item.isChecked
mTodoViewModel.hideCompleted.value = item.isChecked
true
}
R.id.del_selected_tasks -> {
deleteSelectedUsers()
true
}
R.id.del_all_tasks -> {
deleteAllTasks()
true
}
else -> super.onOptionsItemSelected(item)
}
}
// function to delete all of our Tasks
private fun deleteAllTasks() {
val builder = AlertDialog.Builder(requireContext())
builder.setPositiveButton("Yes") {_,_->
mTodoViewModel.deleteAllTasks()
Toast.makeText(requireContext(), "All tasks have been successfully deleted!", Toast.LENGTH_LONG).show()
}
builder.setNegativeButton("No") {_,_-> }
builder.setTitle("Confirm Deletion")
builder.setMessage("Are you sure you want to delete all Tasks?")
builder.create().show()
}
// function to delete only selected Tasks
@SuppressLint("NotifyDataSetChanged")
private fun deleteSelectedUsers() {
val builder = AlertDialog.Builder(requireContext())
// Our todos that have been marked completed by the checkBox
val finishedTodos = todosList.filter { it.todoCheckBox }
builder.setPositiveButton("Yes") {_,_->
finishedTodos.forEach { todos ->
mTodoViewModel.deleteSelectedTasks(todos.id.toLong())
}
Toast.makeText(requireContext(), "Selected tasks successfully deleted!", Toast.LENGTH_LONG).show()
}
builder.setNegativeButton("No") {_,_-> }
builder.setTitle("Confirm Deletion")
builder.setMessage("Are you sure you want to delete only selected Tasks?")
builder.create().show()
Log.i(TAG , "Our todos list size is ${finishedTodos.size}")
}  

// We want to leave no trace of our Binding class Reference to avoid memory leaks
override fun onDestroy() {
super.onDestroy()
_binding = null
}
}

我找到了一个解决方案。原来没有逻辑实际上改变todoCheckBox的布尔值(改为完成),这只是添加一个删除线。所以我采用了一种更好的方法来实现strike - through并重构了一些代码。这是我的代码。

我的适配器

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.ListAdapter
import com.bignerdranch.android.to_dolist.databinding.CustomRowBinding
import com.bignerdranch.android.to_dolist.fragments.add.SIMPLE_DATE_FORMAT
import com.bignerdranch.android.to_dolist.fragments.add.SIMPLE_TIME_FORMAT
import com.bignerdranch.android.to_dolist.model.Todo
import java.text.SimpleDateFormat
import java.util.*

class TodoAdapter(private val listener : OnItemClickListener): 
ListAdapter<Todo, TodoAdapter.TodoViewHolder>(DiffCallBack) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TodoViewHolder {
// this can be done in an inline variable and I may experiment on it later.
val binding = CustomRowBinding.inflate(LayoutInflater.from(parent.context),
parent,
false
)
return TodoViewHolder(binding)
}
override fun onBindViewHolder(holder: TodoViewHolder, position: Int) {
val currentItem = getItem(position)
holder.bind(currentItem)
}
inner class TodoViewHolder(private val binding : CustomRowBinding) : RecyclerView.ViewHolder(binding.root) {

/** Calling onClickListeners for each _Todo and the associated checkBox. **/
init {
binding.apply {
root.setOnClickListener {
val position = adapterPosition // this represents the position of any item in the root layout
// NO_POSITION means that an item is invalid and out of this list, so this is a safe check because-
// we don't want to call a listener on an invalid item
if (position != RecyclerView.NO_POSITION) {
val todo = getItem(position)
listener.onItemClick(todo)
}
}
cbTask.setOnClickListener {
val position = adapterPosition
if (position != RecyclerView.NO_POSITION) {
val todo = getItem(position)
listener.onCheckBoxClick(todo, cbTask.isChecked)
}
}
}
}
fun bind(todo : Todo) {
val dateLocales = SimpleDateFormat(SIMPLE_DATE_FORMAT, Locale.getDefault())
val timeLocales = SimpleDateFormat(SIMPLE_TIME_FORMAT, Locale.getDefault())
binding.apply {
tvTaskTitle.text = todo.title
tvTaskDate.text = dateLocales.format(todo.date)
tvTaskTime.text = timeLocales.format(todo.time)
cbTask.isChecked = todo.completed
tvTaskTitle.paint.isStrikeThruText = todo.completed
}
}
}
interface OnItemClickListener {
fun onItemClick(todo : Todo)
fun onCheckBoxClick(todo: Todo, isChecked: Boolean)
}
// This piece of code checks between our old and changed and lists and updates the recyclerView with the latest list.
// This also stops the recyclerView from redrawing itself after the position of an item has been changed. It even provides a nice animation.
object DiffCallBack : DiffUtil.ItemCallback<Todo>() {
override fun areItemsTheSame(oldItem: Todo, newItem: Todo) =
oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: Todo, newItem: Todo) =
oldItem == newItem
}
}

片段

import android.annotation.SuppressLint
import android.app.AlertDialog
import android.os.Bundle
import android.util.Log
import android.view.*
import android.widget.Toast
import androidx.appcompat.widget.SearchView
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bignerdranch.android.to_dolist.databinding.FragmentListBinding
import com.bignerdranch.android.to_dolist.R
import com.bignerdranch.android.to_dolist.viewmodel.SortOrder
import com.bignerdranch.android.to_dolist.viewmodel.TodoViewModel
import com.bignerdranch.android.to_dolist.model.Todo
import com.bignerdranch.android.to_dolist.utils.onQueryTextChanged
private const val TAG = "ListFragment"
class ListFragment : Fragment(), TodoAdapter.OnItemClickListener {
private var _binding : FragmentListBinding? = null
private val binding get() = _binding!!
private lateinit var mTodoViewModel: TodoViewModel
private lateinit var recyclerView: RecyclerView
private val adapter = TodoAdapter(this)
private var todosList = emptyList<Todo>()

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout for this fragment with ViewBinding style
_binding = FragmentListBinding.inflate(inflater, container, false)
// this tells our activity/fragment that we have a menu_item it should respond to it.
setHasOptionsMenu(true)
recyclerView = binding.recyclerViewTodo
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(requireContext())
recyclerView.setHasFixedSize(true)
/**
*  updates our recyclerView with the new "observed" changes in our database through our adapter
*/
// TodoViewModel
mTodoViewModel = ViewModelProvider(this)[TodoViewModel::class.java]
mTodoViewModel.tasks.observe(viewLifecycleOwner) { todos ->
adapter.submitList(todos)
todosList = todos
}
// Add Task Button
binding.fbAdd.setOnClickListener {

findNavController().navigate(R.id.action_listFragment_to_addFragment)
}
return binding.root
}
override fun onItemClick(todo: Todo) {
mTodoViewModel.onTaskSelected(todo)
}
override fun onCheckBoxClick(todo: Todo, isChecked: Boolean) {
mTodoViewModel.onTaskCheckedChanged(todo, isChecked)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.fragment_list, menu)
val search = menu.findItem(R.id.todo_search)
val searchView = search.actionView as SearchView
searchView.onQueryTextChanged { querySearch ->
mTodoViewModel.searchQuery.value = querySearch
}
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when(item.itemId)  {
R.id.sort_by_name -> {
mTodoViewModel.sortOrder.value = SortOrder.BY_NAME
true
}
R.id.sort_by_date -> {
mTodoViewModel.sortOrder.value = SortOrder.BY_DATE
true
}
R.id.action_hide_completed_tasks -> {
item.isChecked = !item.isChecked
mTodoViewModel.hideCompleted.value = item.isChecked
true
}
R.id.del_selected_tasks -> {
deleteSelectedUsers()
true
}
R.id.del_all_tasks -> {
deleteAllTasks()
true
}
else -> super.onOptionsItemSelected(item)
}
}
// function to delete all of our Tasks
private fun deleteAllTasks() {
val builder = AlertDialog.Builder(requireContext())
builder.setPositiveButton("Yes") {_,_->
mTodoViewModel.deleteAllTasks()
Toast.makeText(requireContext(), "All tasks have been successfully deleted!", Toast.LENGTH_LONG).show()
}
builder.setNegativeButton("No") {_,_-> }
builder.setTitle("Confirm Deletion")
builder.setMessage("Are you sure you want to delete all Tasks?")
builder.create().show()
}
// function to delete only selected Tasks
@SuppressLint("NotifyDataSetChanged")
private fun deleteSelectedUsers() {
val builder = AlertDialog.Builder(requireContext())
// Our todos that have been marked completed by the checkBox
val finishedTodos = todosList.filter { it.completed }
builder.setPositiveButton("Yes") {_,_->
finishedTodos.forEach { todos ->
mTodoViewModel.deleteSelectedTasks(todos.id.toLong())
}
Toast.makeText(requireContext(), "Selected tasks successfully deleted!", Toast.LENGTH_LONG).show()
}
builder.setNegativeButton("No") {_,_-> }
builder.setTitle("Confirm Deletion")
builder.setMessage("Are you sure you want to delete only selected Tasks?")
builder.create().show()
Log.i(TAG , "Our todos list size is ${finishedTodos.size}")
}

// We want to leave no trace of our Binding class Reference to avoid memory leaks
override fun onDestroy() {
super.onDestroy()
_binding = null
}
}

然后在ViewModel

中添加这两个函数
fun onTaskSelected(task : Todo) {
TODO()
}
fun onTaskCheckedChanged(todo : Todo, isChecked : Boolean) {
viewModelScope.launch {
repository.updateTask(todo.copy(completed = isChecked))
}
}