vxh.viet
1/10/2018 - 5:21 AM

Repository Pattern in MVP in Android

SOURCE: StackOverflow, Hannes Dorfmann

Overview about Repository pattern in MVP in Android

Summary

In short, Repository pattern in Android has a different meaning in comparison with the original Martin Fowler's proposal (see the linked Hannes Dorfmann for details).

The Repository pattern is really just a specific type of the Facade pattern. It is just an abstraction layer which sits between the true data sources (Models) and the consumers.

Let's say we have a system that deals with weather data. In this case I see nothing wrong with having three Modal classes:

  • WeatherHttp
  • WeatherDb
  • WeatherPrefs

These can all be members of your repository class injected through the constructor. All three of these are hidden from the consumers (UI) behind a single repository class:

  • WeatherRepository

which might have a single public method:

public void getWeatherForecasts(Callback<List<Forecast> forecastCallback);

or Rx:

public Observable<List<Forecast>> getWeatherForecasts();

Behind the scenes of that method you might well make an http call to fetch the latest data, a database call to save the majority of the details and a shared prefs edit to save the timestamp of the last time the data was fetched. The idea is you are free to change the implementation at any time and the consumers don't care.

The single most important thing about implementing a repository is you must not leak the implementation details. No network classes, database DAOs or SharedPreference keys should be exposed by the public API of the repository.

Detail Version

If you wants to load an User by his id:

interface UserRepository {
  User user(int userId);
}

The concrete implementation of that repository:

class UserDataRepository implements UserRepository {

  private UserDataStoreFactory userDataStoreFactory;

  @Override
  public User user(int userId) {
    UserDataStore userDataStore = userDataStoreFactory.create(userId);
    return userDataStore.get(userId);
  }
}

An UserDataStore is, as the name already suggests, responsible to load a User from a store like disk or a backend (cloud).

interface UserDataStore {
  User getUser(int userId);
}

class DiskUserDataStore implements UserDataStore {
  private DiskCache<Int, User> userCache;

  @Override
  public User getUser(int userId){
    return userCache.get(userId);
  }
}

class CloudUserDataStore implements UserDataStore {
  @Override
  public User getUser(int userId){
    // Make an HTTP request and return User.
  }
}

UserDataStoreFactory is the component that decides which UserDataStore to use when someone calls UserRepository.getUser(userId). For example the factory could check if DiskUserDataStore has a User and if the User object is expired then the factory returns CloudUserDataStore, otherwise DiskUserDataStore. The factory could also take into account if the smartphone has an active internet connection or not. I guess you get the point.

Extra: Using Repository as Singleton

For why the Android Blueprint using the Repository as a Singleton, see this post.