How to make a todo list app in android studio using kotlin
How to make a todo list app in Android Studio using Kotlin
Creating a simple TODO app with three activities (MainActivity, InsertActivity, UpdateActivity) and features like pinning notes, searching, and using a Room database in Android with Kotlin requires several steps. Below is a basic implementation.
1- Setting Up the Project
- Create a new Android project in Android Studio.
2- Add dependencies in the build. gradle:
- Add the Room database dependencies and other necessary libraries.
dependencies {
// Room components
implementation "androidx.room:room-runtime:2.4.3"
kapt "androidx.room:room-compiler:2.4.3"
// Kotlin components
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.6.10"
// Lifecycle components
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.1"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1"
// RecyclerView
implementation "androidx.recyclerview:recyclerview:1.2.1"
// Core components
implementation "androidx.core:core-ktx:1.7.0"
// Material Design
implementation "com.google.android.material:material:1.5.0"
}
3-Create data model for the notes:
@Entity(tableName = "notes")
data class Note(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
val title: String,
val description: String,
val isPinned: Boolean = false
)
4- Create DAO interface:
@Dao
interface NoteDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(note: Note)
@Update
suspend fun update(note: Note)
@Delete
suspend fun delete(note: Note)
@Query("SELECT * FROM notes ORDER BY isPinned DESC, id ASC")
fun getAllNotes(): LiveData<List<Note>>
}
5- Create Room Database
@Database(entities = [Note::class], version = 1, exportSchema = false)
abstract class NoteDatabase : RoomDatabase() {
abstract fun noteDao(): NoteDao
companion object {
@Volatile
private var INSTANCE: NoteDatabase? = null
fun getDatabase(context: Context): NoteDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
NoteDatabase::class.java,
"note_database"
).build()
INSTANCE = instance
instance
}
}
}
}
6- Create Repository Class
class NoteRepository(private val noteDao: NoteDao) {
val allNotes: LiveData<List<Note>> = noteDao.getAllNotes()
suspend fun insert(note: Note) {
noteDao.insert(note)
}
suspend fun update(note: Note) {
noteDao.update(note)
}
suspend fun delete(note: Note) {
noteDao.delete(note)
}
}
7 - Create View Model
class NoteViewModel(application: Application) : AndroidViewModel(application) {
private val repository: NoteRepository
val allNotes: LiveData<List<Note>>
init {
val noteDao = NoteDatabase.getDatabase(application).noteDao()
repository = NoteRepository(noteDao)
allNotes = repository.allNotes
}
fun insert(note: Note) = viewModelScope.launch {
repository.insert(note)
}
fun update(note: Note) = viewModelScope.launch {
repository.update(note)
}
fun delete(note: Note) = viewModelScope.launch {
repository.delete(note)
}
}
8- Create an Adapter Class Using Kotlin
class NoteAdapter(private val listener: OnItemClickListener) : RecyclerView.Adapter<NoteAdapter.NoteViewHolder>() {
private var notes: List<Note> = emptyList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NoteViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_note, parent, false)
return NoteViewHolder(view)
}
override fun onBindViewHolder(holder: NoteViewHolder, position: Int) {
val note = notes[position]
holder.bind(note)
}
override fun getItemCount(): Int {
return notes.size
}
fun setNotes(notes: List<Note>) {
this.notes = notes
notifyDataSetChanged()
}
inner class NoteViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
private val titleTextView: TextView = itemView.findViewById(R.id.titleTextView)
private val descriptionTextView: TextView = itemView.findViewById(R.id.descriptionTextView)
private val pinImageView: ImageView = itemView.findViewById(R.id.pinImageView)
init {
itemView.setOnClickListener(this)
pinImageView.setOnClickListener {
listener.onPinClick(notes[adapterPosition])
}
}
fun bind(note: Note) {
titleTextView.text = note.title
descriptionTextView.text = note.description
pinImageView.setImageResource(if (note.isPinned) R.drawable.ic_pinned else R.drawable.ic_unpinned)
}
override fun onClick(v: View?) {
listener.onItemClick(notes[adapterPosition])
}
}
interface OnItemClickListener {
fun onItemClick(note: Note)
fun onPinClick(note: Note)
}
}
9 Create Layout -:
- create_main_activty.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<EditText
android:id="@+id/searchEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Search notes"
android:padding="16dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/searchEditText"
android:padding="16dp" />
<Button
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:layout_margin="16dp"
android:background="?attr/colorPrimary"
android:text="Add" />
</RelativeLayout>
- item_notes.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
app:cardCornerRadius="8dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<TextView
android:id="@+id/titleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold"
android:textSize="16sp" />
<TextView
android:id="@+id/descriptionTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/titleTextView"
android:textSize="14sp" />
<ImageView
android:id="@+id/pinImageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:src="@drawable/ic_unpinned" />
</RelativeLayout>
</androidx.cardview.widget.CardView>
- activity_insert.xml and activity_update.xml
These layouts will be similar and include EditTexts for title and description and a Button to save the note.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".InsertActivity">
<EditText
android:id="@+id/titleEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Title"
android:padding="16dp" />
<EditText
android:id="@+id/descriptionEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/titleEditText"
android:hint="Description"
android:padding="16dp" />
<Button
android:id="@+id/saveButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/descriptionEditText"
android:layout_centerHorizontal="true"
android:layout_marginTop="16dp"
android:text="Save" />
</RelativeLayout>
10- Implement Activities
- Main Activity
class MainActivity : AppCompatActivity(), NoteAdapter.OnItemClickListener {
private lateinit var noteViewModel: NoteViewModel
private lateinit var adapter: NoteAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
adapter = NoteAdapter(this)
val recyclerView: RecyclerView = findViewById(R.id.recyclerView)
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(this)
noteViewModel = ViewModelProvider(this).get(NoteViewModel::class.java)
noteViewModel.allNotes.observe(this, Observer { notes ->
notes?.let { adapter.setNotes(it) }
})
val searchEditText: EditText = findViewById(R.id.searchEditText)
searchEditText.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
val query = s.toString().trim()
adapter.setNotes(noteViewModel.allNotes.value?.filter {
it.title.contains(query, ignoreCase = true) || it.description.contains(query, ignoreCase = true)
} ?: emptyList())
}
override fun afterTextChanged(s: Editable?) {}
})
findViewById<Button>(R.id.fab).setOnClickListener {
startActivity(Intent(this, InsertActivity::class.java))
}
}
override fun onItemClick(note: Note) {
val intent = Intent(this, UpdateActivity::class.java)
intent.putExtra("note_id", note.id)
startActivity(intent)
}
override fun onPinClick(note: Note) {
noteViewModel.update(note.copy(isPinned = !note.isPinned))
}
}
- Insert Activity
class InsertActivity : AppCompatActivity() {
private lateinit var noteViewModel: NoteViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_insert)
noteViewModel = ViewModelProvider(this).get(NoteViewModel::class.java)
findViewById<Button>(R.id.saveButton).setOnClickListener {
val title = findViewById<EditText>(R.id.titleEditText).text.toString()
val description = findViewById<EditText>(R.id.descriptionEditText).text.toString()
if (title.isNotEmpty() && description.isNotEmpty()) {
noteViewModel.insert(Note(title = title, description = description))
finish()
} else {
Toast.makeText(this, "Please enter both title and description", Toast.LENGTH_SHORT).show()
}
}
}
}
- Update Activity
class UpdateActivity : AppCompatActivity() {
private lateinit var noteViewModel: NoteViewModel
private var noteId: Int = -1
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_update)
noteViewModel = ViewModelProvider(this).get(NoteViewModel::class.java)
noteId = intent.getIntExtra("note_id", -1)
val titleEditText: EditText = findViewById(R.id.titleEditText)
val descriptionEditText: EditText = findViewById(R.id.descriptionEditText)
if (noteId != -1) {
noteViewModel.allNotes.observe(this, Observer { notes ->
val note = notes.find { it.id == noteId }
note?.let {
titleEditText.setText(it.title)
descriptionEditText.setText(it.description)
}
})
}
findViewById<Button>(R.id.saveButton).setOnClickListener {
val title = titleEditText.text.toString()
val description = descriptionEditText.text.toString()
if (title.isNotEmpty() && description.isNotEmpty()) {
noteViewModel.update(Note(id = noteId, title = title, description = description))
finish()
} else {
Toast.makeText(this, "Please enter both title and description", Toast.LENGTH_SHORT).show()
}
}
}
}
This setup should give you a basic TODO app with pinning, searching, inserting, updating, and deleting notes using Room, ViewModel, LiveData, and RecyclerView in Kotlin. Adjust the code and add more features as needed.
Comments
Post a Comment