Reputation: 31
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
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