This is one of series of Simple Android Architecture. and you can check sample code there.

This article shows how to reduce boilerplate and how to focus on business logic only

Prerequisite

  • Jetpack Navigation
  • Koin
  • MVVM

Index

  • Inflate layout and binding ViewModel
  • To get Activity’s ViewModel or other Fragment’s ViewModel
  • How to create and bind FragmentViewModel, layout fast
  • To observe LiveData omitting viewLifeCycleOwner
  • Explanation of MVVM concept
  • Don’t implement frequently used UI functions repeatedly on Activity/Fragment, and just command on ViewModel
  • How to remember the usage?

Inflate layout and binding ViewModel

class MainFragment : BaseFragment() {  
    override val layoutId: Int = R.layout.fragment_main    
    val viewModel: MainViewModel by _bindingViewModel()
}

Just define layout and viewModel, then they’ll be bound automatically.

If you don’t need viewModel or need multiple viewModel. you can do it.

What you have to do is

  • To use BaseFragment, BaseActivity, BaseViewModel
  • Inject ViewModel by Koin (bindingViewModel() use Koin)

To get Activity’s ViewModel or Other Fragment’s ViewModel

class OtherViewModelFragment : BaseFragment() {
    override val layoutId: Int = R.layout.fragment_other_view_model

    val viewModel: OtherViewModelViewModel by bindingViewModel {
        parametersOf(getNavGraphViewModel<MainViewModel>(R.id.mainFragment))
    }
    val viewModel2: ParentViewModel by bindingViewModel {
        parametersOf(getActivityViewModel<MainActivityViewModel>())
    }
    
    val viewModel: NavArgsViewModel by bindingViewModel {
    parametersOf(getNavArgs<NavArgsFragmentArgs>())
    }

}

getNavGraphViewModel is from Jetpack Navigation library. in order to use this, you have to use Navigation on the Fragment.

getNavArgs is getting arguments of the Fragment. It’s data. so, It’s better to set to ViewModel directly

How to create and bind FragmentViewModel, layout fast

Normally, we use same name for Fragment, ViewModel, layout like SampleFragment, SampleViewModel, fragment_sample

So, I created the android studio live template for that.

fragment

# Template Text
class $NAME$ : BaseFragment() {  
    override val layoutId: Int = R.layout.$LAYOUT_NAME$
    $END$  
}

# Variable - NAME
fileNameWithoutExtension()  

# Variable - LAYOUT_NAME
groovyScript("String withoutFragment = _1.substring(0, _1.indexOf('Fragment')); String underlineCase = withoutFragment.replaceAll(/[A-Z]/) { '_' + it }.toLowerCase(); return 'fragment' + underlineCase", fileNameWithoutExtension())

bindingViewModel

# Template Text

val viewModel: $NAME$ViewModel by bindingViewModel()

# Variables - NAME
groovyScript("return _1.substring(0, _1.indexOf('Fragment'))", kotlinClassName())

bindingViewModelActivity

# Template Text

val viewModel: $NAME$ViewModel by bindingViewModel {  
    parametersOf(getActivityViewModel<$VIEWMODEL$>())  
}

# Variables - NAME
groovyScript("return _1.substring(0, _1.indexOf('Fragment'))", kotlinClassName())

If I can create Intellij plugin, I would like to make plugin to create and bind these automatically. but, for now, no plan.

To observe LiveData omitting viewLifeCycleOwner

items.observe {  
    showSnackbar(R.string.loading_tasks_error)  
}

you can omit it by the function in BaseFragment below

fun <T> LiveObject<T>.observe(onChanged: (T) -> Unit) {
    observe(viewLifecycleOwner, onChanged)
}

Explanation of MVVM concept

Before next part, I would like to explain about MVVM concept

View doesn’t think anything. just show UI with data.

ViewModel think ui logic, get data from model, command View to show the data

Model think data logic and manufacture data from various source

It means that View doesn’t command ViewModel to do something

class MainFragment : BaseFragment() {
    fun onClick() {
        viewModel.loadData()
    }
}

loadData on the example is not proper naming
because, View command ViewModel to load data

class MainFragment : BaseFragment() {
    fun onClick() {
        viewModel.onClick()
    }
}

this is appropriate, View doesn’t command, instead, deliver event from user action or Activity/Fragment event

Don’t implement frequently used UI functions repeatedly on Activity/Fragment, and just command on ViewModel

Activity/Fragment Lifecycle event

class MainViewModel : BaseViewModel() {

    override fun onResume() {
        //do ui logic on resume event
    }
}

Sometimes, we do some process onResume, onPause, onStart, onStop.
As View just deliver event to ViewModel, there is onResume() function on BaseViewModel, and BaseFragment deliver event to BaseViewModel. what we have to do is just to override onResume() function and process logic.

Navigation

class BaseViewModel : ViewModel() {
    fun navigateDirection(navDirections: NavDirections)
    fun navigateUp()
    fun navigateDirection(id: Int)
}

ViewModel command what View do.
NavDirections contains which Fragment to redirect to, and arguments of the Fragment
_I think Where to redirect is commanding part. and argument is data. so, I suggest to use the functions above on _ViewModel
 side. and View side navigates by ViewModel’s command

Dialogs(Snackbar, ProgressBar, AlertDialog, etc)

Sometimes we shows dialogs on UI. If the UI customizing is required, we have to write code on View side. but if it’s common UI, then we implement the dialogs on BaseActivity and BaseFragment just one time. and ViewModel just call showSnackbar(), and showProgressbar(), etc predefined on BaseViewModel

class SampleViewModel : BaseViewModel() {
   fun onClick() {
      if(!hasData) {
         showSnackbar(...)
         return
      }
      //do something
   }
}

ViewMode calls startActivityForResult()

You must feel weird about this because startActivity and startActivityResult is Activity or Fragment’s task not related to ViewModel.

Then, why don’t we use it in ViewModel?
I think the reason is that startActivityForResult method has dependency to Activity, and we need requestCode as well.

Then, if no dependency exists, and requestCode is not required?
Even in this case, someone says that it’s View side task. not ViewModel side.

Let’s think.

we call Activity with data and the Activity returns data. the structure is totally same with logical function. b = f(a)
_Then, why don’t we use it like _function
 on ViewModel?

about load(), LiveResource. kindly check this

Even, testing also simple(check test code)

I couldn’t find the answer why we shouldn’t do that on ViewModel. If you find, kindly let me know, I’ll remove this part.

ViewModel calls requestPermissions()

This also same approach with the above. but request permission.

How to remember the usage?

I introduced a lot. and you may feel it’s learning curve.
But, What you have to remember is just BaseActivity, BaseFragment, BaseViewModel.

And functions of them are commented on each class. BaseFragment for example
So, just use the functions mentioned on the comment. and it’s not mandatory to understand inner structure.

All library code, and sample code exists on this repo

You may feel it’s weird or inappropriate on some part.
I also agree on the perspective and also understand the reason somehow.

But, I couldn’t find why we shouldn’t do that. and removing repeated code is important.
and someone may be worry that ViewModel get bigger as all the code is in the ViewModel.
but, It’s not like that, at first, on the code above, ViewModel just commanded View side, not adding View side code on ViewModel.
And the code above do is just removing repeated code of View side only.
so, ViewModel side doesn’t get bigger.

And if ViewModel get bigger because of some complicated business logic.
I recommend to move logic to Model side if it’s not related to UI. Actually, ViewModel is related to View. so, ViewModel shouldn’t know about processing of data, instead, ViewModel just know the result of processing of data

It’s up to you accept the way or not. And I always welcome any feedback.

Happy coding :)