Saturday, January 3, 2015

Adventures in Code: Android MVP By Example

In this post, we'll take a look at an application of the MVP pattern in Android. Given that MVP is an architectural style as opposed to a rigid set of rules to be followed, it goes without saying that what follows is merely one schmuck's interpretation of that pattern, for better or for worse. Comments, feedback, and spirited debate are always welcome!

Note - this write-up assumes some working knowledge of Dagger. I found the documentation on the site to be really thorough, and having experience with Guice is a bit of a plus, but if you want a write-up on Dagger then let me know in the feedback.

Let's start by establishing what we're doing and why we're doing it before we get into the code.

What is MVP?

As mentioned earlier, MVP is an architectural pattern for designing software, that when used, is most commonly found in applications that have a user interface. The pattern assumes three logical components:

  1. Model (an interface defining what data will displayed by a View)
  2. View (an interface describing how data represented by a Model will be displayed)
  3. Presenter (an intermediary facilitating communication between the Model and View)
Simply illustrated...


A typical interaction might go something like this:

  1. A user interacts (click, touch, drag, fling, butt-dial, etc) with a screen element hosted by the View
  2. The View delegates this interaction to a Presenter 
  3. The Presenter interprets this interaction and in turns communicates with one or more associated Models
  4. If any results are forthcoming, they are then communicated back to the Presenter and then finally to the View


OK... But Why Use It?

A few reasons:
  • Separating the domain logic and the presentation logic into discrete components allows you to unit test the behavior of each in isolation, rather than having to test both in conjunction by doing instrumentation style testing on an emulator or a real device:
    • The UI (activities and fragments) can be tested with Presenter(s) that are test doubles (using a framework like Robolectric), and...
    • Presenters can be tested with Views that are test doubles (without any Android dependencies)
  • Having smaller, focused classes helps steer you away from code bloat within any one class 


Sold! Show Me How!

Let's assume for this example that we have an application that bootstraps itself by checking to see if the current installation has an account associated with it. For this, we'll design three components in line with the MVP style:

An interface for the Model...
... an interface for the View...
(It might be kind of hard to see at this point, but BootstrapView is shaping up to be a pretty passive component. For example, it knows how to show and hide its progress indicator... but it doesn't know when to do either!)

... and finally, an interface for the Presenter:
Now let's put some meat on those bones.

This being an Android application, we'll create an Activity named BootstrapActivity that implements our BootstrapView interface:
Let's turn our attention to the more noteworthy parts:
  • (Line 15) as you can see, BootstrapActivity is an extension of InjectingFragmentActivity. A full description of InjectingFragmentActivity goes beyond the scope of this post, but it should suffice to say that its onCreate() method is overridden to invoke Dagger to create a sub-graph of the application's dependencies and subsequently use that graph to inject `this` instance. As a result, we have an instance of a BootstrapPresenter after the call to super.onCreate() on line 22.
  • (Line 15) as previously mentioned, this class implements our BootstrapView that will later be used to drive the UI.
  • (Line 24) this is the jump off point for our domain logic. When the activity is created, the BootstrapPresenter is invoked to check for an existing account.
  • (Lines 34-54) the BootstrapPresenter instance will asynchronously invoke any of these methods depending where it is in it's invocation of checkForAccount().

Now let's look at an implementation of BootstrapPresenter:


Let's once again inspect the noteworthy stuff:
  • Take note that this class is a simple POJO and thus requires no special Android framework-y stuff in order to test
  • (Line 8) as expected, this class implements our BootstrapPresenter interface 
  • (Lines 10-18) the same instance of BootstrapView that invoked the call to checkForAccount() will be injected as an argument for constructing this instance of BootstrapPresenterImpl. Keeping this reference around his gives us a way to call back to the UI to perform the various actions exposed by the BootstrapView interface.
  • (Line 16) wrapping the reference to BootstrapView with a WeakReference here is critical to memory management in that it signals to the VM that the wrapped instance should not be retained on account of this referrer. In other words, if the only references to the wrapped instance are in the form of a WeakReference, then the garbage collector can feel free to reclaim the memory.
  • (Lines 22-24) while the WeakReference itself will not be null, its delegate may be if the previously described circumstances called for it to be garbage-collected. A simple null check will tell you everything you need to know.
  • Now we'll basically drive the behavior of BootstrapView:
    • (Line 27) instruct the BootstrapView to show the user that the application is currently doing work.
    • (Line 31) instruct the BootstrapView to stop doing whatever it was doing to show the user that the app was doing work.
    • (Lines 33-37) invoke BootstrapView to either take the user to the account registration or to whatever other entry point the application has defined for users that already have an account.
  • (Lines 41-43) this is obviously a dummy implementation for example purposes, but this type of behavior could be further decomposed into Services, Interactors, etc. that can likewise be injected into this instance and delegated to.
And there you have it. MVP on Android. 




No comments:

Post a Comment