Adarsh CSE225
Adarsh CSE225
Adarsh CSE225
By
Adarsh Shivam
12115538
CSE 225
ANDROID APP DEVELOPMENT
1. Introduction 1
v) Database file
3. Emulator Screenshots 31
Features:
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.
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:
Database: Utilize Room Persistence Library for local storage 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:
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() {
setupActionBarWithNavController(findNavController(R.id.nav_host_fragment))
}
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>
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() {
binding.apply {
spinner.adapter = myAdapter
btnAdd.setOnClickListener {
if(TextUtils.isEmpty((edtTask.text))){
Toast.makeText(requireContext(), "It's empty!",
Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
viewModel.insert(taskEntry)
Toast.makeText(requireContext(), "Successfully added!",
Toast.LENGTH_SHORT).show()
findNavController().navigate(R.id.action_addFragment_to_taskFragment)
}
}
return binding.root
}
fragment_add.xml
<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() {
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding.lifecycleOwner = this
binding.viewModel = viewModel
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
}
setHasOptionsMenu(true)
hideKeyboard(requireActivity())
return binding.root
}
@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
}
@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)
}
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
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(){
fragment_task.xml
<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() {
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
}
viewModel.update(taskEntry)
Toast.makeText(requireContext(), "Updated!",
Toast.LENGTH_SHORT).show()
findNavController().navigate(R.id.action_updateFragment_to_taskFragment)
}
}
return binding.root
}
<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>
row_layout.xml
<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>
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
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)
TaskDatabase.kt
package com.android.kotlinmvvmtodolist.data.local
import androidx.room.Database
import androidx.room.RoomDatabase
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()
}
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
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)
}
Constants.kt
package com.android.kotlinmvvmtodolist.util
object Constants {
const val TASK_DATABASE = "task_database"
const val TASK_TABLE = "task_table"
}
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.
GithubLinks: https://github.com/iamadishiv1/TaskMaster