Few days ago, during Google I/O was presented Android Architecture Components. We decided to combine these components with MPV Architecture. So, let’s see what we got in a result.
One of the common problems working with MPV Architecture is to keep Presenter’s state while changing screen orientation. There are some solutions for that (Loaders, retainInstance, Mosby, Moxy and so on).
One of those Architecture Components presented during the I/O was a ViewModel.
“The ViewModel class is designed to store and manage UI-related data so that the data survives configuration changes such as screen rotations.” — from official documentation.
It gives us an opportunity to save the object during screen orientation change.
The other component is Lifecycle, which we will use.
“The android.arch.lifecycle package provides classes and interfaces that let you build lifecycle-aware components — which are components that can automatically adjust their behavior based on the current lifecycle of an activity or fragment.” - from official documentation.
1. Before orientation change
2. After orientation change
From the beginning let’s design our Presenter and View. Let’s create a Contract Interface.
public interface BaseContract {
**interface** View {
}
**interface** Presenter<V **extends** BaseContract.View> {
**void** attachLifecycle(Lifecycle lifecycle);
**void** detachLifecycle(Lifecycle lifecycle);
**void** attachView(V view);
**void** detachView();
V getView();
**boolean** isViewAttached();
**void** onPresenterDestroy();
}
}
After this, we need to create Presenter that implements our Presenter Contract.
public abstract class BasePresenter<V extends BaseContract.View> implements LifecycleObserver, BaseContract.Presenter<V> {
**private** V **view**;
@Override
**final public** V getView() {
**return view**;
}
@Override
**final public void** attachLifecycle(Lifecycle lifecycle) {
lifecycle.addObserver(**this**);
}
@Override
**final public void** detachLifecycle(Lifecycle lifecycle) {
lifecycle.removeObserver(**this**);
}
@Override
**final public void** attachView(V view) {
**this**.**view** \= view;
}
@Override
**final public void** detachView() {
**view** \= **null**;
}
@Override
**final public boolean** isViewAttached() {
**return view** != **null**;
}
}
So, we have a Presenter, which attaches and detaches the View and Lifecycle Observer.
With the next step, let’s create our Activity. Here we have a problem: the presenter will be recreating every time after the orientation change.
Let’s go ahead.
The ViewModel is automatically retained during configuration changes so the data it holds is immediately available to the next activity or fragment instance. This will help us not to initialize the Presenter every time.
For that, we will create the Class, which extends from ViewModel. It will receive and return our presenter’s object. ViewModel also has onCleared() method. If the activity is re-created, it receives the same ViewModel instance that was created by the previous activity. When the owner activity is finished, the Framework calls ViewModel’s onCleared() method so that it can clean up resources.
public final class BaseViewModel<V extends BaseContract.View, P extends BaseContract.Presenter<V>> extends ViewModel {
**private** P **presenter**;
**void** setPresenter(P presenter) {
**if** (**this**.**presenter** \== **null**) {
**this**.**presenter** \= presenter;
}
}
P getPresenter() {
**return this**.**presenter**;
}
@Override
**protected void** onCleared() {
**super**.onCleared();
**presenter**.onPresenterDestroy();
**presenter** \= **null**;
}
}
In our next step we will create the BaseViewModel instance inside the Activity.
Also we must give the Lifecycle to the Presenter. So we need LifecycleRegistry. And the Activity must implement to the LifecycleRegistryOwner.
public abstract class BaseActivity<V extends BaseContract.View, P extends BaseContract.Presenter<V>> extends AppCompatActivity implements BaseContract.View, LifecycleRegistryOwner {
**private final** LifecycleRegistry **lifecycleRegistry** \=
**new** LifecycleRegistry(**this**);
**protected** P **presenter**;
@CallSuper
@Override
**protected void** onCreate(@Nullable Bundle savedInstanceState) {
**super**.onCreate(savedInstanceState);
BaseViewModel<V, P> viewModel =
ViewModelProviders._of_(**this**).get(BaseViewModel.**class**);
**if** (viewModel.getPresenter() == **null**) {
viewModel.setPresenter(initPresenter());
}
**presenter** \= viewModel.getPresenter();
**presenter**.attachLifecycle(getLifecycle());
**presenter**.attachView((V) **this**);
}
@Override
**public** LifecycleRegistry getLifecycle() {
**return lifecycleRegistry**;
}
@CallSuper
@Override
**protected void** onDestroy() {
**super**.onDestroy();
**presenter**.detachLifecycle(getLifecycle());
**presenter**.detachView();
}
**protected abstract** P initPresenter();
}
At the first time creating the Activity, we must create a Presenter and give it to the ViewModel. The next time when the activity recreates, we just reuse our Presenter from the ViewModel.
At the first time creating Presenter, we must attach Lifecycle and View. For avoiding memory leak, we also need to detach Lifecycle and View in onDestroy().
As a bonus, we also have Activity Lifecycle events inside presenter.
@OnLifecycleEvent(value = Lifecycle.Event.ON_CREATE)protected void onCreate() {
}
For the complete puzzle, something lacks. It is a state of Views.
For that, we must have Bundle getStateBundle() in our BaseContract, and Bundle stateBundle in the Presenter, which will keep and return our View state.
The final Presenters is like that:
public abstract class BasePresenter<V extends BaseContract.View> implements LifecycleObserver, BaseContract.Presenter<V> {
**private** Bundle **stateBundle**;
**private** V **view**;
@Override
**final public** V getView() {
**return view**;
}
@Override
**final public void** attachLifecycle(Lifecycle lifecycle) {
lifecycle.addObserver(**this**);
}
@Override
**final public void** detachLifecycle(Lifecycle lifecycle) {
lifecycle.removeObserver(**this**);
}
@Override
**final public void** attachView(V view) {
**this**.**view** \= view;
}
@Override
**final public void** detachView() {
**view** \= **null**;
}
@Override
**final public boolean** isViewAttached() {
**return view** != **null**;
}
@Override
**final public** Bundle getStateBundle() {
**return stateBundle** \== **null** ?
**stateBundle** \= **new** Bundle() : **stateBundle**;
}
@CallSuper
@Override
**public void** onPresenterDestroy() {
**if** (**stateBundle** != **null** && !**stateBundle**.isEmpty()) {
**stateBundle**.clear();
}
}
}
Thanks for reading this article. You can find full code there.
Let’s become friends on Twitter, Github and Facebook. If you enjoyed the writings then please use the ❤ heart below to recommend this article so that others can see it.
We will also love to hear your comments and suggestions :)
Thanks.