Skip to content

How to Implement MVVM in Android

MVVM

  • A structure that uses the observer pattern to implement a single source of truth for data callbacks.
  • Core structure (this diagram is drawn based on my understanding using ProcessOn; everyone’s understanding may differ. Feel free to adapt it according to your own business logic) ..mvvm-architecture-framework

Creating a ViewModel is straightforward. Following the official recommendation, you can inject it according to the corresponding lifecycle. This also involves the factory pattern; for details, refer to ViewModelProvider.Factory:

  • ViewModel tied to its own lifecycle
kotlin
private val viewModel: MyViewModel by lazy {
    ViewModelProvider(
        this,
        MyViewModelFactory()
    )[MyViewModel::class.java]
}
  • ViewModel tied to a parent container’s lifecycle
kotlin
private val parentViewModel: RouteViewModel? by lazy {
    parentFragment?.let {
        ViewModelProvider(
            it,
            MyViewModelFactory()
        )[RouteViewModel::class.java]
    }
}

private val activityViewModel: ActivityViewModel? by lazy {
    activity?.let {
        ViewModelProvider(
            it,
            MyViewModelFactory()
        )[ActivityViewModel::class.java]
    }
}

Writing ViewModel and Using LiveData

kotlin
class MyViewModel(
    private val repository: Repository,
): ViewModel() {
    private val _data: MutableLiveData<String> = MutableLiveData()
    val data: LiveData<String> = _data
    
    private val _errorMessage: MutableLiveData<Throwable> = MutableLiveData()
    val errorMessage: LiveData<Throwable> = _errorMessage
    
    private val _isRefresh: MutableLiveData<Boolean> = MutableLiveData()
    val isRefresh: LiveData<Boolean> = _isRefresh
    
    fun fetchData() {
        _isRefresh.value = true
        viewModelScope.launch(Dispatchers.Main + CoroutineExceptionHandler { _, throwable -> {
            _errorMessage.value = throwable
            _isRefresh.value = false
        }}) {
            // Call repository.fetchData() in a background thread to fetch data.
            // Note: when posting to LiveData, you must be on the UI thread.
            _data.value = repository.fetchData()
            _isRefresh.value = false
        }
    }
}

class MyFragment: Fragment() {
    private val viewModel: MyViewModel by lazy {
        ViewModelProvider(this)[MyViewModel::class.java]
    }
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewModel.isRefresh.observe(this) {
            showHideSpinner(it)
        }
        
        viewModel.errorMessage.observe(this) {
            Toast.makeText(context, it.message, Toast.LENGTH_SHORT).show()
        }
        
        viewModel.data.observe(this) {
            updateUI(it)
        }
        
        viewModel.fetchData()
    }
    
    private fun updateUI(data: String) {
        // update UI
        println(data)
    }
}
  • The difference between MVVM and MVP is that MVVM does not necessarily rely on two-way binding. Because DataBinding’s two-way binding capability is quite limited, when using MVVM, I personally prefer a single source of truth. This is also the approach most recommended by Google. With LiveData, a lifecycle-aware observer tool, data in the ViewModel can be easily returned to the View.
  • A notable point: in a common single-Activity, multiple-Fragment setup, using the parentFragment lifecycle is a good way to handle routing.
  • One unavoidable aspect of using LiveData is that traditionally, each LiveData corresponds to a single state. In the example above, there are three states: refreshing, data successfully fetched, and fetch failed. This can lead to a LiveData explosion in the ViewModel, where the number of LiveData objects becomes very large.

Just something casual. Hope you like it. Built with VitePress