CPS251 Android Development by Scott Shaper

Building the ViewModel

Now that we have our Database and Repository, we can build the ViewModel. The ViewModel is the bridge between your UI and your data. It manages the data for your app's screens and keeps everything in sync, even if the device rotates or the app is paused.

Why Use ViewModels with Room?

ViewModels make it easy to manage your app's data and UI. They keep your data safe during screen changes and help your UI always show the latest information from your database.

Building the ViewModel

The ViewModel manages the data for your app's screens. It talks to the Repository to get and update data, and makes sure your UI always shows the latest information.

Example: NoteViewModel (from RoomDatabaseDemo)


// NoteViewModel acts as a communication bridge between the UI (NoteScreen) and the data repository.
// It exposes data to the UI and handles UI-related data operations, abstracting the data source.
class NoteViewModel(private val repository: NoteRepository) : ViewModel() {
    // Expose a StateFlow of notes from the repository. This allows the UI to observe changes.
    // stateIn converts a Flow into a StateFlow, ensuring it's always active while subscribed.
    val notes: StateFlow<List<Note>> = repository.allNotes.stateIn(
        viewModelScope,
        SharingStarted.WhileSubscribed(5000), // Start collecting when there's an active subscriber and stop 5s after the last subscriber disappears.
        emptyList()
    )

    // Function to add a new note. It launches a coroutine in the viewModelScope
    // to perform the insert operation asynchronously via the repository.
    fun addNote(title: String, content: String, date: String) {
        viewModelScope.launch {
            repository.insert(Note(title = title, content = content, date = date))
        }
    }

    // Function to delete an existing note. It launches a coroutine in the viewModelScope
    // to perform the delete operation asynchronously via the repository.
    fun deleteNote(note: Note) {
        viewModelScope.launch {
            repository.delete(note)
        }
    }

    // Companion object to provide a factory for the NoteViewModel.
    // This is necessary because NoteViewModel has a constructor that takes NoteRepository.
    companion object {
        fun provideFactory(application: Application): ViewModelProvider.Factory {
            // Create a NoteRepository, which in turn depends on NoteDao from NoteDatabase.
            return NoteViewModelFactory(NoteRepository(NoteDatabase.getDatabase(application).noteDao
                ()))
        }
    }
}

// NoteViewModelFactory is a custom ViewModelProvider.Factory that allows us to instantiate
// NoteViewModel with a NoteRepository dependency.
class NoteViewModelFactory(private val repository: NoteRepository) : ViewModelProvider.Factory {
    // The create method is responsible for creating new ViewModel instances.
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        // Check if the requested ViewModel class is NoteViewModel.
        if (modelClass.isAssignableFrom(NoteViewModel::class.java)) {
            // If it is, create and return a new NoteViewModel instance.
            @Suppress("UNCHECKED_CAST") // Suppress the unchecked cast warning as we've checked the type.
            return NoteViewModel(repository) as T
        }
        // If an unknown ViewModel class is requested, throw an exception.
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

Understanding the `NoteViewModel` Class

The NoteViewModel is a central component in your Android app's architecture, especially when working with Room and Compose. Its primary role is to hold and manage UI-related data in a lifecycle-conscious way, surviving configuration changes like screen rotations. It acts as a communication bridge between your UI (NoteScreen) and your data layer (NoteRepository).

1. ViewModel Class Definition and Dependency


// NoteViewModel acts as a communication bridge between the UI (NoteScreen) and the data repository.
// It exposes data to the UI and handles UI-related data operations, abstracting the data source.
class NoteViewModel(private val repository: NoteRepository) : ViewModel() {
    // ...
}

2. Exposing Notes as a StateFlow


    val notes: StateFlow<List<Note>> = repository.allNotes.stateIn(
        viewModelScope,
        SharingStarted.WhileSubscribed(5000), // Start collecting when there's an active subscriber and stop 5s after the last subscriber disappears.
        emptyList()
    )

3. Adding and Deleting Notes


    fun addNote(title: String, content: String, date: String) {
        viewModelScope.launch {
            repository.insert(Note(title = title, content = content, date = date))
        }
    }

    fun deleteNote(note: Note) {
        viewModelScope.launch {
            repository.delete(note)
        }
    }

4. ViewModel Factory for Custom Dependencies


    companion object {
        fun provideFactory(application: Application): ViewModelProvider.Factory {
            return NoteViewModelFactory(NoteRepository(NoteDatabase.getDatabase(application).noteDao
                ()))
        }
    }
}

class NoteViewModelFactory(private val repository: NoteRepository) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(NoteViewModel::class.java)) {
            @Suppress("UNCHECKED_CAST")
            return NoteViewModel(repository) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

Tips for Success

Common Mistakes to Avoid

Best Practices