For android architecture, currently googles recommended MVVM pattern over MVP with it's android architecture components (AAC). There are many reasons that mvvm is preferred over MVP. Here are some cons for MVP patterns.
Callback hell
MVP patterns separates business logic and view modification into
Presenter and View, and it requires an interface for the view to
interact with the presenter when business action is performed
(eg.requesting data/uploading data). The view need to implement this
interface, and the presenter typically call methods according to this
interface to provide data to the view. In other words, MVP use
callback for the data channel.
For each action of these interactions, one method is added to the
interface. It quickly becomes overwhelming when the interaction
because there will be two many method to maintain. One change of the
method in the interface also leads to multiple changes in both
presenter and view due to the tightly coupled nature of this MVP.
Configuration changes
- MVP does not solve this problem when configuration changes occurs
(eg. like display metrics changes, screen rotation, etc). View state
will not be persisted until you explicitly handle it.
Tight couple of view and presenter
- As mentioned, in MVP, view and presenter is tightly coupled,
maintenance will be pain in the ass when it comes to adding method,
changing method signatures.
MVVM patterns generally is more suited and a better design pattern in a way that it is more maintainable. I prefer MVVM over MVP for the below reasons,
Reactive
MVVM is short term for Model View ViewModel, where view model is a
similar layer of abstraction over presenter which provide data to the
view. View observes the viewModel when viewModel notifies the view.
It ensures the view always get the latest data. And it avoid
callbacks hells by having only one place that the view can get the
data, and the view always depends on it.
MVVM works with reactive pattern very well. For examples, like you
said, retrofit typically is used with Observable pattern (like RxJava
) in MVVM. Client of retrofit lives in viewModel and retrieve data
from an Observable object that can be observe by the view. Same can
be done for data persistence and data from system services as well.
Survival on configuration changes
- ViewModel survives configuration changes by having view model exceeds
the lifespan of the view. In the implementation for viewModel from
AAC, it does exactly that. When configuration is changed, view can
always retrieve state from the viewModel, which provides a much
better UX.
Code Example
In retrofit, we need to grab an instance of the retrofit client. You should retrieve an instance either by singleton / dependency injection. Here is an example from a production project. Below examples will be in kotlin code cause it is fun.
To create an instance, (note that a JSON converter factory MOSHI is used)
Retrofit.Builder()
.baseUrl("http://google.com")
.addConverterFactory(MoshiConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(get("MockHttpClient"))
.build().create<ApiService>(ApiService::class.java)
Then, an interface needs to be defined for the api you use, note that it return a Flowable which is a Rxjava object which is similar to Observable but with backpressure handling. For now just know that it is an observable and will emit data.
interface ApiService {
@FormUrlEncoded
@POST(ACCESS_TOKEN_PATH)
fun getAccessToken(
@Field("client_id") client_id: String,
@Field("client_secret") client_secret: String,
@Field("grant_type") grant_type: String
): Flowable<GetAccessToken>
}
Then in your viewModel, for simplicity we can inject this instance directly / or retrieve it from the singleton.(A repository layer can also be added for data retrieval from the source) Note that here, live data is used as the connection between viewmodel and view, which is an observable that will dispose itself when the lifecycle of the view ends.
We subscribe to the observable return from the api and get the data from it.
class TimeSettingViewModel(val context: Application, val retrofit: Retrofit) : AndroidViewModel(context) {
private val compositeDisposable = CompositeDisposable()
val accessTokenLiveData: MutableLiveData<AccessToken> = MutableLiveData()
override fun onCleared() {
compositeDisposable.clear()
super.onCleared()
}
fun getAccessToken(){
retrofit.getAccessToken("some","thing","here")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
accessTokenLiveData.value = it
},{
it.printStackTrace()
})
}
}
Later on in your view (Activity/ Fragment / other view-based view-controller), you can inject your viewModel here and observe the data from it. And based on that data, you can update your view.
private fun accessTokenLiveData() {
timeSettingViewModel.accessTokenLiveData.observe(this, android.arch.lifecycle.Observer {
if (it != null) {
updateLoginView(it)
}
})
timeSettingViewModel.getAccessToken()
}