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