Search code examples
javaandroidkotlinandroid-jetpack-composeandroid-room

Kotlin LocalDateTime handling for categorizing task based on today or past or future


Im writing a todolist app and i want to filter the tasks based on today,past or future,im currently persisting my data via Room.

My task model:

@Parcelize
@Entity(tableName = "tasks")
data class Task(
    @ColumnInfo(name = "task_title")
    val title:String,

    @ColumnInfo(name = "task_description")
    val description:String,

    @ColumnInfo(name = "task_date")
    val date:LocalDateTime?,


    @ColumnInfo(name = "task_category")
    val category: String,

    @ColumnInfo(name = "task_priority")
    val priority:Int,

    @IgnoredOnParcel
    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "task_id")
    val id:Int=0,

    @ColumnInfo(name = "task_isDone")
    val isDone:Boolean

):Parcelable

My Converter classes to persist the datefield into room

class DateTimeTypeConverters {
    @TypeConverter
    fun fromTimestamp(timestampSeconds: Long): LocalDateTime? {
        return LocalDateTime.ofEpochSecond(timestampSeconds, 0, ZoneOffset.UTC)
    }

    @TypeConverter
    fun dateToTimestamp(localDateTime: LocalDateTime?): Long {
        val instant = localDateTime?.toInstant(ZoneOffset.UTC)
        return instant?.toEpochMilli() ?: -1L
    }
}

my viewmodel:

@HiltViewModel
class TaskViewModel @Inject constructor(private val taskRepo: TaskRepository) :ViewModel() {
    val taskList: MutableLiveData<List<Task>> = taskRepo.allTasks
    val foundTask: MutableLiveData<Task> = taskRepo.foundTasks

    init {
        taskRepo.getAllTasks()
    }


     fun addTask(task:Task) {
        taskRepo.addTask(task)
         taskRepo.getAllTasks()
    }

    fun deleteTask(task:Task) {
        taskRepo.deleteTask(task)
        taskRepo.getAllTasks()
    }

    fun updateTask(task:Task) {
        taskRepo.updateTask(task)
        taskRepo.getAllTasks()
    }


    fun getTaskById(id:Int) {
        taskRepo.getTaskById(id)
    }

    fun getAllTasks() {
        taskRepo.getAllTasks()
    }

    fun deleteAllTasks() {
        taskRepo.deleteAllTasks()
        taskRepo.getAllTasks()
    }

}

my taskrepo:

class TaskRepository @Inject constructor(private val taskdao: TaskDao) {

    val allTasks = MutableLiveData<List<Task>>()
    val foundTasks = MutableLiveData<Task>()

    private val coroutineScope = CoroutineScope(Dispatchers.Main)

    fun addTask(task: Task){
        coroutineScope.launch(Dispatchers.IO){
            taskdao.addTask(task)
        }
    }

    fun deleteTask(task: Task){
        coroutineScope.launch(Dispatchers.IO){
            taskdao.deleteTask(task)
        }
    }


    fun getAllTasks() {
        coroutineScope.launch(Dispatchers.IO) {
            allTasks.postValue(taskdao.getAllTasks())
        }
    }


    fun getTaskById(id: Int) {
        coroutineScope.launch(Dispatchers.IO) {
            foundTasks.postValue(taskdao.getTaskById(id))
        }
    }


    fun updateTask(task: Task) {
        coroutineScope.launch(Dispatchers.IO){
            taskdao.updateTask(task)
        }
    }

    fun deleteAllTasks() {
        coroutineScope.launch(Dispatchers.IO) {
            taskdao.deleteAllTasks()
        }
    }

}

My lazy column where the data needs to be displayed the dismissable class is a dummy compose func to show a swipe to dismiss card:

LazyColumn(modifier = Modifier.padding(vertical = 20.dp, horizontal = 15.dp), state = lazyListState) {
    item {
        Text(text =tasksList.toString(), color = Color.White)
    }
   when(displayByTime.value){
       DisplayByTime.Today -> {
           val todayItems = tasksList.filter { task->
               val currentDate = LocalDate.now()
               task.date?.toLocalDate()==currentDate
           }
           Log.i(TAG, todayItems.toString())
           Log.i(TAG, LocalDate.now().toString())
           Log.i(TAG,
               LocalDate.now().isEqual(tasksList[0].date?.toLocalDate()).toString()
           )
           Log.i(TAG, LocalDate.now().isAfter(tasksList[0].date?.toLocalDate()).toString())
           Log.i(TAG, LocalDate.now().isBefore(tasksList[0].date?.toLocalDate()).toString())
          items(todayItems, key = {task-> task.id}){
              DismissableCard()
          }
       }
       DisplayByTime.Past->{
           val pastTasks = tasksList.filter { task ->
               val currentDate = LocalDate.now()
               task.date?.toLocalDate()?.isBefore(currentDate) ?: false
           }
           Log.i(TAG, pastTasks.toString())
           items(pastTasks, key = {task-> task.id}){
               DismissableCard()
           }
       }
       DisplayByTime.Future->{
           val futureTasks = tasksList.filter { task ->
               val currentDate = LocalDate.now()
               task.date?.toLocalDate()?.isAfter(currentDate) ?: false
           }
           Log.i(TAG, futureTasks.toString())
           items(futureTasks, key = {task-> task.id}){
               DismissableCard()
           }
       }
   }
}

Other enum classes for reference :

enum class DisplayByTime{
    Past,
    Today,
    Future
}

enum class TaskStatus{
    Completed,
    Pending,
    Missed,
    DoneEarly
}

Emulator result

thee data diplayed on my emulator screen(i.e the task i persisted into the room db):

Task(title=New Task,
description=new desc + 64,
date=+56411-10-10T02:57:28,
category=University,
priority=3,
id=82,
isDone=false)

The code i used to get the above task is to persist the dummy task generated via viewmodel using the following code inside the on click function of my FAB:

Task(
    title = "New Task",
    description = "new desc + ${Random.nextInt(100).toString()}",
    priority = 3,
    date = LocalDateTime.now(),
    category = "University",
    isDone = false
)

the task text on the device screen is just for sake of proof that there exist some data in the main tasklist

Logs showing that the filtered list is empty:

2024-06-12 00:17:11.732  6874-6874  ContentValues           com.example.todolist   I  []
2024-06-12 00:17:11.732  6874-6874  ContentValues           com.example.todolist   I  2024-06-12
2024-06-12 00:17:11.732  6874-6874  ContentValues           com.example.todolist   I  false
2024-06-12 00:17:11.732  6874-6874  ContentValues           com.example.todolist   I  false
2024-06-12 00:17:11.732  6874-6874  ContentValues           com.example.todolist   I  true

I tried all that I have shown above and expect some one to debug the issue im facing.

Im expecting an answer to change the filter function inside my lazy column according to the selected ENUM


Solution

  • I finally solved it,

    the problem is actually with my type converter classes(ill explain why but let me give you some context),

    My new type converter class, thanks to betulnecanli's medium article i referred

        class DateTimeTypeConverters {
            //    @TypeConverter [old type converter]
            //    fun fromTimestamp(timestampSeconds: Long): LocalDateTime? {
            //        return LocalDateTime.ofEpochSecond(timestampSeconds, 0, 
            ZoneOffset.UTC)
            //    }
            @TypeConverter
            fun fromTimestamp(value: String?): LocalDateTime? {
            return value?.let { LocalDateTime.parse(it) }
            }
        
            //    @TypeConverter [ old type converter]
            //    fun dateToTimestamp(localDateTime: LocalDateTime?): Long {
            //        val instant = localDateTime?.toInstant(ZoneOffset.UTC)
            //        return instant?.toEpochMilli() ?: -1L
            //    }
            @TypeConverter
            fun dateToTimestamp(date: LocalDateTime?): String? {
            return date?.toString()
            }
        }
    

    I inspected via logcat the following code:

    Log.i(TAG, tasksList[0].date?.toLocalDate().toString())//Task date from db
    Log.i(TAG, LocalDate.now().toString())// current db
    

    the log values are as follows:

    com.example.todolist I  +56411-10-102024-06-15 17:58:32.311  5905-5905            
    com.example.todolist I  2024-06-15
    

    the first log value is from my db which is not in proper format (see the second log value it should be in that format), so i changed my type converter to store it and parse it as string instead of long so i got the following result, which means it is working fine,

    emulator result

    In short, there in nothing wrong with my filter functions, its just problem with the converters and i successfully solved it.