Simple Android Architecture : MVVM concept and reduce boilerplate code on Activity/Fragment/ViewModel
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 Fragment, ViewModel, 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
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
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 Fragment, ViewModel, 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
you can omit it by the function in BaseFragment below
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
loadData on the example is not proper naming
because, View command ViewModel to load data
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
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
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
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 :)