Skip to main content

Working with ViewModel

1. Creating ViewModel Classes

ViewModel classes in Android are designed to store and manage UI-related data in a lifecycle-conscious way. They are part of the Android Architecture Components and help separate business logic from UI components.

Property Description Notes
ViewModel Base class for ViewModel Extend this class to create custom ViewModels
LiveData Observable data holder Used to observe changes in data
MutableLiveData Mutable version of LiveData Allows data to be updated from within the ViewModel

Example: Basic ViewModel Class

import androidx.lifecycle.ViewModel
import androidx.lifecycle.MutableLiveData

class UserViewModel : ViewModel() {
    private val _user = MutableLiveData<String>()
    val user: LiveData<String>
        get() = _user

    fun setUser(name: String) {
        _user.value = name
    }
}
Note: ViewModel instances are automatically retained during configuration changes like screen rotations.

2. Sharing ViewModel Between Fragments

Fragment ViewModel Instance Notes
Fragment A sharedViewModel Accessed via by viewModels() with viewModelProviderFactory
Fragment B sharedViewModel Same instance as Fragment A's ViewModel

Example: Sharing ViewModel Between Fragments

class SharedViewModel : ViewModel() {
    private val _data = MutableLiveData<String>()
    val data: LiveData<String> = _data

    fun updateData(newData: String) {
        _data.value = newData
    }
}

class FragmentA : Fragment() {
    private val sharedViewModel: SharedViewModel by viewModels(
        factoryProducer = { SharedViewModelFactory() }
    )

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        sharedViewModel.data.observe(viewLifecycleOwner) { data ->
            // Update UI with data
        }
    }
}

class FragmentB : Fragment() {
    private val sharedViewModel: SharedViewModel by viewModels(
        factoryProducer = { SharedViewModelFactory() }
    )

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        sharedViewModel.updateData("Updated from Fragment B")
    }
}
Note: Both fragments must use the same ViewModelProvider.Factory to ensure they receive the same instance.
Warning: Avoid direct access to ViewModel instances in fragments without proper scoping; use viewModels() delegate for lifecycle-aware access.

3. Using SavedStateHandle

The SavedStateHandle is a key component in Android's ViewModel that allows you to save and restore data across configuration changes (like screen rotations) and process death. It provides a way to persist data in a structured and type-safe manner.

Property Type Description
get(key: String) T? Retrieves a value of type T associated with the given key.
set(key: String, value: T) Unit Sets a value of type T for the given key.
contains(key: String) Boolean Returns true if the key exists in the handle.
remove(key: String) T? Removes and returns the value associated with the key.

Example: Using SavedStateHandle in ViewModel

class MyViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {

    private val nameKey = "name"
    private val ageKey = "age"

    fun setName(name: String) {
        savedStateHandle.set(nameKey, name)
    }

    fun getName(): String? = savedStateHandle.get(nameKey)

    fun setAge(age: Int) {
        savedStateHandle.set(ageKey, age)
    }

    fun getAge(): Int? = savedStateHandle.get(ageKey)
}
Note: SavedStateHandle is automatically managed by the framework and ensures that data persists across configuration changes. It is particularly useful for passing data between fragments or activities.
Warning: Avoid storing sensitive data in SavedStateHandle as it may be persisted to disk and could pose a security risk.

Key Features of SavedStateHandle

  • Automatic Persistence: Data is automatically persisted and restored across configuration changes.
  • Type Safety: Provides type-safe access to stored data.
  • Key Management: Ensures keys are managed to avoid collisions.

4. Injecting Dependencies into ViewModel

Dependency Type Injection Method Description
Repository ViewModelProvider Pass repository instance via constructor in ViewModel
Use Case ViewModelProvider Inject use case object to handle business logic
Context ViewModelProvider Inject application context for accessing resources

Example: Injecting Repository into ViewModel

class MyViewModel(private val repository: MyRepository) : ViewModel() {
    fun fetchData() {
        viewModelScope.launch {
            val data = repository.fetchData()
            // Handle data
        }
    }
}

Example: Using ViewModelProvider with Factory

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

// In Activity/Fragment
val factory = MyViewModelFactory(repository)
val viewModel = ViewModelProvider(this, factory).get(MyViewModel::class.java)

5. Managing ViewModel Scope

Property Description Usage
viewModelScope Coroutine scope tied to the ViewModel's lifecycle Used to launch coroutines that should be cancelled when the ViewModel is cleared
lifecycleScope Coroutine scope tied to the Lifecycle owner's lifecycle Used for launching coroutines that should be cancelled when the Lifecycle is destroyed

Example: Using viewModelScope

class MyViewModel : ViewModel() {
    fun fetchData() {
        viewModelScope.launch {
            val data = repository.fetchData()
            // Update UI with data
        }
    }
}

Example: Using lifecycleScope

class MyFragment : Fragment() {
    private val viewModel: MyViewModel by viewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        lifecycleScope.launch {
            val data = viewModel.fetchData()
            // Update UI with data
        }
    }
}
Note: viewModelScope is preferred for ViewModel-specific coroutines, while lifecycleScope is used in fragments or activities to manage coroutines tied to their lifecycle.

6. Handling Configuration Changes

Configuration Change Description ViewModel Behavior
Screen Rotation Changing device orientation from portrait to landscape ViewModel is retained and not recreated
Keyboard Visibility Showing or hiding the soft keyboard ViewModel is retained and not recreated
Locale Change Changing language or region settings ViewModel is retained and not recreated

Example: Observing Configuration Changes in ViewModel

class MyViewModel : ViewModel() {
    private val _configChange = MutableLiveData<String>()
    val configChange: LiveData<String> = _configChange

    fun handleConfigChange(newLocale: String) {
        _configChange.value = newLocale
    }
}
Note: ViewModel automatically survives configuration changes, making it ideal for holding UI-related data.
Warning: Avoid storing context or activity references in ViewModel to prevent memory leaks.

7. Clearing ViewModel

Example: Clearing ViewModel Data

class MyViewModel : ViewModel() {
    private val _data = MutableLiveData<String>()
    val data: LiveData<String> = _data

    fun clearData() {
        _data.value = null
    }
}

class MyActivity : AppCompatActivity() {
    private lateinit var viewModel: MyViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel = ViewModelProvider(this)[MyViewModel::class.java]

        viewModel.clearData()
    }
}
Method Description Usage
clearData() Resets the data in the ViewModel to null Call this method to clear the LiveData value
Note: Clearing data in ViewModel does not destroy the ViewModel instance itself. It only resets the state of the data.

8. Using ViewModel with Compose

Example: Basic ViewModel usage in Compose

import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel

class UserViewModel : ViewModel() {
    private val _user = mutableStateOf("John Doe")
    val user: State<String> = _user

    fun updateUser(name: String) {
        _user.value = name
    }
}

@Composable
fun UserScreen(viewModel: UserViewModel = viewModel()) {
    Column(
        modifier = Modifier.padding(16.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        Text(text = "Current user: ${viewModel.user.value}")
        Button(onClick = { viewModel.updateUser("Jane Smith") }) {
            Text(text = "Update User")
        }
    }
}
Feature Description Notes
viewModel() viewModel() is a Compose function to access ViewModel instances. Automatically handles lifecycle and scope
State<T> Used to hold state in ViewModel for Compose. Immutable and reactive
mutableStateOf() Creates a mutable state holder for reactive updates. Used inside ViewModel to manage UI state
Note: ViewModel in Compose is designed to be lifecycle-aware and should not hold references to composables or views.

9. Testing ViewModel

Example: Unit Test with JUnit and Mockito

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.Mockito

class UserViewModelTest {

    @get:Rule
    val rule = InstantTaskExecutorRule()

    private lateinit var viewModel: UserViewModel

    @Before
    fun setup() {
        val repository = Mockito.mock(UserRepository::class.java)
        viewModel = UserViewModel(repository)
    }

    @Test
    fun `getUsers returns correct data`() {
        val expectedUsers = listOf("Alice", "Bob")
        Mockito.`when`(repository.getUsers()).thenReturn(expectedUsers)

        val users = viewModel.users.value

        assertEquals(expectedUsers, users)
    }
}
Test Type Description Tools
Unit Test Tests ViewModel logic in isolation Junit, Mockito
Integration Test Tests ViewModel with Repository and other components Robolectric, Espresso
Note: Use InstantTaskExecutorRule to ensure LiveData observers execute synchronously in tests.
Warning: Avoid testing UI logic directly in ViewModel tests. Focus on business logic and state transitions.

10. Implementing ViewModel Factory

Example: Custom ViewModel Factory

import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider

class MyViewModel(val data: String) : ViewModel() {
    // ViewModel logic here
}

class MyViewModelFactory(private val data: String) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(MyViewModel::class.java)) {
            @Suppress("UNCHECKED_CAST")
            return MyViewModel(data) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}
Property Description Usage
ViewModelProvider.Factory Interface for creating ViewModels Implement to customize ViewModel creation
create(modelClass) Method to instantiate ViewModel Must return instance of requested ViewModel class
Note: The factory pattern allows for dependency injection and custom initialization logic in ViewModels.

Was this article helpful?