Inside ComposeEverything: Room, Navigation and MVVM in Pure Jetpack Compose

ComposeEverything is a reference app I keep adding to whenever I pick up a Compose pattern worth remembering — the goal is one repo where I can go look up how I wired something before, instead of re-deriving it from docs every time. It currently covers Room persistence, Navigation with typed arguments, and a ViewModel/Repository split, all in Material Design Compose UI.

Repository sitting between the ViewModel and Room

The task-manager module keeps the ViewModel free of DAO calls by routing everything through a thin repository. allTasks exposes the DAO's Flow straight through, so the UI recomposes automatically whenever the table changes — no manual refresh calls anywhere in the app:

class TaskRepository(private val taskDao: TaskDao) {
    val allTasks: Flow<List<TaskEntity>> = taskDao.getAllTasks()

    suspend fun insert(task: TaskEntity) {
        taskDao.insertTask(task)
    }

    suspend fun update(task: TaskEntity) {
        taskDao.updateTask(task)
    }

    suspend fun delete(task: TaskEntity) {
        taskDao.deleteTask(task)
    }
}

class TaskViewModel(private val repository: TaskRepository) : ViewModel() {
    val allTasks = repository.allTasks

    fun addTask(title: String, description: String) = viewModelScope.launch {
        repository.insert(TaskEntity(title = title, description = description))
    }

    fun deleteTask(task: TaskEntity) = viewModelScope.launch {
        repository.delete(task)
    }
}

It's a small amount of ceremony for a task list, but it's the same shape I'd reach for on a production screen with a real backend behind the DAO — swapping Room for a network-backed repository later doesn't touch the ViewModel at all.

Passing real objects through Navigation, not just IDs

The Compose Navigation graph routes to a music player screen with three typed arguments pulled straight off the back stack entry, instead of stashing state in a shared ViewModel or a singleton:

composable(
    route = "musicplayer/{song}/{artist}/{imageResId}",
    arguments = listOf(
        navArgument("song") { type = NavType.StringType },
        navArgument("artist") { type = NavType.StringType },
        navArgument("imageResId") { type = NavType.IntType }
    )
) { backStackEntry ->
    val song = backStackEntry.arguments?.getString("song")
    val artist = backStackEntry.arguments?.getString("artist")
    val imageResId = backStackEntry.arguments?.getInt("imageResId")
    MusicPlayer(navController, song, artist, imageResId)
}

It keeps the destination screen a pure function of its arguments — reopen the app on that route and it renders correctly without anything else in memory.

The repo keeps growing as I run into new Compose APIs worth keeping around; full source for both modules is on GitHub.