18.5
18.5

Reputation: 31

How to Integrate Material Date Picker with Room Database in a Task App in android that uses the databinding?

I have a simple task app that uses RecyclerView with data binding and Room as the database. The app works fine, but now I want to add a Material Date Picker to allow users to select start and end dates for tasks. I'm having trouble figuring out how to integrate the Material Date Picker with my Room database. Here are the relevant parts of my setup

@Entity(tableName="mesimah_table")
data class Mesimah (
    @PrimaryKey(autoGenerate = true)
    var taskId: Long = 0L,

    @ColumnInfo(name="task_name")
    var taskName: String = "",

    @ColumnInfo(name="task_done")
    var taskDone: Boolean = false,

    @ColumnInfo(name="task_description")
    var taskDescription: String = "",

    @ColumnInfo(name="start_date")
    var startDate: Long,

    @ColumnInfo(name="end_date")
    var endDate: Long
)
@Dao
interface TaskDao {
    @Insert
    suspend fun insert(tasks: Mesimah)

    @Insert
    suspend fun insertAll(tasks: List<Mesimah>)

    @Delete
    suspend fun delete(task: Mesimah)

    @Update
    suspend fun update(task: Mesimah)

    @Query("Select * From mesimah_table WHERE taskId = :taskId")
    fun get(taskId: Long): LiveData<Mesimah>

    @Query("Select * FROM mesimah_table ORDER BY taskId DESC")
    fun getAll(): LiveData<List<Mesimah>>
}

class TasksViewModel(val dao: TaskDao) : ViewModel() {
    private val TAG = "TASKS_VIEW_MODEL"
    private val _navigateToMesimah = MutableLiveData<Long?>()
    val navigateToMesimah: LiveData<Long?>
        get() = _navigateToMesimah

    var newTaskName = ""
    var newTaskDescription = ""
    var start_date: Long = 0L
    var end_date: Long = 0L
    val tasks = dao.getAll()

    fun addTask() {
        viewModelScope.launch {
            val task = Mesimah(
                taskName = newTaskName,
                taskDescription = newTaskDescription,
                startDate = start_date,
                endDate = end_date
            )
            Log.i(TAG,"before add task")
            dao.insert(task)
            Log.i(TAG,"after add task")
        }
    }

    fun onTaskClicked(taskId: Long) {
            _navigateToMesimah.value = taskId
    }

    fun onTaskNavigated() {
        _navigateToMesimah.value = null
    }
}
class MesimahFragment : Fragment() {
    private val TAG: String = "MESIMAH FRAGMENT"
    private var _binding: FragmentMesimahBinding? = null
    private val binding get() = _binding!!

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

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {

        _binding = FragmentMesimahBinding.inflate(inflater, container,false)
        val view = binding.root

        val application = requireNotNull(this.activity).application
        val dao = MesimahDatabase.getInstance(application).taskDao

        val viewModelFactory = TasksViewModelFactory(dao)
        val viewModel = ViewModelProvider(
            this,viewModelFactory).get( TasksViewModel::class.java)

        binding.btnOpenDatePicker.setOnClickListener {
            showDateRangePicker()
        }

        binding.viewModel = viewModel
        binding.lifecycleOwner = viewLifecycleOwner

        Log.i(TAG, "Before adapter initialized")
        val adapter = MesimahItemAdapter { taskId ->
           viewModel.onTaskClicked(taskId)
        }

        binding.tasksListRecyclerView.adapter=adapter

        viewModel.tasks.observe(viewLifecycleOwner, Observer {
            it?.let {
                adapter.submitList(it)
            }
        })

        viewModel.navigateToMesimah.observe(viewLifecycleOwner, Observer { taskId ->
             taskId?.let {
                 val action = MesimahFragmentDirections
                     .actionMesimahFragmentToMesimahEditFragment(taskId)
                 this.findNavController().navigate(action)
                 viewModel.onTaskNavigated()
             }
        })

        return view
    }

    private fun showDateRangePicker() {
        val dateRangePicker = MaterialDatePicker.Builder.dateRangePicker()
            .setTitleText("Select Date Range")
            .build()

        dateRangePicker.show(parentFragmentManager, "date_range_picker")
        dateRangePicker.addOnPositiveButtonClickListener { dateRange ->
            val startDate = dateRange.first
            val endDate = dateRange.second

            val formatter = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
            val startDateString = formatter.format(Date(startDate))
            val endDateString = formatter.format(Date(endDate))
            val message = "Selected Date Range: $startDateString - $endDateString"
            Toast.makeText(requireContext(), message,Toast.LENGTH_SHORT).show()
        }
    }

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

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context=".MesimahFragment">

    <data>
        <variable
            name="viewModel"
            type="com.example.mesimah.viewmodel.TasksViewModel" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="text"
            android:layout_margin="8dp"
            android:id="@+id/task_name"
            android:hint="@string/task_prompt"
            android:text="@={viewModel.newTaskName}"/>

        <EditText
            android:id="@+id/task_description"
            android:layout_height="wrap_content"
            android:layout_width="match_parent"
            android:inputType="text|textMultiLine"
            android:maxLength="2000"
            android:layout_margin="8dp"
            android:maxLines="4"
            android:hint="task description"
            android:text="@={viewModel.newTaskDescription}"/>

        <com.google.android.material.button.MaterialButton
            android:id="@+id/btn_open_date_picker"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:text="@string/btn_select_date"
            app:cornerRadius="16dp"
            app:strokeColor="@color/black"
            app:strokeWidth="2dp"/>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/save_button"
            android:layout_gravity="center"
            android:text="@string/btn_save"
            android:onClick="@{() -> viewModel.addTask()}"/>

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/tasks_list_recycler_view"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:gravity="top"
            app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
            app:spanCount="2"/>

    </LinearLayout>
</layout>

Upvotes: 0

Views: 46

Answers (1)

18.5
18.5

Reputation: 31

I have found a solution for it by just using Two-way data binding and using converters to convert from one type to another from the ui to the view and vice versa. here is the link for more information the converter class

object DateConverters {
    private val dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.getDefault())

    @JvmStatic
    @InverseMethod("stringToDate")
    fun dateToString(dateMillis: Long) : String {
        return if (dateMillis != null) {
            dateFormat.format(Date(dateMillis))
        } else {
            ""
        }
    }

    @JvmStatic
    fun stringToDate(dateString: String): Long {
       return try {
           val date = dateFormat.parse(dateString)
           date?.time ?: 0L
       } catch (e: Exception) {
           0L
       }
    }
}

the layout

<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context=".MesimahEditFragment">

<data>
    <variable
        name="viewModel"
        type="com.example.mesimah.viewmodel.EditMesimahViewModel" />
    <import type="com.example.mesimah.model.DateConverters" />
</data>

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <EditText
        android:id="@+id/task_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="16dp"
        android:inputType="text"
        android:text="@={viewModel.task.taskName}" />

    <EditText
        android:id="@+id/task_description"
        android:layout_height="wrap_content"
        android:layout_width="match_parent"
        android:inputType="text|textMultiLine"
        android:maxLines="4"
        android:hint="@string/hint_desc"
        android:text="@={viewModel.task.taskDescription}"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="16dp">

        <com.google.android.material.textview.MaterialTextView
            android:id="@+id/start_date_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@={DateConverters.dateToString(viewModel.task.startDate)}"
            android:textSize="16sp"
            app:lineHeight="24sp" />

        <com.google.android.material.textview.MaterialTextView
            android:id="@+id/end_date_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@={DateConverters.dateToString(viewModel.task.endDate)}"
            android:textSize="16sp"
            app:lineHeight="24sp" />
    </LinearLayout>

    <com.google.android.material.button.MaterialButton
        android:id="@+id/btn_edit_date_picker"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        style="@style/Widget.MaterialComponents.Button.OutlinedButton"
        android:text="@string/btn_select_date"
        app:cornerRadius="16dp"
        app:strokeColor="#145015"
        app:strokeWidth="2dp"/>

    <CheckBox
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/task_done"
        android:textSize="16sp"
        android:checked="@={viewModel.task.taskDone}"/>

    <Button
        android:id="@+id/update_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="Update Task"
        android:onClick="@{() -> viewModel.updateTask()}" />

    <Button
        android:id="@+id/delete_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="Delete Task"
        android:onClick="@{() -> viewModel.deleteTask()}" />

</LinearLayout>
</layout>

Upvotes: 0

Related Questions