android – @Delete annotation in Room Database not working for lists

I have a TodoList App and I am trying to delete a list Completed tasks based on a strikeThrough over the task. I have set several Implementations to single out the Task that has a strikeThrough in it which I’m honestly not sure if it works properly but I definitely know something is wrong with the Delete Query as well because it wasn’t also working. The second Implementation is attached to the main one but I commented it out.

This is 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
)

Then My Dao, Repository and ViewModel

 import androidx.lifecycle.LiveData
 import androidx.room.*
 import com.bignerdranch.android.to_dolist.model.Todo

 /**
  *  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 {

    // 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)

    fun readAllData() : LiveData<List<Todo>>

    @Query("DELETE FROM todo_table WHERE todoCheckBox = 1")
    suspend fun deleteSelectedTasks()

    // second method - This is the second method where I tried passing in an array
    //    @Delete
//    suspend fun deleteSelectedTasks(todoList : Array<Todo>)

    // Method in use
    @Query("DELETE FROM todo_table")
    suspend fun deleteAllTasks()
}

My Repository

 import androidx.lifecycle.LiveData
 import com.bignerdranch.android.to_dolist.data.TodoDao
 import com.bignerdranch.android.to_dolist.model.Todo

 /**
 * This repository class abstracts access to multiple data sources(dao etc.) It hides 
 the implementation behind data and provides access to the needed Information.
 */

 class TodoRepository(private val todoDao : TodoDao) {

    val readAllData : LiveData<List<Todo>> = todoDao.readAllData()

    suspend fun addTodo(todo : Todo) {
        todoDao.addTodo(todo)
    }

    suspend fun delSelectedTasks() {
        todoDao.deleteSelectedTasks()
    }

    suspend fun delAllTasks() {
    todoDao.deleteAllTasks()
    }
}

ViewModel

    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.
     **/

    val readAllData : LiveData<List<Todo>>
    private val repository : TodoRepository

    init {
        // having access to our TodoDao from our database
        val userDao = TodoDatabase.getDatabase(application).todoDao()
        repository = TodoRepository(userDao)
        readAllData = repository.readAllData
    }

    // 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() {
        viewModelScope.launch(Dispatchers.IO) {
            repository.delSelectedTasks()
        }
    }

    fun deleteAllTasks() {
        viewModelScope.launch(Dispatchers.IO) {
            repository.delAllTasks()
        }
    }
}

And then this is my ListFragment and ListAdapter. I have tried calling the deleteSelectedTasks through the ViewModel from both the Fragment and Adapter but none as worked.

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.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.TodoViewModel
import com.bignerdranch.android.to_dolist.model.Todo

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 lateinit var selectedTodos : List<Todo>

    // second method
//    var selectedTodos = arrayOf<Todo>()


    // TODO - WHEN I COME BACK, I WILL SEE IF I CAN DO THE IMPLEMENTATION HERE IN THE LIST 
FRAGMENT

    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.
        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.readAllData.observe(viewLifecycleOwner) { todos ->
            adapter.setData(todos)
            selectedTodos = 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)
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        return when(item.itemId)  {
            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())
//        val todo = emptyList<Todo>()
        val finishedTodos = selectedTodos.takeWhile {  it.todoCheckBox }
        builder.setPositiveButton("Yes") {_,_->
        mTodoViewModel.deleteSelectedTasks()
            adapter.notifyDataSetChanged()
        }
        builder.setNegativeButton("No") {_,_->}
        builder.setTitle("Confirm Deletion")
        builder.setMessage("Are you sure you want to delete only selected Tasks?")
        builder.create().show()
        Log.d(TAG, "Our todos $selectedTodos and $finishedTodos")
    }


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

ListAdapter

import android.annotation.SuppressLint
import android.graphics.Paint.STRIKE_THRU_TEXT_FLAG
import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.Adapter
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.*

private const val TAG = "ListAdapter"

class ListAdapter: Adapter<ListAdapter.TodoViewHolder>() {
    private var todoList = emptyList<Todo>()
    private var todo = Todo()


    // will toggle strikeThrough on the Task title
    private fun toggleStrikeThrough(tvTaskTitle : TextView, cbTask : Boolean) {
        if (cbTask) {
            tvTaskTitle.paintFlags = tvTaskTitle.paintFlags  or STRIKE_THRU_TEXT_FLAG
        } else {
            tvTaskTitle.paintFlags = tvTaskTitle.paintFlags and STRIKE_THRU_TEXT_FLAG.inv()
        }
    }


    inner class TodoViewHolder(val binding : CustomRowBinding) : 
RecyclerView.ViewHolder(binding.root)


    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 todo = todoList[position]
        val dateLocales = SimpleDateFormat(SIMPLE_DATE_FORMAT, Locale.getDefault())
        val timeLocales = SimpleDateFormat(SIMPLE_TIME_FORMAT, Locale.getDefault())
        holder.apply {
            binding.tvTaskTitle.text = todo.title
            binding.tvTaskDate.text = dateLocales.format(todo.date)
            binding.tvTaskTime.text = timeLocales.format(todo.time)
            binding.cbTask.isChecked = todo.todoCheckBox

            toggleStrikeThrough(binding.tvTaskTitle , todo.todoCheckBox)
            binding.cbTask.setOnCheckedChangeListener { _, isChecked ->
                toggleStrikeThrough(binding.tvTaskTitle, isChecked)
                todo.todoCheckBox = !todo.todoCheckBox

                taskCheck(todoList as MutableList<Todo>)
            }
        }
    }

    private fun taskCheck(todo : List<Todo>) {
        val listFragment = ListFragment()
        val finishedTodos = todo.takeWhile {  it.todoCheckBox }
        // second method
//        listFragment.selectedTodos = finishedTodos.toTypedArray()

        Log.i(TAG, "Our ${finishedTodos.size}")
    }


    // as usual will return the size of the List
    override fun getItemCount() = todoList.size

    @SuppressLint("NotifyDataSetChanged")
    fun setData(todo : List<Todo>) {
        this.todoList = todo
        notifyDataSetChanged()
    }

    @SuppressLint("NotifyDataSetChanged")
    fun deleteSelectedTasks() {
        val listFragment = ListFragment()
        listFragment.mTodoViewModel.deleteSelectedTasks()
        notifyDataSetChanged()
    }
}

Leave a Comment