VGTech is a blog where the developers and devops of Norways most visited website share code and tricks of the trade… Read more



Are you brilliant? We're hiring. Read more

Don’t forget the View Model!

Android

Background

I’ve observed an increase in architecture-focused Android posts lately in channels¬†such as /r/androiddev and Android Weekly. That’s great, but frankly it’s about time. When I transitioned from Windows Phone development to Android a couple of years ago I felt it was difficult to find good examples on how to architect a solid app. It didn’t help that Google’s examples violated most best practices[1] either. I kind of went with my own version of MVVM (or MVPVM) which I had with me from the .NET platform and it sort of worked. Reading
Hannes Dorfmann’s post on MVP and his Mosby framework, I realize I’m not the only one who’ve been struggling with getting the established patterns working with Android (he spent 3 years). The Android SDK¬†is not exactly leading you into a good architecture out of the box. Maybe Fragments is partly to blame, and why Square Inc is advocating against it.

In any case the community is steadily going in a direction where the good UI-architecture is common knowledge. Frameworks[2] are helping in that matter and blog posts like Dorfmann’s and Artem Zin’s post on the Model. Still, a term I seldom hear in the Android world is View Model – but there really shouldn’t be a reason for it. The View Model concept fits nicely into any MVC/MVP pattern. In this post I’ll explain how you can and should split your model into two or even three layers of models.

[1] An example is the Connecting to the Network-article. View, Model and presenter-code all in one file and the inner class DownloadWebpageTask have dependency to the Activity. Same mistake is done in Parsing XML Data. You could also question if they maybe should hint at 3rd-party libraries existing that greatly simplifies both XML-parsing and HTTP communication.

[2] In addition to Framework-libraries, smaller libraries or micro-frameworks such as Dagger, Flow, Mortar, Parceler, Icepick and RxAndroid are building blocks which paved the way for MVP-frameworks.

The different model types

The View Model

In this post I’m defining the View Model somewhat loosely as the model mirroring a concrete user interface. Martin Fowler names it the Presentation Model. It differs from the Business Model in that it has filtered, merged or converted data for presentation in one specific context. From this follows that you can have multiple View Models for one Business Model, or multiple Business Models represented in one View Model. In the Microsoft MVVM pattern it is tightly coupled with the Business Model through databinding. That is not a requirement in my definition and use, but it can certainly be implemented if that is a requirement in your project.

The Business Model

What I name the “Business Model” is the model representing an actual business entity. Think user, product , supplier or order in an online web shop system. In the Android code it contains strongly typed objects and business logic for working with these.

The Transport Model

I’m going to introduce a third model type I name “The Transport Model”. You will not always need it and even if you do you will not always have it as a separate class. The Transport Model is the model representing the business model in transport. If you have a REST service, it will be the class representing the JSON-data. I’ve found it useful to separate this as a standalone class as it allows me to unit test the parsing easily as well as allowing for switching the transport protocol with as little friction as possible. The pattern works very will with parser libraries such as Gson or Jackson.

Example

I’m going to paint a scenario here with for a Movie Database app (heard it before?). The App fetches a list of movies with metadata from a JSON REST service and displays the list of movies in the app. Here is the JSON data:

Show code
{
    "v" : 1,
    "data" : [{
            "released" : "2011-08-04",
            "title" : "Hell on Wheels",
            "category" : "TV series",
            "id" : "tt1699748",
            "stars" : "Anson Mount, Colm Meaney",
            "image" : [
                "http://ia.media-imdb.com/images/M/MV5BMTQ5NTE5NTYzMF5BMl5BanBnXkFtZTgwOTc4OTY0MzE@._V1_.jpg",
                1297,
                1404
            ]
        },
        {
            "released" : "2014-04-02",
            "title" : "Hello Ladies: The Movie",
            "category" : "TV movie",
            "id" : "tt3762944",
            "stars" : "Stephen Merchant, Christine Woods",
            "image" : [
                "http://ia.media-imdb.com/images/M/MV5BMTQ5MjYxMjkwOV5BMl5BanBnXkFtZTgwODE3MjY0MzE@._V1_.jpg",
                1012,
                1500
            ]
        }
    ]
}

So this with this JSON blob i want to illustrate a typical issue: The data itself is wrapped in a somewhat unnecessary object (with the version identifier). You don’t want this and other unnecessary data to bleed into your app business model. Also you want the date, image and list of stars as strongly typed data objects – so you create a set of Transport Models separate from the Business Model:

Show code
public class MovieJsonModel {
    public String released;
    public String title;
    public String category;
    public String id;
    public String stars;
    public ArrayList<Object> image;
}

public class MovieListJsonModel {
    String v; // Version
    public ArrayList<MovieJsonModel> data;
}

The classes above can be directly parsed from the JSON feed using a one-liner with Gson:

MovieListJsonModel transportModel = new GsonBuilder().fromJson(jsonString, MovieListJsonModel.class);

The business model would look somewhat different:

Show code
public class MovieModel {
    public String title;
    public String id;
    public Uri imageUri;
    public Date releaseDate;
    public ArrayList<String> stars;

    // Just illustrating that the model can and should contain business logic, 
    // not only data.
    public boolean isReleased() {
        return releaseDate != null && releaseDate.before(new Date());
    }
}

(To create a list of this simply instantiate an ArrayList<MovieModel>.)

So here you see that many of the String-fields in the Transport Model have gotten a strong typed counterpart. The Date, The image URI and the comma separated list of stars is actually an ArrayList. I have not included the conversion code here but I’d advice to put it in the Transport Model class. This way you will not introduce a dependency from the Business Model to the transport protocol.

Finally an example of how a View Model could look:

Show code
public class MovieViewModel {
    public String title;
    public String releaseDate;
    public Bitmap image;
    public int backgroundColor;
    public boolean isSelected;
}

Here you can see that String-representation of the Date again. We want to control how a Java Date object is presented, and we do that when we create the View Model. Note that this also allows us to unit test the presentation text. Furthermore we have a Bitmap object for the image which earlier was represented with a URL. This of course requires us to actually load the image. I’m not so determined on where to place this loading code – either in the View Model or in the Business Model.

Finally there may be fields exclusively for the view, such as the backgroundColor here, or state properties such as isSelected. You may also have data from other Business Models, but I’ve not included that in this example.

Persisting the state

One of the key benefits of having a specialized View Model is when you’re persisting the model for state preservation. You probably know that you need to do this in case the system kills the process or if you rotate the device. The Parceler framework makes this simple, but we do need to make some adjustments to the simplified example above to accommodate for the non-parcelable Bitmap object:

Show code
@Parcel
public class MovieViewModel {
    public String title;
    public String releaseDate;
    @Transient public Bitmap image; 
    public int backgroundColor;
    public boolean isSelected;

    // Reference properties:
    public String imageUrl;  

    // A service client for downloading images asynchronously w/Rx
    private ImageService mImageService; 
}

Now I’ve included the image URL as a String as well (Uri is not parcelable either). The code to load the image could be like this, using RxAndroid for callbacks (could also be implemented as a standard callback):

Show code
    public Observable<Void> loadDataAsync() {
        if (image != null) return Observable.from(new Void[0]);
        else {
            Observable<Bitmap> imageObs = mImageService.loadImageAsync(movieModel.imageUri);
            Observable<Void> doneObs = imageObs
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .map(new Func1<Bitmap, Void>() {
                    @Override
                    public Void call(Bitmap bitmap) {
                        image = bitmap;
                        return null;
                    }
                });
            return doneObs;
        }
    }

The View, Fragment or Activity would then ensure the data is always loaded as illustrated in the onResume method here, while the onCreate ensure the View Model object is always instantiated:

Show code
   public class MovieActivity
   private MovieViewModel mViewModel;

   @Override
   public void onCreate() {
       super.onCreate(Bundle savedInstanceState);
       if (savedInstanceState != null) {
           mViewModel = (MovieViewModel) savedInstanceState.getParcelable("ViewModel");
       }
   }

   @Override 
   public void onSaveInstanceState(Bundle outState) {
        outState.putParcelable("ViewModel", mViewModel);
        super.onSaveInstanceState(outState);
   }

   @Override
   public void onResume() {
       super.onResume();
        if (mViewModel != null) {
            loadAndBindViewModel();
        }
        else {
            // Load the models asynchronously from the REST Service, 
            // then call loadAndBindViewModel()
        }
    }

    private void loadAndBindViewModel() {    
       mViewModel.loadDataAsync().subscribe(new Action1<Void>() {
            @Override
            public void call(Void dummy) {
                modelToUi();
            }
        });
    }

    private void modelToUi() {
        // Map all View Model properties to actual view controls
    }

There might be cases where you want to persist the Business Model as well (or instead), like if you’re doing local caching of the service data. In this case you probably do not want to store it in the Bundle attached to the Activity or Fragment though, but instead use Shared Preferences, Internal Storage or SqLite. I will not go into details about that in this post.

Summary

The “Model” term in MVP/MVC covers all types of Model representations I’ve touched upon here, but by sticking to the View Model representation you will separate the concerns of the transport, storage, business and presentation layer. Creating a separate View Model class will ensure you don’t leak view-related stuff into the business layer, or transport-related fields into the business or view layer. Persisting the ViewModel class in the Bundle using Parceler will ensure that you retain the state of the view even after device rotations or process terminations.

Android developer at VG


4 comments

  • Pavel Synek

    Could you please elaborate why is Mosby framework "not exactly leading you into a good architecture."?


  • Frode Nilsen

    Frode Nilsen

    Ah, I see that that sentence could be misunderstood. I ment the Android SDK framework, not Mosby. I have updated the blog post to reflect that.


  • Thor Even Tutturen

    Nice article! First time I have seen someone split every model in up to three different classes. And it sort of makes sense. You get a nice flexibility towards your API, and ViewModel classes which makes saving the ViewState a breeze. But what about overhead?

    Say you want to add a single attribute to your Movie object, i.e. "rating". A walk in the park, right? You would first change your MovieJsonModel to match the attribute in your API. Then you would change your MovieModel. And don't forget to change the transformation between those two classes as well. And lastly, you would implement the "rating" in your MovieViewModel. It seems to me like some kind of tradeoff. Don't get me wrong - I like the idea here, just think one should bear in mind the pros and cons.

    Another thing, and this might be a little off-topic. From the business model to the view model in this example, you went from having the image as an URI to a Bitmap, and you used Rx to do the loading. Was this a just proof of concept (in terms of having a pratical ViewModel), or a way things should be done? I am genuinely curious.

    Is the Rx loading bound to the activity lifecycle in this example? I'm new to reactive programming in Android.

    What is the purpose of storing actual bitmaps in the class, and not use a third party library to load them on the fly? Licensing issues? Performance? Can it lead to memory issues (i.e. in a list of hundreds of instantiated objects)?

    It was a nice article, hope you write some more in the future. Maybe about the other parts of MVP?


  • Frode Nilsen

    Good question Thor Even. The questions about overhead is something I ask myself all the time as well. Just as often as this approach, I use the more pragmatic approach with a combined transport- and business model where I decorate the properties with serialization-related annotations where needed. I don't think that a project needs to have only one approach - decide from entity to entity (but others may disagree).

    As for the Rx-loading of the bitmap. First of all: The use of Rx is not needed at all here. It it isn't bound to the Activity lifecycle so it doesn't really at anything to a plain callback (it's just that Rx is in all blog post nowadays so I felt I had to include it for buzzword points (almost not kidding ;))

    The purpose of storing actual bitmaps in the model could be that you are using the models as in-memory-cache for a ListView adapter or something. Scenario: Your view goes out of sight, unbinds, but you keep the model with the Bitmap until next time the view comes into the screen and rebind. IF you are using a third party library like Ion or Picasso, which have caching built in themselves, you may not need to store the Bitmap objects in the model. It may even be an anti-pattern.

    Thanks for the feedback!


Leave your comment