How to prevent adding empty items to Room Database in Android

Issue

I am working on a TodoList App and I set an Implementation to NOT allow users to add a Task if one or all of the required fields are empty. This Implementation was working perfectly before. However, when I recently encountered some bugs, it mysteriously stopped working. I’ve rechecked everything and even the commit when I made it work the first time and I didn’t see any difference but it still isn’t working. I humbly ask that you check to see if my implementation is right and suggest how to properly handle this feature. Here is my code…

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
)

AddFragment

import android.graphics.Color
import android.os.Bundle
import android.text.SpannableString
import android.text.TextPaint
import android.text.TextUtils
import android.text.method.LinkMovementMethod
import android.text.style.ClickableSpan  
import android.util.Log  
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast  
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import com.bignerdranch.android.to_dolist.R
import com.bignerdranch.android.to_dolist.data.TodoViewModel
import com.bignerdranch.android.to_dolist.databinding.FragmentAddBinding
import com.bignerdranch.android.to_dolist.fragments.dialogs.DatePickerFragment
import com.bignerdranch.android.to_dolist.fragments.dialogs.TimePickerFragment
import com.bignerdranch.android.to_dolist.model.Todo
import java.text.SimpleDateFormat
import java.util.*

private const val DIALOG_DATE = "DialogDate"
private const val DIALOG_TIME = "DialogTime"
const val SIMPLE_DATE_FORMAT = "MMM, d yyyy"
const val SIMPLE_TIME_FORMAT = "H:mm"

private const val TAG = "AddFragment"

class AddFragment : Fragment() {

    private lateinit var todoViewModel : TodoViewModel
    private var _binding : FragmentAddBinding? = null
    private val binding get() = _binding!!
    private lateinit var todo : Todo

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        todo = Todo()
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentAddBinding.inflate(inflater, container, false)

        todoViewModel = ViewModelProvider(this)[TodoViewModel::class.java]

        remindersTextSpan()

        // will insert our database when clicked
        binding.abCheckYes.setOnClickListener {
            insertTodoInDatabase()
        }

        // showing our datePickerDialog
        binding.edDate.setOnClickListener {

            childFragmentManager.setFragmentResultListener("requestKey", viewLifecycleOwner) 
{_, bundle ->
                val result = bundle.getSerializable("bundleKey") as Date
                // passing the result of the user selected date directly to the _Todo class instead
                todo.date = result
                // will ONLY update the date field when it is not empty so that we don't get a preloaded Date textView
                if(todo.date.toString().isNotEmpty()) {
                    updateDate()
                }
            }

            DatePickerFragment().show([email protected], DIALOG_DATE)
        }

        // showing our timePickerDialog
        binding.edTime.setOnClickListener {

            childFragmentManager.setFragmentResultListener("tRequestKey", 
viewLifecycleOwner) {_, bundle ->
                val result = bundle.getSerializable("tBundleKey") as Date
                // passing the result of the user selected time directly to the _Todo class instead
                todo.time = result
                // will ONLY update the time field when it is not empty so that we don't get a preloaded Time textView
                if (todo.time.toString().isNotEmpty()) {
                    updateTime()
                }

            }
           TimePickerFragment().show([email protected], DIALOG_TIME)
        }
        return binding.root
    }


    // function to update Date
    private fun updateDate() {
        val dateLocales = SimpleDateFormat(SIMPLE_DATE_FORMAT, Locale.getDefault())
        binding.edDate.text = dateLocales.format(todo.date)

    }

    // function to update Time
    private fun updateTime() {
        val timeLocales = SimpleDateFormat(SIMPLE_TIME_FORMAT, Locale.getDefault())
        binding.edTime.text = timeLocales.format(todo.time)
    }

    // our reminders Text span
    private fun remindersTextSpan() {
        val spannableString = SpannableString("Set Reminders")

        val clickableSpan = object : ClickableSpan() {
            override fun onClick(widget: View) {
                Toast.makeText(context, "Set Reminders!", Toast.LENGTH_LONG).show()
            }

            override fun updateDrawState(ds: TextPaint) {
                super.updateDrawState(ds)

                ds.color = Color.BLUE
            }
        }
        spannableString.setSpan(clickableSpan, 0, 13, 
SpannableString.SPAN_INCLUSIVE_EXCLUSIVE)

        binding.tvReminders.text = spannableString
        binding.tvReminders.movementMethod = LinkMovementMethod.getInstance()
    }


    // This function's job will be to insert Tasks in our database
    private fun insertTodoInDatabase() {
        val title = binding.edTaskTitle.text.toString()
        val date  = binding.edDate.text.toString()
        val time = binding.edTime.text.toString()

        if (inputCheck(title, date, time)) {
            val todo = Todo(0, title)
            todoViewModel.addTodo(todo)
            // This will make a toast saying Successfully added task if we add a task
            Toast.makeText(requireContext(), R.string.task_add_toast, 
Toast.LENGTH_LONG).show()

            // FIXME: There is a bug here that allows the user to add Todos even when some OR all of the fields are empty.
            // FIXME: It has to do with updateTime and updateDate functions I think, so I will try to fix it but maybe not now.

            findNavController().navigate(R.id.action_addFragment_to_listFragment)
        } else {
            Toast.makeText(requireContext(), R.string.no_task_add_toast, 
Toast.LENGTH_LONG).show()
        }
        Log.d(TAG, "Our todo widgets are $title $date and $time")
    }

    // This function will help us check if the texts are empty and then proceed to add them to the database
    // so that we do not add empty tasks to our database
    private fun inputCheck(title : String, date: String, time: String) : Boolean {

        // will return false if fields in TextUtils are empty and true if not
        return !(TextUtils.isEmpty(title) && TextUtils.isEmpty(date) && time.isEmpty())
    }

    override fun onDestroy() {
        super.onDestroy()
        _binding = null
    }
}

My DAO

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

    @Query("SELECT * FROM todo_table ORDER BY id ASC")
    fun readAllData() : LiveData<List<Todo>>

    @Query("DELETE FROM todo_table WHERE id IN (:idList)")
    suspend fun deleteSelectedTasks(idList : Long)


    @Query("DELETE FROM todo_table")
    suspend fun deleteAllTasks()
}

These are the emptyLists which weren’t supposed be added even if one is missing which is the title in this case.

Empty fields in todos

Then this my debugging test which proves that some or all the fields in any attempted Task is actually empty. The debug log messages that have spaces between "and" or only a date or a time proves that some or all the fields are empty but it still gets added. This was working perfectly before, I don’t know why it stopped.

The log message

Solution

I think so… An error occurred in the inputCheck method.

Change the and operator to the or operator.

before

    // This function will help us check if the texts are empty and then proceed to add them to the database
    // so that we do not add empty tasks to our database
    private fun inputCheck(title : String, date: String, time: String) : Boolean {

        // will return false if fields in TextUtils are empty and true if not
        return !(TextUtils.isEmpty(title) && TextUtils.isEmpty(date) && time.isEmpty())
    }

after

    // This function will help us check if the texts are empty and then proceed to add them to the database
    // so that we do not add empty tasks to our database
    private fun inputCheck(title : String, date: String, time: String) : Boolean {

        // will return false if fields in TextUtils are empty and true if not
        return !(TextUtils.isEmpty(title) || TextUtils.isEmpty(date) || time.isEmpty())
    }

add comment.

‘and’ operator is all values must be true.

‘is’ operator is true even if only one value is true.

for example, field state. (title is empty, date is not empty, time is not empty.)

Before method step.

  1. !(TextUtils.isEmpty(title) && TextUtils.isEmpty(date) && time.isEmpty())
  2. !(true && false && false)
  3. !(false)
  4. true

After method step.

  1. !(TextUtils.isEmpty(title) || TextUtils.isEmpty(date) || time.isEmpty())
  2. !(true || false || false)
  3. !(true)
  4. false

Answered By – Seungho Kang

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published