On Communication Between V And P In Android MVP

Nikita Barishok
AndroidPub
Published in
10 min readMar 7, 2016

Every day in Android developer circles appears more and more information about applying MVP, Clean Architecture with different libraries to build well-structured apps. There is (already)(more than) enough information to combine and get a solution that generally will be loosely coupled, testable, maintainable. And… That’s really good! As you can observe, I couldn’t resist (OK, I didn’t even try to resist) and have written some story, too. And if you are already bored with it, at least take a look at the sample that I’ve made. It is an MVP-and-Clean-Architecture-based-Android-app with improved (I hope so) communication between View and Presenter. Well, let’s move to the article itself.

When I began reading various articles about MVP, Clean Architecture, how it can be applied to Android there were some dark places at first (handling configuration changes, for exmaple). Time was going by, I was reading something new, tried it on the sample project, finally things fell into place..But one thing still was bugging me(a lot) and seemed suspicious, — how communication between presenter and view is organized.

The thing is view has lifecycle constraints and actually needs presenter attached only between onResume and onPause of it’s lifecycle(which is reasonable), while presenter is POJO with no lifecycle constraints, so all interaction with view in presenter has to be wrapped in null-checks-of-the-view to be sure it is in correct lifecycle phase(which is ugly).

Likely you are already familiar with how it looks like, but for the sake of the story consistency:

One thing to highlight is the fact that generally presenter acts as a container for interactors(use cases) of the system. Presenter has subscribers to that interactors and when they emit some signal to some subscriber, it executes logic based on the signal (that logic may lead to some communication with view(actually almost always does so, because that’s why presenter is needed)).

With this in mind let’s move forward. At first, it’d be nice to reformulate the reason to the aforementioned null-checks in a more clear form:

We are null-checking view in presenter to avoid NPE in case when presenter, being (already) detached from the view, receives signal that leads to communication with view.

And the natural answer to statement above will be:

So don’t let presenters receive any signals if view gets detached from them! In other words, unsubscribe from interactors if view gets detached from presenter.

Implementing this statement in code won’t be a big deal, especially if you are familiar with RxJava. Operator takeUntil(Observable) perfectly suits the needs. It’s marble diagram is the next:

This operator introduces an ability to force stop receiving items based on some event from other observable. So adding to presenter

  1. an observable tracking whether presenter’s view is attached
  2. “takeUntil” operator to presenter’s interactors,

should lead us to null-checks-free presenter.

Here is how it looks in code:

When view is attached to presenter the tracking-if-view-is-attached-observable is created. This observable is cold, so it won’t emit anything until somebody subscries to it. And when it’s time to detach view from presenter, we activate observable, it emits empty onCompleted() signal thus triggering unsubscription from interactors.

Seems great, no more null-checks! It’s a win, you say.. Oh..No.. Not yet.

Having been so focused on that nasty repetitive occurrences of null-checks, another issue with the same roots(even more serious because it relates to the end user of the app) left totally unnoticed, — data loss! Whether it is null-checks or usage of takeUntil() operator, in both cases presenter deliberately throws away incoming data if result comes while view is temporarily detached(during configuration change, for example). Actually in this case view potentially loses state also, but I’ll refer to this issue as “data loss”.

One can say the possibility of such a losses is not that big and I agree with that, but heey.. But we are trying to #BuildBetterApps here, so this is…

We need another, even more natural answer to the issue highlighted above to cover data loss case also (“takeUntil” operator is not guilty and can be quite useful in many other scenarios though). Its time to think about the issue from another point:

Instead of unsubscribing from interactors we need an ability to persist the result or, even more, overall state of the view, so that it can be restored when view becomes (re-)attached .

Seems like the attention should be shifted from presenter to the communication between presenter and view. Traditionally the Observer pattern is used for decoupling the view (receiver) and presenter (sender). And the view has lifecycle, it isn’t able to listen to presenter events all the time (only while in foreground). It’s now clear that applying Observer pattern separatelty is not enough, because doing so the is a possibility of a data loss. The thing is:

We have to decouple the presenter and view in time also.

And when it comes to decoupling in time, some buffer is needed. So it sounds like additional layer should be added, something like “smart” event bus, for communication between view and presenter, with the next responsibilities:

  1. from presenter’s point of view: this layer acts like a view without lifecycle
  2. from view’s point of view: this layer acts like a presenter with ability to restore view state when it is needed (after view being recreated generally)

So this event bus should be “sticky” to be able to push the results to the view if at the moment of (re-)attachment it (already) has some data for it. Doing so data losses should vanish away.

The functionality described above is separate responsibility so we are, being good citizens of oo-world, will extract it in the separate class. And in theory adding this functionality should be transparent for both view and presenter, so it will be a good indicator if during implementation there will be no need to change code of presenter and view(the presenter, in fact, will change.. in a good way☺) OK, let’s move closer to the implementation details.

Before describing communication between view and presenter, it’d be good to show how they look like for the sake of story consistency. I’ll highlight here only the main implementation details which I believe will be enough to grasp things. But if you prefer just code or gave this article a chance at the beginning but now totally bored with it, full code sample can be found here in repo, as I mentioned before.

For the strongest ones I’ll continue. We’ll go with a simple example: app that grabs from backend data about current temperature in some city and displays it. So presenter here is responsible coordinating work between interactor for getting current temperature and view. View is just LCEView with ability to handle one general error. The code of presenter:

Now the view:

As you can see, presenter is a view-interactors coordinator and nothing more, that’s good. It has no null-checks also(remember that now view has no lifecycle from presenter’s point of view). View itself is as simple as it can be and that’s also good. It is good place to mention that presenter in this example has an ability to be persisted during configuration changes and to save in a bundle something it needs if app process is about to be killed because of low-memory. All of this logic is implemented with Dagger 2 and some helper classes. It is effectively abstracted away in ComponentManagerFragment for ease of reusing (or not using for cases where it isn’t needed). Implementation details of how to achieve this can be found in this article.

Now class for communication between view and presenter, WeatherCommunicationBus, can be introduced. The presenter and view will think they’re talking to each other directly, but actually it will be done through this class. So to achieve it WeatherCommunicationBus should implement both views’ and presenters’ interaces naturally. Here is the base version of the bus:

Yes.. I’ve noticed it, too.. Null-checks just have migrated to WeatherCommunicationBus! But be patient, everything is good in its season. Lets move forward. Having built the basis for transparent communication, the next step is to implement aforementioned presenter’s point of view(full source):

from presenter’s point of view: this layer acts like a view without lifecycle

WeatherCommunicationBus has no lifecycle constraints, lives as long as presenter lives and acts as view for presenter, forwarding all the calls to the view when it is available(there is not much sense in such a plain delegation, but be patient). Now presenter gets view attached after being constructed and before onCreate(), and view stays attached until presenter is destroyed. Thus, presenter has gained the ability to communicate to view at any desired moment.

OK, now the second one:

from view’s point of view: this layer acts like a presenter with ability to restore view state when it is needed (after being recreated generally)

In another words, WeatherCommunicationBus should track view’s state and be able to use the this state to restore view after it has been recreated. It seems reasonable to use State pattern(surprise) to track view state. In academic way this pattern looks like this, but also can be built in simplified way (which is suitable for simple LCEViews). Here is how it looks like for example(full source):

As you can see, it has to implement Parcelable interface in order to be placed in a bundle. Also it has methods setStateXXX where XXX is the corresponding method of the view. The most interesting one here is the apply() method , it is the heart of view state, because it actually responsible for restoring the view to the actual state.

If you are reading this article, likely you have spent some time reading articles about Android MVP, so chances are you have heard about Mosby library. It is really great, there are lots of nice practices and saving view state is among them. But there is one thing in Mosby (I’ve seen it in other projects also) that bothers me about view state: view state is tracked in view. Here is how it goes:

So what’s the point? Why bother? Everything seems reasonable you may say.

OK.. I’ve already told it, but it’s really important so I’ll repeat:

View is constrained by lifecycle object.

And here is the answer in a nutshell: issue is connected with the fact that presenter can have something for the view when it is temporarily detached, so view state (being tracked by the view) can’t be updated (or be updated behid time, see interaction scheme below for explanation), therefore data gets lost.

Given the fact that view is traditonally attached to presenter in onViewCreated() and detached in onDestroyView() (this fact itself can cause troubles) and view state saving occures in onSaveInstanceState(), let’s depict the interaction between presenter and view:

In this interaction three distinct intervals can be identified:

(1) is safe for communicating with view and won’t cause any troubles. The only thing to mention here is the fact that updating view after onPause() is impractical, just saving its’ state is enough.

(2) is where things begin getting worse. At first, communication with view after onSaveInstanceState() also won’t be reflected in UI of the View_Instace1 + it won’t be saved to view state. Also there is danger if view is going to commit() fragment transactions here.

(3) is the interval where view and presenter are detached from each other so that view state also can’t be updated.

So in cases (2) and (3) data loss can occur. With this approach even if setRetainInstance(true) is applied to view(fragment), the interval where data can be lost will only get smaller, but won’t be eliminated nonetheless.

As you may have already guessed, we are going to use view state in WeatherCommunicationBus, thus saving ourselves from many troubles. Code for tracking view state is quite simillar to the one above (full source):

And finally the schematic view of the approach with communication bus:

All the complexities of attachment/detachment, saving/restoring state are managed by WeatherCommunicationBus, therefore making possible to track view state correctly even if view is temporarily detached from presenter.

Well, while communication between view and presenter looks fine, it’s fair to turn back what we begin with and ask: what about null-checks that migrated to WeatherCommunicationBus? And the answer is the next:

**meditatively**

It is something you can live with in the end.

Or if you totally can’t stand it, you can apply Null-Object pattern as I did (actually it could be applied to Presenter in the beggining, but I thought that showing use case of takeUntil operator for solving not-that-important-null-checks issue as intro to very-important-data-loss issue might be useful to somebody☺). The code with Null-Object applied is really straightforward.

OK, now it’s time to combine all the parts to get the desired result. No, I don’t want to flood the screen with full sources of all the classes that took place in solution. Just a few words about how all the parts above are injected. Given you have regular DI for MVP settled (I mean injection of presenter, interactors etc.), CommunicationBus injection will go smoothly. That’s because it looks like presenter (implements presenter’s interface)(full sources, before and after).

Before:

After:

Due to Dagger2 handy @Named qualifier the necessary dependencies are provided gracefully. And as all parts of the solution have been covered, it’s time to wrap up. The described sample is Work In Progress, it will be updated and maintained, but for now my intent was to cover the main idea behind communication between V and P.

Hope you’ve found something interesting/helpful/new in this story!

And thanks for reading.

UPD: There is a continuation of the story! It is all about handling-navigation-with-this-approach! Here it is:

https://medium.com/@nbarishok/on-navigation-in-android-mvp-d26065586dcd#.dk80n66tz

If you enjoyed this story, click that little green heart below.

twitter.com/onemanparty_

--

--