Android Tutorial: Model-View-ViewModel Awkwardness

MVVM vs MVP/MVC?

Whenever I am asked this question, I am quick emphasize the idea that no single GUI architecture works great in all situations. Why, you may ask? The best architecture (or at least a good choice) for a given application depends strongly on the requirements at hand. Let us briefly think about what this word requirements actually means:

  • How complex is your UI? A simple UI does not generally require complex logic to coordinate it; whereas a complex UI may require extensive logic and fine-grained control to work smoothly.
  • How much do you care about testing? Generally speaking, classes which are tightly coupled to frameworks and the OS (especially the user interface) require extra work to test.
  • How much re-usability and abstraction do you wish to promote? What if you want to share the back end, domain, and even presentation logic of your application across different platforms?
  • Are you by nature pragmatic, perfectionist, lazy, or all of the above at different times, in different situations?

THOU SHALT NOT REFERENCE THY VIEW CLASSES

For my ESL friends who cannot read old English: “YOU MAY NOT REFERENCE VIEW CLASSES

  • Your ViewModels may not possess any references (member variables, properties, mutable/immutable fields) to any Views
  • Your ViewModels may not depend on any Views
  • Your ViewModels may not talk directly to your Views

ViewLogic + ViewModel or View + ViewModelController?

When I said “break down” in the previous section, I do not mean to say that the pattern literally breaks. I mean that it breaks down into (at least) two different approaches which have very distinct appearances, benefits, and consequences. Let us consider these two approaches, and when you may wish to prefer one over the other.

MVVM is not a god damn magic wand.

First Approach: Prioritize Reusable ViewModels

As far as I can tell (we are getting in to murky water), most people who implement MVVM make it a goal to promote re-usability of ViewModels, so that they may be reused for n number of different Views (many-to-one ratio). In simple terms, there are two ways in which this re-usability is achieved:

  • By not referencing a specific View. Hopefully this is not news to you at this point.
  • By knowing as little as possible about the details of the UI in general
class NoteViewModel(val repo: NoteRepo): ViewModel(){
//Note: you may also publish data to the View via Databinding, RxJava Observables, and other approaches. Although I do not like to use LiveData in back end classes, it works great with Android front end with AAC
val noteState: MutableLiveData<Note>()
//...
fun handleEvent(event: NoteEvent) {
when (event) {
is NoteEvent.OnStart -> getNote(event.noteId)
//...
}
}
private fun getNote(noteId: String){
noteState.value = repo.getNote(noteId)
}
}
data class Note(val creationDate:String,
val contents:String,
val imageUrl: String,
val creator: User?)
private fun observeViewModel() {
viewModel.notes.observe(
viewLifecycleOwner,
Observer { notes: List<Note> ->
if (notes.isEmpty()) showEmptyState()
else showNoteList(notes)
}
)
//..
}

Second Approach: Humble View, Control-Freak ViewModel

Sometimes not having fine-grained control over your Views (which is a consequence of prioritizing re-usability of ViewModels), actually kind of sucks. To make me even less enthusiastic about applying the previous approach to my day to day Android work, I find that I often do not need to reuse a ViewModel.

Ironically, “too much abstraction” is a common critique of MVP over MVVM.

With that being said, one cannot simply add a reference back in to the ViewModel in order to regain this fine-grained control over the View. That would basically just be MVP + memory leaks (assuming you are still using ViewModel from AAC).

class UserViewModel(
val repo: IUserRepository,
){

//The actual data model is kept private to avoid unwanted tampering
private val userState = MutableLiveData<User>()

//Control Logic
internal val authAttemptState = MutableLiveData<Unit>()
internal val startAnimation = MutableLiveData<Unit>()

//UI Binding
internal val signInStatusText = MutableLiveData<String>()
internal val authButtonText = MutableLiveData<String>()
internal val satelliteDrawable = MutableLiveData<String>()

private fun showErrorState() {
signInStatusText.value = LOGIN_ERROR
authButtonText.value = SIGN_IN
satelliteDrawable.value = ANTENNA_EMPTY
}
//...
}
class LoginView : Fragment() {

private lateinit var viewModel: UserViewModel
//...

//Create and bind to ViewModel
override fun onStart() {
super.onStart()
viewModel = ViewModelProviders.of(
//...
).get(UserViewModel::class.java)

//start background anim
(root_fragment_login.background as AnimationDrawable).startWithFade()

setUpClickListeners()
observeViewModel()

viewModel.handleEvent(LoginEvent.OnStart)
}

private fun setUpClickListeners() {
//...
}

private fun observeViewModel() {
viewModel.signInStatusText.observe(
viewLifecycleOwner,
Observer {
//"it" is the value of the MutableLiveData object, which is inferred to be a String automatically
lbl_login_status_display.text = it
}
)

viewModel.authButtonText.observe(
viewLifecycleOwner,
Observer {
btn_auth_attempt.text = it
}
)

viewModel.startAnimation.observe(
viewLifecycleOwner,
Observer {
imv_antenna_animation.setImageResource(
resources.getIdentifier(ANTENNA_LOOP, "drawable", activity?.packageName)
)
(imv_antenna_animation.drawable as AnimationDrawable).start()
}
)

viewModel.authAttemptState.observe(
viewLifecycleOwner,
Observer { startSignInFlow() }
)

viewModel.satelliteDrawable.observe(
viewLifecycleOwner,
Observer {
imv_antenna_animation.setImageResource(
resources.getIdentifier(it, "drawable", activity?.packageName)
)
}
)
}

Preferences And Requirements

The point of this article was to look at two different approaches which a developer can take in terms of constructing a MVVM style GUI architecture on the Android Platform (with some carry over to other platforms).

  • Should the View observe a field for every individual widget/control it posesses, or should it observe one field which publishes a single model to render the entire View anew each time?
  • Maybe we could avoid having to make our ViewModels one-to-one, while keeping our Views as Humble Objects, simply by adding something like a Presenter or Controller to the mix?
  • Play with several approaches until you find one which you prefer. This is best done by actually building an application (it can be simple) in each style, and seeing what feels right.
  • Understand that preferences aside, different styles will tend to emphasize different benefits in exchange for different deficits. Eventually, you will be able to pick good choices based on your understanding of the project requirements rather than blind faith.

If You Learned Something…

Learn More

Application Programming Fundamentals with Kotlin and Android

Social

https://www.instagram.com/wiseassbrand/
https://www.facebook.com/wiseassblog/
https://twitter.com/wiseass301
http://wiseassblog.com/

Donate

https://www.paypal.me/ryanmkay

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Ryan Michael Kay

Ryan Michael Kay

Self-taught software developer & student of computer science.