Android MVVM Architecture

Model View ViewModel is an android architecture model. It separates data-presentation logic from business logic. It’s designed to store and manage UI-related data.

  • Model - Application data logic. Plain Objects. API processing classes. Databases.
  • View - Screen’s layout. Houses all widgets for displaying information.
  • ViewModel - Business Logic. Object which describes the behaviour of view logic depending on the result of the model work.

We usually create one ViewModel for an activity or a fragment. Sometimes ViewModels can be shared by two or more Activities or Fragments. ViewModel are created when the activity is created, and they live until the activity is cleared from memory. Therefore ViewModel can only hold values which belong to the activity.

Implementation

We will have 3 packages: model, view, and viewmodel.

Inside model, we have data classes such as this one

data class Country(val countryName: String?)

Inside view, we move the MainActivity class that already comes with the default project.

In viewmodel we create a ListViewModel class.

Dependencies

Add the following dependencies to your app’s build.gradle.

implementation "android.arch.lifecycle:viewmodel:1.1.1" 
implementation "android.arch.lifecycle:extensions:1.1.1" 

ViewModel

LiveData is a variable type that anyone can subscribe to and can get this data in real time. When this variable is updated then all the subscribers to this variable will also get notified and they may be updated.

class ListViewModel: ViewModel() {

	val countries = MutableLiveData<List<Country>>()
	val countryLoadError = MutableLiveData<Boolean>()
	val loading = MutableLiveData<Boolean>()
	
	fun refresh() {
		countries.value = ...
	}
}

Following this model, anyone who subscribes to the variable countries will get notified whenever this variable is updated.

View

This is how to use ViewModel from MainActivity.

class MainActivity : AppCompatActivity() {

	lateinit var viewModel: ListViewModel
	lateinit var adapter: ...
	
	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		setContentView(R.layout.activity_main)
		
		viewModel = ViewModelProviders.of(this).get(ListViewModel::class.java)
		viewModel.refresh()
		
		observeViewModel()
	}
	
	fun observeViewModel() {
		viewModel.countries.observe(this, Observer { countries ->
			// this will get called whenever 'countries.value = ...' is set
			countries?.let { adapter.updateCountries(it) }
		})
	}
}

This way, the ViewModel just needs to update its data and doesn’t know anything from the consumer Activity, as it will be observing and called when the data changes.

Activity vs Fragments

When binding ViewModel, it has to be bound to an Activity. Not a Fragment. This way all the Fragment that are related to the same Activity will share the same ViewModel status.

bind an Activity
lateinit var viewModel: ListViewModel

override fun onCreate(...) {
	viewModel = ViewModelProviders.of(this).get(ListViewModel::class.java)
}
bind a Fragment
lateinit var viewModel: ListViewModel

override fun onCreate(...) {
	viewModel = ViewModelProviders.of(requireActivity()).get(ListViewModel::class.java)
}

ViewModel Factory

Sometimes we need to pass constructor params to a ViewModel as a starting point. To do such thing, we need support from a ViewModel Factory.

We have the following base ViewModel class. We have set the constructor param we need.

class MainActivityViewModel(startingTotal : Int) : ViewModel() {
	private var total = 0
	
	init {
		total = startingTotal
	}
	
	fun getTotal(): Int { return total }
	fun setTotal(input: Int) { total += input }
}

In order to do this, we create the following factory. We override the create() method and add the same constructor param.

class MainActivityViewModelFactory(private val startingTotal : Int) : ViewModelProvider.Factory {

	override fun<T : ViewModel?> create(modelClass: Class<T>): T {
		if(modelClass.isAssignableFrom(MainActivityViewModel::class.java)) {
			return MainActivityViewModel(startingTotal) as T
		}
		throw IllegalArgumentException("Unknown View Model Class")
	}

}

Then we use it as follows

class MainActivity : AppCompatActivity() {
	private lateinit var viewModel: MainActivityViewModel
	private lateinit var viewModelFactory: MainActivityViewModelFactory
	
	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		
		// programmatically set the value we need. 
		viewModelFactory = MainActivityViewModelFactory(5)
		viewModel = ViewModelProvider(this, viewModelFactory).get(MainActivityViewModel::class.java)
	}
}

Adapter

In order to display a list of elements into the view, we need an Adapter. This is a class that will take the data in the form the ViewModel gives and it will transform that list into something the view’s layouts can display.

This adapter takes a ViewHolder. We will create it as an internal class.

class CountryListAdapter(var countries: ArrayList<Country>) : RecyclerView.Adapter<CountryViewHolder>() {
	
	override fun onCreateViewHolder(parent: ViewGroup, p1: Int) = CountryViewHolder(
		LayoutInflater.from(parent.context).inflate(R.layout.item_country, 
		parent,
		false)
	)
	
	override fun getItemCount() = countries.size
	
	override fun onBindViewHolder(holder: CountryViewHolder, position: Int) {
		holder.bind(countries[position])
	}
	
	class CountryViewHolder(view: View): RecyclerView.ViewHolder(view) {
		fun bind(country: Country) 
			
		}
	}
	
}