How I Initiate MVVM after 5 years Qasir using MVP

Bhrahar Wibisono
Qasir
Published in
5 min readJun 24, 2022
source: https://stapp.space/content/images/2016/02/refactoring_poster-r723da0bee0b0423799654d8a59350032_i5v9b_8byvr_1024.jpg

Model View View Model or MVVM is now very famous in Android Development. Since Google release it and said that MVVM is the recommended architecture for building robust, high-quality apps in Android App Project, everyone races to refactor their project architecture. But it’s not that easy to refactor an architecture in a project, you need a strategy to refactor the pattern like how you refactor the old code while you are still in the middle of the sprint.

The recommendation architecture from Google is a new thing that every developer needs to learn, like how to implement MVVM, how to update the view using LiveData, and how to get the data using Coroutine.

Lucky me in Qasir there’s always a room and a chance to grow my skill. My Tech Lead approved for his android team member to do refactor one of our module features which is Attendance for refactoring into MVVM. Why Attendance? because this feature is one of the feature that didn’t have many dependencies on other module and the existing logic wasn’t too complicated.

So the first thing I did was to create the new class AttendanceOperatorDashboardViewModel to replace the presenter class

class AttendanceOperatorDashboardViewModel() : ViewModel() {// Your logic here ...}

And since at that time Qasir still did not support hilt and android 12, so I require to create the factory to class to create the view model instance

class AttendanceOperatorDashboardViewModelFactory() : ViewModelProvider.Factory {

override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(AttendanceOperatorDashboardViewModel::class.java)) {
return AttendanceOperatorDashboardViewModel() as T
}

throw Exception("Unknown View Model")
}
}

Then I need to initialize the view model and the factory in the view class

class AttendanceOperatorDashboardFragment : BaseFragment() {

private lateinit var viewModel: AttendanceOperatorDashboardViewModel
private lateinit var viewModelFactory: AttendanceOperatorDashboardViewModelFactory
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

viewModelFactory = AttendanceOperatorDashboardViewModelFactory()
viewModel = ViewModelProvider(this, viewModelFactory).get(
AttendanceOperatorDashboardViewModel::class.java
)
}

The next step I took was to move the logic and the presenter to view the model class. It’s not as easy like Ctrl+C and Ctrl+V, in the presenter we need to attach the view to the presenter so we can call the view inside the presenter when we want to update the view. Yes there are some differences between MVP and MVVM for updating the view, below is the flow of how MVVM updates the view if any data changes or any UI event from the user.

source: https://developer.android.com/topic/architecture/ui-layer

At that time in MVVM I need to create a LiveData variable that will observe in view so when any data changes the variable will update the view. I create the variable in every event like when need to show on boarding, event click button from user, or clock-in/out text that need to show in view

private val _isShowLoading = MutableLiveData<Boolean>()
val isShowLoading: LiveData<Boolean> = _isShowLoading

private val _isResponseSuccess = MutableLiveData<String?>()
val isResponseSuccess: LiveData<String?> = _isResponseSuccess

private val _isHaveConnection = MutableLiveData<Boolean>()
val isHaveConnection: LiveData<Boolean> = _isHaveConnection

private val _clockIn = MutableLiveData<String?>()
val clockIn: LiveData<String?> = _clockIn
val isClockInEmpty: LiveData<Boolean> = Transformations.map(clockIn) {
clockIn.value.isNullOrEmpty()
}

private val _clockOut = MutableLiveData<String>()
val clockOut: LiveData<String> = _clockOut
val isClockOutEmpty: LiveData<Boolean> = Transformations.map(clockOut) {
clockOut.value.isNullOrEmpty()
}

While in view I observe all the variable like below

viewModel.clockIn.observe(viewLifecycleOwner, androidx.lifecycle.Observer {
binding.textClockIn.text = it?.mapToClock().setDefaultValueIfEmpty()
})
viewModel.clockOut.observe(viewLifecycleOwner, androidx.lifecycle.Observer {
binding.textClockOut.text = it?.mapToClock().setDefaultValueIfEmpty()
})
viewModel.isShowLoading.observe(viewLifecycleOwner, androidx.lifecycle.Observer {
if (it) {
startLoading()
} else {
stopLoading()
}
})
viewModel.isClockInEmpty.observe(viewLifecycleOwner, androidx.lifecycle.Observer {
if (it) {
enableButtonClockIn()
} else {
disableButtonClockIn()
}
})
viewModel.isClockOutEmpty.observe(viewLifecycleOwner, androidx.lifecycle.Observer {
if (it) {
enableButtonClockOut()
} else {
disableButtonClockOut()
}
})

That’s what I changed to refactor Attendance’s feature presenter into MVVM. I know there’s still a lot of thing I need to improve in that code implementation since this is my first time implement MVVM. I questioning my own code like “Why there’s a lot of variable I need to create in every event/state, Is this normal?” After me and my team discuss about the code I made and ask that question we agreed to make a ViewState base class to handle every state in view model

sealed class ViewState {
abstract class Feature : ViewState()

data class Error(
val error: State.Error
) : ViewState()

sealed class Loading : ViewState() {
object Generic : Loading()
object LoadMore : Loading()
object SwipeLoading : Loading()
}
}

So there’s no need to create one variable for one event/state, we can convert my first code to be like this by create the sealed class first that inherit the ViewState Feature

sealed class AttendanceOperatorDashboardViewState : ViewState.Feature() {

object ShowOnBoarding : AttendanceOperatorDashboardViewState()
data class GetUserDetails(val detailAttendanceUser: AttendanceUser) :
AttendanceOperatorDashboardViewState()

}
// View Modelclass AttendanceOperatorDashboardViewModel() : ViewModel() {
private val _viewState = MutableLiveData<AttendanceOperatorDashboardViewState>()
val viewState: LiveData<AttendanceOperatorDashboardViewState> = _viewState
}// ViewviewModel.viewState.observe(this) {
when (it) {
is AttendanceOperatorDashboardViewState.GetUserDetails -> {
// Update UI
}
is AttendanceOperatorDashboardViewState.ShowOnBoarding -> {
// Update UI
}
}
}

By doing this, it will reduce a the code a lot. But I found a weakness in this implementation that the state will not saved when there are device configuration like rotation. But since Qasir not support the rotation, there’s no problem to implement like this.

Conclusion

By doing this initiate refactor MVP to MVVM I learn a lot like how LiveData works, how ViewModel can strain the data when any configuration changes and how the implementation of MVVM should not be strict like in documentation, it may adapt based on your project.

The next step after I initiate this refactor was to implement MVVM in every new feature and refactor presenter code if there’s not too much trouble for your sprint. Now 40% of Qasir’s feature has adapt ViewModel.

I hope these topic can give you any insight to start initiating any idea in your projects. !

--

--

Bhrahar Wibisono
Qasir
Writer for

An android developer who will continue to learn android to the best of its ability