The following is the first few sections of a chapter from Android's Architecture Components, plus headings for the remaining major sections, to give you an idea about the content of the chapter.
There are lots of possible ways that your app’s data can be stored. It could be local, remote, or both. The local copy could be in SQLite, XML files, JSON files, or other forms. The server could be using REST, GraphQL, gRPC, or something else.
And, on the whole, your UI should not care.
Your UI code has enough problems to deal with. Figuring out where the data comes from (to show the user) and where it goes (after getting input from the user) is more than it should have to bear.
That is where the repository pattern comes into play. In a nutshell: you design a single API that abstracts out all of the storage stuff. The repository implementation deals with all of the decision-making for where the data goes, what all has to get updated, what has to be refreshed from some remote source, and so on. The API just offers “give me X” and “here is an update to Y” and so on — the basic operations that the UI needs in order to function.
Therefore, in many respects, the repository pattern is not significantly different from any other abstraction that one might use. However, since data storage and retrieval is usually the reason why the app exists, it is important to give this pattern some thought.
A repository has a few key roles inside of your app.
First and foremost, this is where you isolate all of the details of the data storage, including all the esoteric rules that your app may require (e.g., we have to update the catalog after midnight in the server’s time zone).
The repository is responsible for:
The details of this will vary widely from app to app. Some of those details may be dictated by business requirements. Some of those details may be dictated by the server team. Some of those details might be under your control. As a result, there is no single recipe for implementing a repository — all books like this can do is explain the role, illustrate some implementations, and provide general guidance.
Your UI code probably will work best with a nice clean object model representing the data that the app needs to allow the user to see and manipulate.
However, it is quite likely that you will not get a clean object model from the data storage code:
ContentValues, which do not resemble business objects
Your UI code should not have to deal with any of that.
So, another part of the repository is to normalize the data from the data storage into clean model objects that the UI code can consume. So, the repository gets to convert those Retrofit POJOs and those Room POJOs (neither of which may resemble the other) into some consistent POJOs that form the object model that the rest of the app uses.
The UI code needs to be able to make generic requests for normalized data, with the repository handling all of the “dirty details” for making that happen.
At the same time, the UI code needs to have the patience to allow the repository to do its work. The responsiveness of the repository could range from microseconds to seconds, depending on a lot of environmental factors:
Here, “reactive” could mean RxJava, or possibly
LiveData. It could be
some form of event bus. It could be a callback system. What it has to be
is asynchronous — the API exposed by the repository has to force the UI
code to receive the data at some time in the future, not immediately.
You might be tempted to cut corners on the previous point, and have some
APIs exposed by the repository that return immediately. So long as those APIs
are set up to gracefully fail — such as returning
null if the data is not
cached in memory — that can be fine. However, in general, that is still not
a good idea, for one simple reason: things change. Today, your implementation
might support those real-time APIs. Tomorrow, your implementation might not,
for any number of reasons:
If you design a reactive API around a generalized object model, you should be able to change the implementation of the repository without requiring changes in the UI code. The only time that the UI code would change is if the data structure itself changes (e.g., new fields or objects added to the object model).
The preview of this section was traded for a bag of magic beans.
The preview of this section may contain nuts.
The preview of this section took that left turn at Albuquerque.