Activity — ViewModel connection with naming convention contract

Eric Wijaya
4 min readOct 4, 2021

Architecture Component — Activity — ViewModel — LiveData

In this article we will discuss about naming convention contract between Activity / Fragment with ViewModel. Contract here is a naming convention connection between Activity and ViewModel. Data will be stored using LiveData.

Why we need naming convention contract at Activity — ViewModel?

  • Code more readable, because there is similarity with our colleague code.
  • As reminder that data always be stored and processed at ViewModel (not at Activity).
  • When we skimming the contract, we can fast to find the UI interaction.

Why readability is important?

  • Code will be easy to understand, easy to debug, maintain, also easy to changed.
  • We or our colleague will be often to re-read that code. When code is easy to understand we will save our time when we learn the code.

Before we discuss to naming convention contract, better we remember again about Activity, ViewModel and LiveData.

Activity

  • Activity has responsibility to manage UI and interaction with user.
  • Activity has lifecycle. Activity will be destroyed and recreated if configuration changed.
  • If data stored at Activity this data will be gone if configuration change happen. (can be store via savedInstanceState — but bothering and size is limited)
  • So data better stored at ViewModel.
  • Data will be stored using LiveData.
  • Activity will observe LiveData and if there is change on data do something on UI.
  • Minimum logic at Activity, do logic at ViewModel. Also because we doing unit test at ViewModel.

ViewModel

  • ViewModel will be use for manage data.
  • ViewModel is helper class for Activity to prepare data to show at UI.
  • Data stored at ViewModel is persistent. This data will persist when configuration changes happen, like screen rotation, activity destroyed, etc.
  • ViewModel has not android.* import.
  • ViewModel not depended app lifecycle.
  • ViewModel must not has view object, activity, fragment or context. With doing this will prevent memory leak.
  • On Activity, do setup ViewModel at onCreate(). On fragment, do setup ViewModel after fragment attached with Activity.

LiveData

  • LiveData is observable data.
  • LiveData know Activity lifecycle, so LiveData only will update their observer(Activity) that has active lifecycle.
  • LiveData can store data(object) or a collection.
  • LiveData will be observed by Activity at activity onCreate()
  • LiveData value will be updated at ViewModel.

We will discuss about contract between Activity and ViewModel. Contract using interface and ViewModel will implement this interface.

Contract will using Input — Output concept.

  • Input = user interaction at Activity.
  • Process = process data at ViewModel.
  • Output = after data processed at ViewModel, what will Activity do at the UI.
    To inform Activity that the process has been done we using observer pattern.
    ViewModel has LiveData that observed by Activity.

Case example:

  • Activity has recycleView to show list data soccer player.
  • Pull to refresh to refresh data.
  • Load more to load more data next page.
  • Update recyclerView after get data from API.
  • Show error snackbar when error get data from API.
  • Click item recyclerView.

Example interface contract:

interface MyViewModelContract {

// Input sections
fun onViewLoaded(countryId: String)
fun onRefresh()
fun onLoadMore(page: Int)
fun onItemClicked(index: Int)

// Output sections
fun doUpdatePlayerList(): LiveData<MutableList<Player>>
fun doShowErrorSnackBar(): LiveData<Boolean>
}

For input section using prefix “on” — verb — interaction at UI.

For output section using prefix “do” — verb — do something at UI.

At ViewModel:

class MyViewModel:  MyViewModelContract {
var countryId = ""
var page = 1
private val updatePlayerList: MutableLiveData<List<Player>> = MutableLiveData()
private val showErrorSnackBar: MutableLiveData<Boolean> = MutableLiveData()
// Input Sections
override fun onViewLoaded(countryId: String) {
this.countryId = countryId
page = 1
getPlayerFromApi(countryId, page)
}
override fun onRefresh() {
page = 1
getPlayerFromApi(countryId, page)
}
override onLoadMore(page: Int) {
this.page = page
getPlayerFromApi(countryId, page)
}
// Output Sections
override doUpdatePlayerList = updatePlayerList
override doShowErrorSnackBar = showErrorSnackBar
private fun getPlayerFromApi(countryId: String, page: Int) {
// get data from api
// ex: success result is List<Player>
// this just for example
if (Result.Success) {
updatePlayerList.value = result
} else {
showErrorSnackBar.value = true
}
}
}

At Activity:

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//...
// prepare viewModel
initRecyclerView()
setUiListener()
subscribeToLiveData()
// ex: countryId from intent when start this activity
viewModel.onViewLoaded(countryId)
}

private fun initRecyclerView() {
// For simplicity I'm just using adapter for explain
myAdapter = MyAdapter(object: MyAdapter.Listener {
override onItemClicked(index: Int) {
viewModel.onItemClicked(index)
}
override onRefresh() {
viewModel.onRefresh()
}
override onLoadMore(page: Int) {
viewModel.onLoadMore(page)
}
)}
}

private fun setUiListener() {
// for handle other UI input (if any)
}

// Observer to ViewModel LiveData
// Output section, do something to response LiveData changes
private fun subscribeToLiveData() {
viewModel.run {
doUpdatePlayerList().reObserve(this@Activity, Observer) {
updateRecyclerView(it)
})
doShowErrorBottomSheet().reObserve(this@Activity, Observer) {
showErrorBottomSheet()
})
}
}

With this naming convention contract will made us easier when need to check the code, ex: when need to make changes or bug tracing.

We can check what UI interaction that we interested and then check the process at ViewModel.

When we create unit test also more clear because method at ViewModel has single responsibility.

Conclusion

In this article we discuss about:

  • Naming convention contract between Activity and ViewModel for better readability.
  • Some point to remember about Activity, ViewModel, and LiveData
  • Using Input — Output concept
  • Input Naming: using prefix “on” — verb — UI interaction
  • Output Naming: using prefix “do” — verb — do something at UI
  • All data store at ViewModel using LiveData
  • Code example

--

--