Looking for the perfect Android architecture II: General architecture of an app
Today, I’m going to talk about the architecture I use to apply on my apps. First, a few basic guidelines I always respect:
- This is intended for client apps attacking a REST api (or similar). This is not valid for games or certain stuff.
- All data should be persisted. If you respect your user then persist the data!
- The local database it’s not a database per se, it’s a cache.
- The real database is located on the server and we only have a part of it. Keep this always in mind.
- I would sometimes ignore the normal forms for simplicity and performace sake. It does not matter, remember, I’m just a cache, the good database with normalized data is on the server.
- The splash screens on Android apps are evil.
- The style guide is sacred. It only can be ignored to innovate and improve. I don’t know anyone capable to do this. Doing it differently does not mean innovation. Doing it like in iPhone it’s not innovation.
- My rules are mine and I skip then whenever I want.
General architecture by layers
For a couple of years I worked with Code Igniter, an excellent Framework MVC in PHP (yes, I have a past as PHP developer, I was young, I needed the money). I’m sorry, but I don’t think Android is MVC. This is like discussing the sex of the angels, there will be people agreeing with me and others not, so we will assume that our architecture is NOT based on MVC.
When talking about layers, I split the application between Frontend and Backend. Or as I use to call them: ui and sdk (or core).
In fact, in big projects I usually create the sdk in a library project and the ui in a main project. This helps a lot to separate these two layers and to distribute the work.
On the ui layer I got everything in charge of managing the interface: activities, fragments, views, adapters, etc. All of them, data consumer objects. It can be that something else is included here because of Android nature, even if they are not ui, like object Application, Notifications, Push, NFC handlers, etc.
On the other side, on the sdk layer (the one with the main part) I got the parts managing the data: beans, clients, ContentProviders and tasks (and some other things such as helpers).
The UI layer
We will talk deeply about this one with examples another time. By now, here go some overviews and rules I use to follow.
All the elements from this layer are included inside the ui package. So, we will have ui.activity, ui.fragment, ui.adapter, ui.view, etc.
All classes have as suffix what they are , for example, MainActivity, FooAdapter, etc.
Each element has a set of public constants, final and static that start with EXTRA with the name of the parameters that can be expected in the bundle provided to this class.
The Intents between activities and fragments do not send full objetcs (and even less arrays). They only send the object id. For example, if I have an activity (or fragment, in this case it’s the same) ProductListActivity with a list of products when selecting one I invoke the following activity ProductActivity I just only send the id and this second one invokes the content provider to return the record and to read it. If the data can change during the view by external elements I use a loader to show the details, and it receives notifications from content providers.
The adapters ALWAYS uses ViewHolder.
The SDK layer
This layer is the one in charge of data input and output to the server and its persistence. See below a graphic with the main elements:
A review on the packages I use to create the what’s included in each:
- bean: all the data objects. Maybe you call them DTO, domain or something similar. No problem. But I make no difference here. I try the beans to be as POJO as possible and I don’t make them getters or setters. They are simple structures with annotations for the JSON deserialization.
- client: These are the representations of the web services. For example, a LoginClinet class invokes the login service , manages the answers and returns it processed. I use a a structure from simple IntentService that invokes the web service that the client provides as parameters. We will discuss about this topic on another article. The advantage of the system is that you make easily a lot of clients. Here takes place the serialization/deserialization to JSON ( or what ever it’s needed).
- model: all the persisted data model, on the root I place the data contract, the set of constant that define tables, fields and entities of the content provider. In some cases, when the applications are quite big, I split the database contract and the one from the content provider. But please take into account that then the maintenance is complex and tedious.
- model.content: the content provider, following the structure explained in previous article with DespicableContentProvider and MinionContentProvider
- model.cursor: here I have one CursorHelper per entity. Basicaly, they are in charge of passing from cursor record to object and viceversa.
- model.db: contains DatabaseHelper and similar.
- service: the IntentService invoked by clients, and any other service that might be needed.
- task: these are atomics process that invoke clients and ContentProviders. For example, the typical task of UpdateProductsTask invokes GetProductsClient, makes a delete over the Content Provider of products and then a bulk insert. This process has an output showing if it has been successful or not.
The key is also in my library to accessing web services called Rarest. For more information, please check www.rarestandroid.com.
On the next article we will see a nice example showing the above explained.