Android Tutorial: Model-View-ViewModel Awkwardness

MVVM vs MVP/MVC?

  • 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

  • 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?

MVVM is not a god damn magic wand.

First Approach: Prioritize Reusable ViewModels

  • 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

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

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

  • 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

Social

Donate

--

--

--

Self-taught software developer & student of computer science.

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Shrink Oversized Data Files in Microsoft SQL Server

What Is Manual Testing? The most effective method to Do Manual Testing

Git Credentials on MacOS — Caching, Updating, and Deleting Your GitHub Account Credentials

The 10 universal laws of developer stupidity

GraphQL VS REST API

Cross Chain Swap vs Layer2 Swap

Java Microservice Architecture

Stuck on a Bug? Here’s What to Do Next

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.

More from Medium

Some Best Practices for Android App Architecture

How I reduced my App Size: Android App Bundles

Basics for your first Android App: REST API, Database, and Fragment Navigation

MVC vs MVP vs MVVM Design Patterns