All days
Day 10Module 5 · Persistence· 5–7 hrs

Room II — CRUD, Coroutines, WorkManager, Notifications, DI

Complete the ToDo app: edit/delete, coroutines for async, WorkManager for a scheduled reminder notification, and DI (Hilt) to wire it all up cleanly.

0% done

Key concepts

  • AlertDialog & PopupMenu for delete confirmations
  • Coroutines (viewModelScope, suspend, Dispatchers.IO)
  • WorkManager (OneTimeWorkRequest, PeriodicWorkRequest)
  • NotificationChannel + NotificationCompat
  • Hilt basics (@HiltAndroidApp, @Inject, @Module)

Lectures in this day

  1. 01

    Edit ToDo / Mark as Completed

    Tap a row to open the Add screen, but pre-filled with that todo's values. If we're editing (we already have a todo), call `update`. If we're adding new, call `add`. The checkbox on each row flips `done` right away.

    Real life · A grocery list on the fridge: tick 'Bread' when you buy it. Change '1 loaf' to '2 loaves' if you change your mind.
    kotlin
    binding.save.setOnClickListener {
        val t = existing?.copy(                 0
            title = titleText,
            description = descText,
            dueAt = dueAt,
        ) ?: Todo(                              1
            title = titleText,
            description = descText,
            dueAt = dueAt,
        )
    
        if (existing == null) vm.add(t) else vm.update(t)
        findNavController().popBackStack()      2
    }

    Practice · 3 quick questions

    1. Q1.Editing a ToDo typically means…

    2. Q2.Marking as completed toggles…

    3. Q3.UI shows completion by…

    Pick one answer per question.
  2. 02

    Delete ToDo (Popup Menu + AlertDialog)

    Long-press or tap an overflow icon on a row to show a PopupMenu with 'Delete'. Then show an AlertDialog to ask 'Are you sure?' before actually deleting — this saves users from accidents.

    Real life · A paper shredder with a safety catch: it always asks 'sure?' before turning paper into confetti.
    kotlin
    PopupMenu(view.context, view).apply {
        menu.add("Delete")
        setOnMenuItemClickListener {
            AlertDialog.Builder(view.context)
                .setTitle("Delete this todo?")
                .setPositiveButton("Delete") { _, _ -> vm.delete(todo) }
                .setNegativeButton("Cancel", null)
                .show()
            true
        }
    }.show()

    Practice · 3 quick questions

    1. Q1.A PopupMenu shows…

    2. Q2.AlertDialog is used to…

    3. Q3.Delete calls…

    Pick one answer per question.
  3. 03

    Object Binding and Binding Adapters

    With Data Binding you can pass whole objects into the XML with `<variable>`. `@BindingAdapter` lets you teach the XML a NEW attribute — for example, `app:formattedDate` that takes a Long millis and shows a friendly date.

    Real life · Teaching your template a new word: say `app:formattedDate` and it knows to turn milliseconds into 'Sat, 5 pm'.
    kotlin
    @BindingAdapter("formattedDate")
    fun TextView.setFormattedDate(millis: Long) {
        text = SimpleDateFormat("EEE, h:mm a", Locale.getDefault())
            .format(Date(millis))
    }
    
    0

    Practice · 3 quick questions

    1. Q1.Object binding lets XML bind to…

    2. Q2.@BindingAdapter customizes…

    3. Q3.Two-way binding uses…

    Pick one answer per question.
  4. 04

    Introduction to Coroutines

    A coroutine is a small, cheap 'task' that can pause and resume without blocking the main thread. `suspend` on a function means 'this function can pause'. `Dispatchers.IO` runs your code on a pool of background threads good for file/network work.

    Real life · Ordering coffee: you sit down and wait. The barista pings you when it's ready — you didn't block the queue standing at the counter.
    kotlin
    suspend fun loadUser(): User = withContext(Dispatchers.IO) {
        api.fetchUser()   0
    }

    Practice · 3 quick questions

    1. Q1.Coroutines are…

    2. Q2.Suspend functions can be called from…

    3. Q3.viewModelScope is…

    Pick one answer per question.
  5. 05

    Implement Coroutines into ToDo App

    Mark DAO writes as `suspend` so Room can run them off the main thread. In the ViewModel, wrap the call in `viewModelScope.launch { }`. The UI stays smooth while the database work happens in the background.

    Real life · The receptionist keeps chatting up front while the clerk files papers in the back room — nobody waits.
    kotlin
    fun add(title: String, desc: String, dueAt: Long) =
        viewModelScope.launch {                          0
            repo.add(Todo(title = title, description = desc, dueAt = dueAt))
        }

    Practice · 3 quick questions

    1. Q1.DB calls should be…

    2. Q2.In ViewModel, launch with…

    3. Q3.Dispatchers.IO is best for…

    Pick one answer per question.
  6. 06

    WorkManager and Schedule Work

    WorkManager runs work that MUST eventually finish, even if the user closes the app or the phone reboots — like a reminder or an upload. It is NOT for exact-second timing; it is for reliability.

    Real life · A postman who promises: 'I will deliver this within 2 hours, even if it rains and even if you close your door.'
    kotlin
    val req = OneTimeWorkRequestBuilder<ReminderWorker>()
        .setInitialDelay(2, TimeUnit.HOURS)
        .setInputData(workDataOf("title" to todo.title))
        .build()
    
    WorkManager.getInstance(context).enqueue(req)

    Practice · 3 quick questions

    1. Q1.WorkManager is for…

    2. Q2.You define work by extending…

    3. Q3.Schedule with…

    Pick one answer per question.
  7. 07

    Send Local Notification (ToDo Alert)

    A notification is a message from your app that shows in the phone's status bar. On Android 8+ you first create a NotificationChannel (once). On Android 13+ you also need to ask the user for the notification permission at runtime.

    Real life · A gentle tap on the shoulder from the phone: 'hey, this todo is due right now'.
    kotlin
    class ReminderWorker(ctx: Context, p: WorkerParameters)
        : CoroutineWorker(ctx, p) {
    
        override suspend fun doWork(): Result {
            val title = inputData.getString("title") ?: return Result.failure()
    
            val notif = NotificationCompat.Builder(applicationContext, "todo")
                .setSmallIcon(R.drawable.ic_bell)
                .setContentTitle("Todo due")
                .setContentText(title)
                .build()
    
            NotificationManagerCompat.from(applicationContext).notify(1, notif)
            return Result.success()
        }
    }

    Practice · 3 quick questions

    1. Q1.A notification needs a…

    2. Q2.Build with…

    3. Q3.Show with…

    Pick one answer per question.
  8. 08

    Dependency Injection Overview

    Instead of each class making its own database/repository/helper, one central 'container' hands them out. That's Dependency Injection (DI). Hilt is Google's easy DI library. It cuts boilerplate and makes testing easier.

    Real life · A coffee supplier ships ground beans to every café. Nobody grows their own. Change supplier once — all cafés benefit.

    Practice · 3 quick questions

    1. Q1.DI (Dependency Injection) is…

    2. Q2.Common DI library on Android?

    3. Q3.Benefit of DI?

    Pick one answer per question.
  9. 09

    Implement DI into ToDo App

    Turn on Hilt by annotating your Application class with `@HiltAndroidApp`. Tell Hilt how to build the DB and DAO inside a `@Module`. Then just `@Inject` the repository into your ViewModel — Hilt provides it automatically.

    Real life · Wiring the whole building to one fuse box — flip a switch and everyone's lights work.
    kotlin
    @HiltAndroidApp
    class TodoApp : Application()
    
    @Module @InstallIn(SingletonComponent::class)
    object DbModule {
        @Provides @Singleton
        fun db(@ApplicationContext ctx: Context) =
            Room.databaseBuilder(ctx, AppDb::class.java, "todo.db").build()
    
        @Provides fun dao(db: AppDb) = db.todoDao()
        @Provides fun repo(dao: TodoDao) = TodoRepository(dao)
    }
    
    @HiltViewModel
    class TodoViewModel @Inject constructor(
        private val repo: TodoRepository
    ) : ViewModel()

    Practice · 3 quick questions

    1. Q1.In a Hilt app you annotate the Application with…

    2. Q2.Inject into an Android class using…

    3. Q3.Providing a type with a constructor uses…

    Pick one answer per question.
  10. 10

    Module 5 Summary

    You now have a real-world app shape: Room saves data, coroutines keep the UI smooth, WorkManager runs reliable background work, notifications talk to the user, and Hilt wires it all together.

    Real life · You've moved into a fully wired flat — plumbing, electricity, doorbell. Ready to invite users in.

    Practice · 3 quick questions

    1. Q1.Module 5 mainly built…

    2. Q2.Which is NOT in Module 5?

    3. Q3.You should now be able to…

    Pick one answer per question.

Real-life analogies

Coroutines

Ordering food at a counter — instead of standing there, you sit down (suspend) and get pinged when it's ready. The kitchen (IO thread) keeps working; you keep chatting (UI thread stays responsive).

WorkManager

A reliable postman. You tell them 'deliver this in 2 hours even if I close the app or reboot'. They'll retry until it gets there.

Dependency Injection

Instead of every restaurant grinding its own coffee beans, a supplier delivers ground coffee to whoever asks. Same idea for repositories/DAOs.

Code examples

Coroutines in ViewModel

kotlin
class TodoViewModel(
    private val repo: TodoRepository
) : ViewModel() {

    fun add(title: String, dueAt: Long) = viewModelScope.launch {
        repo.insert(Todo(title = title, dueAt = dueAt))
    }

    fun toggle(t: Todo) = viewModelScope.launch(Dispatchers.IO) {
        repo.update(t.copy(done = !t.done))
    }
}

WorkManager + notification

kotlin
class ReminderWorker(ctx: Context, params: WorkerParameters)
    : CoroutineWorker(ctx, params) {

    override suspend fun doWork(): Result {
        val title = inputData.getString("title") ?: return Result.failure()
        NotificationManagerCompat.from(applicationContext)
            .notify(1, buildNotif(applicationContext, title))
        return Result.success()
    }
}

0
val req = OneTimeWorkRequestBuilder<ReminderWorker>()
    .setInitialDelay(2, TimeUnit.HOURS)
    .setInputData(workDataOf("title" to "Buy milk"))
    .build()
WorkManager.getInstance(context).enqueue(req)

Hilt setup

kotlin
@HiltAndroidApp
class TodoApp : Application()

@Module
@InstallIn(SingletonComponent::class)
object DbModule {
    @Provides @Singleton
    fun provideDb(@ApplicationContext ctx: Context): AppDb =
        Room.databaseBuilder(ctx, AppDb::class.java, "todo.db").build()

    @Provides fun provideDao(db: AppDb) = db.todoDao()
}

Hands-on checklist

Milestone

Production-style ToDo app: CRUD + async + scheduled notifications + DI.

Common pitfalls

  • Forgetting to create a NotificationChannel on Android 8+ — nothing shows.
  • Missing POST_NOTIFICATIONS runtime permission on Android 13+.