Adarsh CSE225

You might also like

You are on page 1of 35

TaskMaster

Dissertation submitted in fulfilment of the requirements for the Degree of


BACHELOR OF TECHNOLOGY
in
COMPUTER SCIENCE AND ENGINEERING

By
Adarsh Shivam
12115538

CSE 225
ANDROID APP DEVELOPMENT

School of Computer Science and Engineering


Lovely Professional University
Phagwara, Punjab (India)
Month – April, Year - 2024
TABLE OF CONTENTS (16 Bold)

Sl.No CONTENTS PAGE NO.

1. Introduction 1

2. Modules or Activity Explanation & Screenshots 2


i) MainActivity.kt and its xml file

ii) AddFragments.kt and its xml file

iii) TaskFragments.kt and its xml file

iv) UpdateFragments.kt and its xml file

v) Database file

vi) Util file

3. Emulator Screenshots 31

4. Conclusion & Future Scope

5. Project Github link


INTRODUCTION
TaskMaster: Organize, Schedule, and Track Your Daily Tasks.

Features:

Task Management: Users can create, update, and delete tasks.

Scheduling: Schedule tasks on a priority basis.

Priority Levels: Assign priority levels to tasks (e.g., high, medium, low).

Custom Views: Implement custom views for displaying task details and
delete using swiping one by one or delete all at once.

Add or update the task based on priority.

Used three fragments for displaying three different interface for task,
add, update.

Used low system ui design with integrate the data on the main page based
on data provided.

Technical Details:

Architecture: Follow MVVM architecture for clean separation of


concerns.

Database: Utilize Room Persistence Library for local storage of tasks and
categories.

User Interface: Implement fragments with constraints layout for


displaying lists of tasks and categories.

Custom Views: Create custom views for displaying task details, swipe to
delete, priority indicators and delete all features.
Used interface to define every function in advance then override them in
their own file.

Additional Considerations:

UI/UX Design: Focus on creating a user-friendly interface with intuitive


navigation and visually appealing design. Simple and easy design. User
friendly.

Accessibility: Ensure that the app is accessible to users with disabilities


by following accessibility guidelines. Simple and friendly design. Easy to
use and manipulate the data and update or delete the list.

Modules or Activity and Code Explanation of it

I. MainActivity.kt
package com.android.kotlinmvvmtodolist.ui

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.findNavController
import androidx.navigation.ui.setupActionBarWithNavController
import com.android.kotlinmvvmtodolist.R
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {


super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

setupActionBarWithNavController(findNavController(R.id.nav_host_fragment))
}

override fun onSupportNavigateUp(): Boolean {


val navController = findNavController(R.id.nav_host_fragment)
return navController.navigateUp() || super.onSupportNavigateUp()
}
}
Explaination: The purpose of this MainActivity is to serve as the entry
point of the application, initialize the UI, and manage navigation using
the Navigation Component. It integrates with Hilt for dependency
injection.

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.MainActivity">

<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/my_nav"
tools:ignore="FragmentTagUsage" />
</androidx.constraintlayout.widget.ConstraintLayout>

Explaination: This main layout sets up a ConstraintLayout with a


single NavHostFragment as its child, which will manage the navigation
flow of the associated activity based on the navigation graph defined in
my_nav.xml.

II. AddFragment.kt
package com.android.kotlinmvvmtodolist.ui.add

import android.os.Bundle
import android.text.TextUtils
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.Toast
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import com.android.kotlinmvvmtodolist.R
import com.android.kotlinmvvmtodolist.data.local.TaskEntry
import com.android.kotlinmvvmtodolist.databinding.FragmentAddBinding
import com.android.kotlinmvvmtodolist.ui.task.TaskViewModel
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class AddFragment : Fragment() {

private val viewModel: TaskViewModel by viewModels()

private var _binding: FragmentAddBinding? = null


private val binding get() = _binding!!

override fun onCreateView(


inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {

_binding = FragmentAddBinding.inflate(inflater, container, false)

val myAdapter = ArrayAdapter(


requireContext(),
android.R.layout.simple_spinner_dropdown_item,
resources.getStringArray(R.array.priorities)
)

binding.apply {
spinner.adapter = myAdapter
btnAdd.setOnClickListener {
if(TextUtils.isEmpty((edtTask.text))){
Toast.makeText(requireContext(), "It's empty!",
Toast.LENGTH_SHORT).show()
return@setOnClickListener
}

val titleTitle = edtTask.text.toString()


val priority = spinner.selectedItemPosition

val taskEntry = TaskEntry(


0,
titleTitle,
priority,
System.currentTimeMillis()
)

viewModel.insert(taskEntry)
Toast.makeText(requireContext(), "Successfully added!",
Toast.LENGTH_SHORT).show()
findNavController().navigate(R.id.action_addFragment_to_taskFragment)
}
}
return binding.root
}

override fun onDestroyView() {


super.onDestroyView()
_binding = null
}

Explaination: This Kotlin code defines a Fragment (`AddFragment`)


responsible for enabling users to add tasks to a taskmaster application.
Utilizing Hilt for dependency injection, the Fragment initializes a
ViewModel (`TaskViewModel`) to handle task data operations. Within the
`onCreateView()` method, the Fragment inflates its layout using view
binding, sets up a spinner adapter for task priorities, and configures a
click listener for the "Add" button. Upon button click, the Fragment
checks if the task title is empty and, if not, creates a `TaskEntry` object
with the provided title, selected priority, and current timestamp. It then
inserts this task entry into the database using the ViewModel, displays a
success toast message, and navigates back to the task list fragment.
Through these mechanisms, the code seamlessly integrates UI
functionality with database operations, facilitating a smooth user
experience within the taskmaster app.

fragment_add.xml

<?xml version="1.0" encoding="utf-8"?>


<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">

<data>

</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.add.AddFragment">

<EditText
android:id="@+id/edt_task"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:ems="10"
android:hint="@string/enter_task"
android:inputType="textPersonName"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="Autofill" />

<Spinner
android:id="@+id/spinner"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:entries="@array/priorities"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/edt_task" />

<Button
android:id="@+id/btn_add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/add"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/spinner" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Explaination: This XML layout file utilizes the Android Data Binding
Library, indicated by the `<layout>` root element. It defines the UI
elements for the AddFragment, allowing users to input a task title, select
its priority from a spinner, and add the task using a button. Within the
`<data>` element, data variables could be declared for binding data to
the layout, although none are present in this instance. The
`<androidx.constraintlayout.widget.ConstraintLayout>` serves as the
root layout, ensuring flexible positioning of UI elements. It contains an
EditText for entering the task title (`edt_task`), a Spinner for selecting the
task priority (`spinner`), and a Button (`btn_add`) to initiate the task
addition process. Constraints are applied to each element within the
ConstraintLayout to specify their positioning relative to the parent and
other views. Additionally, string resources are utilized for the EditText
hint and Button text, promoting localization and maintainability. Overall,
this layout facilitates a user-friendly interface for adding tasks to the to-
do list application.

III. TaskFragment.kt

@file:Suppress("DEPRECATION")

package com.android.kotlinmvvmtodolist.ui.task

import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.os.Bundle
import android.view.*
import android.view.inputmethod.InputMethodManager
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.SearchView
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.android.kotlinmvvmtodolist.R
import com.android.kotlinmvvmtodolist.databinding.FragmentTaskBinding
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch

@AndroidEntryPoint
class TaskFragment : Fragment() {

private val viewModel: TaskViewModel by viewModels()


private lateinit var mAdapter: TaskAdapter
private var _binding: FragmentTaskBinding? = null
private val binding get() = _binding!!

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

_binding = FragmentTaskBinding.inflate(inflater, container, false)

binding.lifecycleOwner = this
binding.viewModel = viewModel

mAdapter = TaskAdapter(TaskClickListener { taskEntry ->

findNavController().navigate(TaskFragmentDirections.actionTaskFragmentToUpdate
Fragment(taskEntry))
})

lifecycleScope.launch{
repeatOnLifecycle(Lifecycle.State.STARTED){
viewModel.getAllTasks.collect{ tasks ->
mAdapter.submitList(tasks)
}
}
}

binding.apply {
recyclerView.adapter = mAdapter
floatingActionButton.setOnClickListener {
findNavController().navigate(R.id.action_taskFragment_to_addFragment)
}
}

ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(0,
ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT){
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return false
}

override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction:


Int) {
val position = viewHolder.adapterPosition
val taskEntry = mAdapter.currentList[position]
viewModel.delete(taskEntry)
Snackbar.make(binding.root, "Deleted!", Snackbar.LENGTH_LONG).apply
{
setAction("Undo"){
viewModel.insert(taskEntry)
}
show()
}
}
}).attachToRecyclerView(binding.recyclerView)

setHasOptionsMenu(true)

hideKeyboard(requireActivity())

return binding.root
}

private fun hideKeyboard(activity: Activity) {


val inputMethodManager =
activity.getSystemService(Context.INPUT_METHOD_SERVICE) as
InputMethodManager
val currentFocusedView = activity.currentFocus
currentFocusedView.let {
inputMethodManager.hideSoftInputFromWindow(
currentFocusedView?.windowToken,
InputMethodManager.HIDE_NOT_ALWAYS
)
}
}

@Deprecated("Deprecated in Java")
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.task_menu, menu)
val searchItem = menu.findItem(R.id.action_search)
val searchView = searchItem.actionView as SearchView
searchView.setOnQueryTextListener(object :
SearchView.OnQueryTextListener{
override fun onQueryTextSubmit(query: String?): Boolean {
return true
}

override fun onQueryTextChange(newText: String?): Boolean {


if(newText != null){
runQuery(newText)
}
return true
}
})
}

fun runQuery(query: String){


val searchQuery = "%$query%"
viewModel.searchDatabase(searchQuery).observe(viewLifecycleOwner) { tasks -
>
mAdapter.submitList(tasks)
}
}

@Deprecated("Deprecated in Java")
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when(item.itemId){
R.id.action_priority -> {
lifecycleScope.launch{
repeatOnLifecycle(Lifecycle.State.STARTED){
viewModel.getAllPriorityTasks.collectLatest { tasks ->
mAdapter.submitList(tasks)
}
}
}
}
R.id.action_delete_all -> deleteAllItem()
}
return super.onOptionsItemSelected(item)
}

private fun deleteAllItem() {


AlertDialog.Builder(requireContext())
.setTitle("Delete All")
.setMessage("Are you sure?")
.setPositiveButton("Yes"){dialog, _ ->
viewModel.deleteAll()
dialog.dismiss()
}.setNegativeButton("No"){dialog, _ ->
dialog.dismiss()
}.create().show()
}

override fun onDestroyView() {


super.onDestroyView()
_binding = null
}
}

Explaination: This Kotlin file, `TaskFragment.kt`, encapsulates the


implementation of a RecyclerView adapter (`TaskAdapter`) tailored for
presenting a list of tasks within the TaskFragment of a task master
application. The adapter employs the ListAdapter class to efficiently
handle list updates through DiffUtil, ensuring smooth updates while
minimizing unnecessary layout operations. Each task item is represented
by a ViewHolder, responsible for binding task data to the corresponding
layout using data binding. Additionally, a TaskClickListener interface is
defined to facilitate item click handling, allowing for seamless interaction
with individual tasks within the RecyclerView. Overall, this adapter
streamlines the task display process within the TaskFragment, enhancing
user experience by enabling intuitive task management.

TaskAdapter.kt

package com.android.kotlinmvvmtodolist.ui.task

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.android.kotlinmvvmtodolist.data.local.TaskEntry
import com.android.kotlinmvvmtodolist.databinding.RowLayoutBinding

class TaskAdapter(private val clickListener: TaskClickListener):


ListAdapter<TaskEntry, TaskAdapter.ViewHolder>(TaskDiffCallback) {

companion object TaskDiffCallback : DiffUtil.ItemCallback<TaskEntry>(){


override fun areItemsTheSame(oldItem: TaskEntry, newItem: TaskEntry) =
oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: TaskEntry, newItem: TaskEntry) =
oldItem == newItem
}

class ViewHolder(private val binding: RowLayoutBinding):


RecyclerView.ViewHolder(binding.root) {
fun bind(taskEntry: TaskEntry, clickListener: TaskClickListener){
binding.taskEntry = taskEntry
binding.clickListener = clickListener
binding.executePendingBindings()
}
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):


ViewHolder {
return
ViewHolder(RowLayoutBinding.inflate(LayoutInflater.from(parent.context), parent,
false))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val current = getItem(position)
if(current != null){
holder.bind(current, clickListener)
}
}
}

class TaskClickListener(val clickListener: (taskEntry: TaskEntry) -> Unit){


fun onClick(taskEntry: TaskEntry) = clickListener(taskEntry)
}

Explaination: This Kotlin file, `TaskAdapter.kt`, defines a RecyclerView


adapter (`TaskAdapter`) responsible for managing the display of tasks
within a to-do list application's UI. The adapter extends
`ListAdapter<TaskEntry, TaskAdapter.ViewHolder>`, leveraging the
ListAdapter class to efficiently handle updates to the task list using
DiffUtil. The companion object `TaskDiffCallback` implements
DiffUtil.ItemCallback to determine whether items in the list are the same
or have the same content, ensuring optimized updates. The adapter's
ViewHolder inner class binds task data to the corresponding layout using
data binding, facilitating seamless integration of task information with
the UI. Additionally, a TaskClickListener interface is defined to handle
item click events, enabling interaction with individual tasks within the
RecyclerView. Overall, this adapter enhances the user experience by
streamlining task management within the taskmaster application's UI.

TaskViewModel.kt

package com.android.kotlinmvvmtodolist.ui.task

import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.android.kotlinmvvmtodolist.data.TaskRepository
import com.android.kotlinmvvmtodolist.data.local.TaskEntry
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class TaskViewModel @Inject constructor(
private val repository : TaskRepository
) : ViewModel(){

val getAllTasks = repository.getAllTasks()


val getAllPriorityTasks = repository.getAllPriorityTasks()

fun insert(taskEntry: TaskEntry) = viewModelScope.launch {


repository.insert(taskEntry)
}

fun delete(taskEntry: TaskEntry) = viewModelScope.launch{


repository.deleteItem(taskEntry)
}

fun update(taskEntry: TaskEntry) = viewModelScope.launch{


repository.updateData(taskEntry)
}

fun deleteAll() = viewModelScope.launch{


repository.deleteAll()
}

fun searchDatabase(searchQuery: String): LiveData<List<TaskEntry>> {


return repository.searchDatabase(searchQuery)
}

Explaination: In TaskViewModel.kt, a ViewModel is implemented to


manage task-related data and operations within a to-do list application.
Injected with a TaskRepository dependency via constructor injection, it
provides LiveData streams (getAllTasks and getAllPriorityTasks) for
observing all tasks and priority tasks respectively. Utilizing coroutines
and the viewModelScope, it defines methods for inserting, deleting,
updating, and deleting all tasks from the database. Additionally, a
method searchDatabase() facilitates searching tasks based on a query,
returning LiveData containing the matching task list. Overall, this
ViewModel orchestrates interactions with task data, exposing LiveData
for observing changes and offering methods for performing CRUD
operations and searching tasks within the database, enhancing the
application's functionality and maintainability.

How TaskAdapter, TaskFragment and TaskViewModel are


connected and defining the page ui with recycler view?

In the context of a typical MVVM (Model-View-ViewModel)


architecture:
1. TaskAdapter: It's responsible for binding the task data to the
RecyclerView in the UI (View). It receives data from the ViewModel and
displays it in the UI. When an item in the RecyclerView is clicked, it
triggers the associated click listener, which communicates with the
ViewModel to perform actions such as updating or deleting tasks.

2. TaskFragment: This is the UI component (View) that hosts the


RecyclerView. It interacts with the ViewModel to observe changes in the
task data and update the UI accordingly. It typically observes LiveData
objects exposed by the ViewModel to stay synchronized with the
underlying data.

3. TaskViewModel: This is the ViewModel layer that serves as an


intermediary between the UI (View) and the data layer (Model). It
exposes LiveData objects representing the task data to the UI, which the
TaskFragment observes. The TaskViewModel also contains methods for
performing actions on the task data, such as inserting, updating, or
deleting tasks. When the TaskFragment interacts with the ViewModel
(e.g., through user actions), the ViewModel updates the underlying data
(via the TaskRepository) and notifies the UI of any changes.

Overall, TaskAdapter, TaskFragment, and TaskViewModel are connected


through the LiveData objects exposed by the ViewModel. The
ViewModel holds the task data and exposes it to the UI components
(TaskFragment) via LiveData. TaskFragment observes these LiveData
objects and updates the UI accordingly using TaskAdapter. When user
actions occur in the UI, TaskFragment communicates with
TaskViewModel to perform the necessary actions on the task data,
ensuring separation of concerns and promoting a clear and maintainable
architecture.

fragment_task.xml

<?xml version="1.0" encoding="utf-8"?>


<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">

<data>
<variable
name="viewModel"
type="com.android.kotlinmvvmtodolist.ui.task.TaskViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.task.TaskFragment">

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="0dp"
android:layout_height="0dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:clipToPadding="false"
android:padding="4dp"
tools:listitem="@layout/row_layout"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/floatingActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="24dp"
android:layout_marginBottom="24dp"
android:clickable="true"
android:src="@drawable/ic_add"
app:tint="@android:color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:ignore="ContentDescription,KeyboardInaccessibleWidget" />

</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Explaination: This XML layout file follows the Android Data Binding
pattern and is associated with the TaskFragment. Within the layout tag, a
data element is defined, declaring a variable named "viewModel" of type
TaskViewModel, enabling data binding with the TaskViewModel class.
The root layout is a ConstraintLayout, facilitating flexible arrangement
of UI elements. Inside, a RecyclerView (`recycler_view`) is configured to
display a list of tasks, with LinearLayoutManager for vertical scrolling.
The RecyclerView's item layout is specified as `row_layout`. A
FloatingActionButton (`floatingActionButton`) is included for adding new
tasks, positioned at the bottom end of the layout. It's adorned with an add
icon and tinted in white for visual appeal. Overall, this layout promotes
seamless integration with the TaskViewModel, enabling dynamic data
binding and facilitating intuitive task management within the
TaskFragment UI.

IV. UpdateFragment.kt

package com.android.kotlinmvvmtodolist.ui.update

import android.os.Bundle
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import com.android.kotlinmvvmtodolist.R
import com.android.kotlinmvvmtodolist.data.local.TaskEntry
import com.android.kotlinmvvmtodolist.databinding.FragmentUpdateBinding
import com.android.kotlinmvvmtodolist.ui.task.TaskViewModel
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class UpdateFragment : Fragment() {

private val viewModel: TaskViewModel by viewModels()


private var _binding: FragmentUpdateBinding? = null
private val binding get() = _binding!!
private val args by navArgs<UpdateFragmentArgs>()

override fun onCreateView(


inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {

_binding = FragmentUpdateBinding.inflate(inflater, container, false)

binding.apply {
updateEdtTask.setText(args.task.title)
updateSpinner.setSelection(args.task.priority)

btnUpdate.setOnClickListener {
if(TextUtils.isEmpty(updateEdtTask.text)){
Toast.makeText(requireContext(), "It's empty!",
Toast.LENGTH_SHORT).show()
return@setOnClickListener
}

val taskTitle = updateEdtTask.text.toString()


val priority = updateSpinner.selectedItemPosition

val taskEntry = TaskEntry(


args.task.id,
taskTitle,
priority,
args.task.timestamp
)

viewModel.update(taskEntry)
Toast.makeText(requireContext(), "Updated!",
Toast.LENGTH_SHORT).show()

findNavController().navigate(R.id.action_updateFragment_to_taskFragment)
}
}
return binding.root
}

override fun onDestroyView() {


super.onDestroyView()
_binding = null
}

Explaination: In the `UpdateFragment` Kotlin file, a Fragment is


implemented to facilitate updating tasks within a to-do list application.
Utilizing Dagger Hilt for dependency injection, the Fragment initializes a
TaskViewModel through viewModels(), enabling seamless interaction
with task data. It inflates a layout using data binding, providing UI
elements for users to input updated task information. The Fragment
retrieves navigation arguments using navArgs(), allowing access to the
task data being updated. Upon user interaction with the "Update" button,
the Fragment validates the input, ensuring the task title is not empty, and
constructs a new TaskEntry object with the updated task details. This
object is then passed to the ViewModel's update() method to persist the
changes in the database. Feedback is provided to the user via Toast
messages, indicating the success of the update operation. Finally,
navigation is performed back to the task list fragment, enhancing user
experience by seamlessly integrating task updating functionality within
the application's UI.
fragment_update.xml

<?xml version="1.0" encoding="utf-8"?>


<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">

<data>

</data>

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.update.UpdateFragment">

<EditText
android:id="@+id/update_edt_task"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:autofillHints=""
android:ems="10"
android:hint="@string/enter_task"
android:inputType="textPersonName"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<Spinner
android:id="@+id/update_spinner"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:entries="@array/priorities"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/update_edt_task" />

<Button
android:id="@+id/btn_update"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/update"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/update_spinner" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Explaination: In this XML layout file associated with the


UpdateFragment, a ConstraintLayout serves as the root layout, providing
a flexible container for arranging UI elements. Inside the layout, an
EditText (`update_edt_task`) is included for users to input the updated
task title, with constraints applied to align it horizontally centered and
vertically top-aligned. Below the EditText, a Spinner (`update_spinner`)
allows users to select the updated task priority from a predefined list,
positioned beneath the EditText with a margin applied for separation.
Lastly, a Button (`btn_update`) is provided for users to initiate the update
operation, positioned below the Spinner with constraints ensuring
horizontal centering and a margin for vertical spacing. Overall, this
layout facilitates an intuitive user interface for updating task details
within the UpdateFragment, promoting a seamless user experience in
managing tasks within the taskmaster application.

row_layout.xml

<?xml version="1.0" encoding="utf-8"?>


<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">

<data>
<variable
name="taskEntry"
type="com.android.kotlinmvvmtodolist.data.local.TaskEntry" />

<variable
name="clickListener"
type="com.android.kotlinmvvmtodolist.ui.task.TaskClickListener" />
</data>

<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
app:cardCornerRadius="8dp"
app:cardElevation="4dp"
android:onClick="@{() -> clickListener.onClick(taskEntry)}">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/row_background"
android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView
android:id="@+id/taskTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Title"
android:text="@{taskEntry.title}"/>

<TextView
android:id="@+id/taskPriority"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/taskTitle"
tools:text="High"
app:setPriority="@{taskEntry.priority}"/>

<TextView
android:id="@+id/taskTimestamp"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/taskPriority"
app:layout_constraintVertical_bias="0.0"
tools:text="timestamp"
app:setTimestamp="@{taskEntry.timestamp}"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</layout>

Explaination: In this XML layout file, a CardView encapsulates a task


item's visual representation within a RecyclerView. Utilizing data
binding, two variables are declared: "taskEntry" of type TaskEntry and
"clickListener" of type TaskClickListener. The CardView is configured
with rounded corners and elevation, enhancing its visual appeal. Inside,
a ConstraintLayout defines the layout for displaying task details,
including the task title (`taskTitle`), priority (`taskPriority`), and
timestamp (`taskTimestamp`). Constraints are applied to position these
TextViews within the layout, ensuring proper alignment and spacing. The
task details are bound to the TextViews using data binding expressions,
enabling dynamic updates based on the associated TaskEntry object.
Additionally, an `onClick` attribute is set for the CardView, invoking the
clickListener's onClick method when the task item is clicked, facilitating
interaction with individual tasks within the RecyclerView. Overall, this
layout file promotes a visually appealing and interactive user experience
for viewing and interacting with tasks in the taskmaster application.

V. Database Storing and Use Query on it for insertion, deletion,


updation and sorting based on priority:

TaskEntry.kt

package com.android.kotlinmvvmtodolist.data.local

import android.os.Parcelable
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.android.kotlinmvvmtodolist.util.Constants.TASK_TABLE
import kotlinx.parcelize.Parcelize

@Parcelize
@Entity(tableName = TASK_TABLE)
data class TaskEntry(
@PrimaryKey(autoGenerate = true)
var id: Int,
var title: String,
var priority: Int,
var timestamp: Long
):Parcelable

Explaination: In this `TaskEntry` data class within the `data.local`


package, a model representing a single task entry in the local database is
defined. Annotated with `@Entity`, it specifies that instances of this class
will be stored as entities in the database, with the table name specified as
`TASK_TABLE`. The class implements the `Parcelable` interface using
`@Parcelize`, allowing instances to be serialized and passed between
components within the Android application. Properties such as `id`,
`title`, `priority`, and `timestamp` represent the attributes of a task entry,
with `id` annotated as the primary key and set to auto-generate. This
enables automatic assignment of unique identifiers to each task entry
added to the database. Overall, this data class serves as a blueprint for
representing and manipulating task entries within the local database of
the application.

TaskDao.kt

package com.android.kotlinmvvmtodolist.data.local

import androidx.lifecycle.LiveData
import androidx.room.*
import kotlinx.coroutines.flow.Flow

@Dao
interface TaskDao {

@Insert
suspend fun insert(taskEntry: TaskEntry)

@Delete
suspend fun delete(taskEntry: TaskEntry)

@Update
suspend fun update(taskEntry: TaskEntry)

@Query("DELETE FROM task_table")


suspend fun deleteAll()

@Query("SELECT * FROM task_table ORDER BY timestamp DESC")


fun getAllTasks(): Flow<List<TaskEntry>>

@Query("SELECT * FROM task_table ORDER BY priority ASC")


fun getAllPriorityTasks(): Flow<List<TaskEntry>>

@Query("SELECT * FROM task_table WHERE title LIKE :searchQuery ORDER


BY timestamp DESC")
fun searchDatabase(searchQuery: String): LiveData<List<TaskEntry>>
}
Explaination: In the `TaskDao` interface within the `data.local`
package, various methods are defined to perform database operations
related to task entries. Annotated with `@Dao`, it serves as the Data
Access Object for accessing and manipulating data stored in the local
database.

The interface includes the following methods:

1. `insert(taskEntry: TaskEntry)`: Inserts a new task entry into the


database.

2. `delete(taskEntry: TaskEntry)`: Deletes a specified task entry from the


database.

3. `update(taskEntry: TaskEntry)`: Updates an existing task entry in the


database.

4. `deleteAll()`: Deletes all task entries from the database.

5. `getAllTasks()`: Retrieves all task entries from the database, ordered


by timestamp in descending order. Returns a Flow of lists of TaskEntry
objects, allowing for observation of changes in the data.

6. `getAllPriorityTasks()`: Retrieves all task entries from the database,


ordered by priority in ascending order. Returns a Flow of lists of
TaskEntry objects.

7. `searchDatabase(searchQuery: String)`: Searches the database for


task entries whose titles match the specified search query, ordered by
timestamp in descending order. Returns a LiveData object containing a
list of TaskEntry objects, allowing for real-time updates when the
underlying data changes.

These methods provide the necessary functionality to interact with the


local database and perform CRUD (Create, Read, Update, Delete)
operations on task entries within the application. The use of LiveData
and Flow ensures that data updates are observed and reflected in the UI
components accordingly.

TaskDatabase.kt

package com.android.kotlinmvvmtodolist.data.local
import androidx.room.Database
import androidx.room.RoomDatabase

@Database(entities = [TaskEntry::class], version = 1, exportSchema = false)


abstract class TaskDatabase: RoomDatabase() {
abstract fun taskDao(): TaskDao
}

Explaination: In the `TaskDatabase` class within the `data.local`


package, an abstract class is defined to represent the Room database for
storing task entries. Annotated with `@Database`, it specifies the entities
it contains (in this case, `TaskEntry`), the database version, and whether
to export the schema. This class extends `RoomDatabase`, which is the
main access point for the underlying SQLite database. Inside the class,
an abstract method `taskDao()` is declared, which returns an instance of
the `TaskDao` interface. This method serves as the entry point for
accessing and interacting with the DAO (Data Access Object) methods
defined in `TaskDao`. Overall, `TaskDatabase` acts as a central hub for
managing and accessing the local database, providing a way to
instantiate the DAO and perform database operations within the
application.

AppModule.kt

package com.android.kotlinmvvmtodolist.di

import android.content.Context
import androidx.room.Room
import com.android.kotlinmvvmtodolist.data.local.TaskDatabase
import com.android.kotlinmvvmtodolist.util.Constants.TASK_DATABASE
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object AppModule {

@Singleton
@Provides
fun provideTaskDao(@ApplicationContext context: Context) =
Room.databaseBuilder(
context,
TaskDatabase::class.java,
TASK_DATABASE
).build().taskDao()
}

Explaination: In the `AppModule` class within the `di` package,


Dagger's dependency injection functionality is utilized to provide an
instance of the `TaskDao` interface to other components within the
application. Annotated with `@Module` and
`@InstallIn(SingletonComponent::class)`, this object defines a module
that contributes to the singleton component's object graph, ensuring that
only a single instance of the provided dependencies is created and shared
throughout the application. Inside the module, a method named
`provideTaskDao()` is annotated with `@Provides` and `@Singleton`,
indicating that it provides a singleton instance of the `TaskDao`. The
method takes an `@ApplicationContext` parameter to access the
application context. It uses `Room.databaseBuilder()` to build an
instance of `TaskDatabase`, specifying the database class, database name
(retrieved from `Constants.TASK_DATABASE`), and the application
context. Finally, it calls `build().taskDao()` to obtain the DAO instance
from the database and returns it. This module ensures that a single
instance of `TaskDao` is available for injection throughout the
application, facilitating easy database access and management.

TaskRepository.kt

package com.android.kotlinmvvmtodolist.data

import androidx.lifecycle.LiveData
import com.android.kotlinmvvmtodolist.data.local.TaskDao
import com.android.kotlinmvvmtodolist.data.local.TaskEntry
import javax.inject.Inject

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

suspend fun insert(taskEntry: TaskEntry) = taskDao.insert(taskEntry)

suspend fun updateData(taskEntry: TaskEntry) = taskDao.update(taskEntry)

suspend fun deleteItem(taskEntry: TaskEntry) = taskDao.delete(taskEntry)

suspend fun deleteAll() {


taskDao.deleteAll()
}

fun getAllTasks() = taskDao.getAllTasks()


fun getAllPriorityTasks() = taskDao.getAllPriorityTasks()

fun searchDatabase(searchQuery: String): LiveData<List<TaskEntry>> {


return taskDao.searchDatabase(searchQuery)
}

Explaination: The `TaskRepository` class serves as an intermediary


between the data source (in this case, the `TaskDao`) and the rest of the
application. It provides methods to perform CRUD (Create, Read,
Update, Delete) operations on task entries.

- `insert(taskEntry: TaskEntry)` method inserts a new task entry into the


database.
- `updateData(taskEntry: TaskEntry)` method updates an existing task
entry in the database.
- `deleteItem(taskEntry: TaskEntry)` method deletes a specific task entry
from the database.
- `deleteAll()` method deletes all task entries from the database.
- `getAllTasks()` method returns a `Flow` of all task entries, ordered by
timestamp in descending order.
- `getAllPriorityTasks()` method returns a `Flow` of all task entries,
ordered by priority in ascending order.
- `searchDatabase(searchQuery: String)` method searches for task
entries based on a provided search query and returns a `LiveData` list of
matching task entries.

By encapsulating these database operations within the repository, it


promotes separation of concerns and makes it easier to manage data
access and manipulation within the application. Additionally, the
repository abstracts away the details of how data is fetched or
manipulated, allowing for easier testing and potential future changes to
the data source without affecting the rest of the application.

VI. Util:

BindingAdapters.kt

package com.android.kotlinmvvmtodolist.util

import android.annotation.SuppressLint
import android.graphics.Color
import android.widget.TextView
import androidx.databinding.BindingAdapter
import java.text.DateFormat

@SuppressLint("SetTextI18n")
@BindingAdapter("setPriority")
fun setPriority(view: TextView, priority: Int){
when(priority){
0 -> {
view.text = "High Priority"
view.setTextColor(Color.RED)
}
1 -> {
view.text = "Medium Priority"
view.setTextColor(Color.BLUE)
}
else -> {
view.text = "Low Priority"
view.setTextColor(Color.GREEN)
}
}
}

@BindingAdapter("setTimestamp")
fun setTimestamp(view: TextView, timestamp: Long){
view.text = DateFormat.getInstance().format(timestamp)
}

Explaination: In the `util` package, a Kotlin file named


`BindingAdapters` contains two binding adapter functions used for data
binding in Android applications. Annotated with `@BindingAdapter`,
these functions allow custom behavior to be associated with XML
attributes within layout files.

The first function, `setPriority`, takes a `TextView` and an integer


representing priority as parameters. It sets the text of the `TextView`
based on the priority value: "High Priority" for 0, "Medium Priority" for
1, and "Low Priority" for any other value. Additionally, it changes the
text color of the `TextView` according to the priority level: red for high
priority, blue for medium priority, and green for low priority.

The second function, `setTimestamp`, takes a `TextView` and a long


representing a timestamp as parameters. It formats the timestamp using
`DateFormat.getInstance().format(timestamp)` and sets the formatted text
to the `TextView`. This function is useful for displaying formatted
timestamps in UI components, such as displaying the creation date of a
task.

Overall, these binding adapter functions enhance the flexibility and


functionality of data binding in Android applications by allowing custom
logic to be applied to UI elements directly from XML layout files.

Constants.kt

package com.android.kotlinmvvmtodolist.util

object Constants {
const val TASK_DATABASE = "task_database"
const val TASK_TABLE = "task_table"
}

Explaination: The `Constants` object in the `util` package contains two


constant values related to the task database: `TASK_DATABASE` and
`TASK_TABLE`.

- `TASK_DATABASE` holds the name of the task database. It is used


when building the Room database instance.
- `TASK_TABLE` holds the name of the table in the task database where
task entries are stored. It is used as the entity table name in Room
database annotations.

By storing these values as constants in a separate object, it ensures


consistency and ease of maintenance throughout the application. If the
database or table names need to be changed, developers can simply
update these constants in one place, and the changes will reflect
throughout the application.

Screenshot is on next page……


EMULATOR SCREENSHOTS
Conclusion & Future Scope
Conclusions:

1. Effective Implementation of MVVM Architecture: Through the


development of the taskmster app using Kotlin and MVVM
architecture, you've demonstrated an understanding of separating
concerns within the application, making it more maintainable and
scalable.
2. Enhanced User Experience: The implementation of MVVM
architecture likely resulted in a more responsive and intuitive user
interface, enhancing the overall user experience.
3. Ease of Testing: With the ViewModel separating the business
logic from the UI, unit testing becomes more straightforward,
ensuring the reliability and robustness of the application.
4. Scalability and Maintainability: MVVM promotes modularity,
making it easier to add new features or modify existing ones
without affecting other parts of the codebase. This will be
beneficial for future updates and enhancements.

Future Scope:
1. Syncing Capability: Integrate cloud synchronization features to
allow users to sync their taskmaster across multiple devices. This
could involve using services like Firebase or building a custom
backend.
2. User Authentication and Authorization: Implement user
authentication to secure the taskmaster and provide personalized
experiences for different users. This could involve integrating
OAuth or other authentication mechanisms.
3. Data Visualization: Enhance the app by incorporating data
visualization features such as charts or graphs to provide insights
into productivity trends or completion rates.
4. Reminders and Notifications: Allow users to set reminders for
tasks and receive notifications to ensure they don't miss deadlines.
5. Collaborative Features: Enable users to share taskmaster with
others and collaborate on tasks. This could involve real-time
updates and chat features for better collaboration.
6. Customization Options: Provide users with the ability to
customize the app's theme, layout, and other preferences to tailor
the experience to their liking.
7. Accessibility Features: Ensure the app is accessible to users with
disabilities by implementing features such as screen reader support,
voice commands, and high contrast modes.
8. Localization: Expand the app's reach by adding support for
multiple languages and regions to cater to a global audience.
9. Performance Optimization: Continuously optimize the app's
performance to ensure smooth operation, even on low-end devices
or in poor network conditions.
10. Feedback Mechanism: Implement a feedback mechanism within
the app to gather user feedback and suggestions for further
improvements.

Project Github link:

GithubLinks: https://github.com/iamadishiv1/TaskMaster

You might also like