When you first learn how to code, the goal is just to get something working. To get going, you can make a simple layout, add in some data, respond to some user input, and throw all that into one giant class. This kind of coding is what Israel Ferrer Camacho calls the “Burrito Design Pattern.” When you make a burrito, you throw everything in, wrap it up, and enjoy! But what happens when your burrito maker throws in olives (which you’re not a fan of)? It’s pretty unreasonable to open the burrito again and pick through everything just to get them out.
Don’t let your code be a burrito. When building an app, you not only want to make it work now, but you want to make it work in the long run. When creating a project from the ground up, you want to keep in mind that features are likely to get added, dropped, evolved, etc. By creating a structure or procedure to writing code, you enable yourself to later customize individual components without the need to change how each component interacts, which can be a frequent cause of unintended bugs.
At a high level, defining an architecture for your app just means defining how objects interact with each other. Does your activity hold a reference to all your data objects? Does your EditText update the Database itself when its contents change? Do your views need to know about your Activity lifecycle?
One large goal of defining these relationships is to abstract out good coding behaviors. Some excellent guidelines for keeping code clean and testable are the S.O.L.I.D principles. In general, these principles aim to ensure your code is separated into a reasonable number of classes, reducing code complexity, and making your classes safer for extension. By implementing these good habits into interfaces and base classes (your architecture), each new feature added in code already has a determined structure.
Models and Views
The two components most accepted architectures have in common is the idea of Models and Views.
The Model represents the data you plan to display and interact with in your app. When you’re starting to build your model, it is often easiest to think about it from a very high level. Creating an interface with a method for each type of data needed can be an effective way to visualize what your Model will need to return. For example, if you’re making an app that displays a list of burrito ingredients, simply start with a method called getBurritoIngredients(). For some applications, your data may be as simple as a list of Strings. In other architecture patterns however, the data may be more complex, and your model may need to query some kind of data source like a database or an API. The benefit to starting with an interface defining your Model is that how the data was retrieved doesn’t matter to any components that are requesting the data; from their point of view, all they know is that they’re getting a list of burrito ingredients.
The View is simply responsible for updating the contents of your UI and accepting user input. This view should be able to accept information to display on the screen as well as have a reference to another component to delegate user input to. The view should not be responsible for formatting or massaging the data, but should be agnostic to the actual Model. One important note on the View is that, on Android, there is also the Android class ‘android.view.View.’ While an Android view could be a View in your architecture, they may not always align. Your View could also be an Activity, a Fragment, or some other abstraction you define.
While most architectures often share the general roles for Model/Entities and Views, where they start to diverge is in the bridge between them. One of the broadest patterns is MVC, or Model-View-Controller. Here, the model and view are as described above, and the Controller is the bridge between them. The below diagram from the Apple Developer Documentation shows the flow of information through these 3 components. The way the relationship between each component is defined below, the View and Model never communicate directly with each other. In other architectures this may not be the case. Defining these relationships between each component of your project is a vital step in implementing an established architecture.
Now with the basics covered, delving into some examples can be a great place to start. Tin Megali’s article “Model View Presenter (MVP) in Android” breaks down a simple MVP architecture step by step. It starts with some basic information including a diagram similar to the one above, and then walks through moving from high level plans to implementing the architecture on Android quickly through the use of a library. The article also has some tips on testing your implementation.
While MVC and MVP mostly cover updating the UI based on changes to data, there are a few other great examples that further break down the Model into additional layers. An excellent talk on taking MVP one step further is Israel Ferrer Camacho’s talk Android Development Like a Pro.
An important thing to keep in mind when developing an architecture for your project is that there is no one pattern that is the best. While one may be more explicit and structured, it may gain some complexity and lose flexibility as a result. In Richa Khandelwal’s talk Effective Android Architecture, she not only provides an excellent example of creating an architecture that fits your project’s needs, but also discusses both testing your pattern and how to handle growing the architecture to accommodate new tools and project needs.
Recently there have been many new tools available to Android developers that can make implementing an architecture much easier, like Data Binding and RxJava. Google has a sample repository on GitHub with several examples of different architectures. Each branch contains its own pattern and a README breaking down the choices they made for each pattern. In addition to showing off different architectures, there are also branches that cover the same architectures, but utilizing different frameworks for the backend.
The Long Term
Google’s sample repository also provides useful examples of how one can document their architecture. Making your choices explicit and transparent will not only help anyone new to your codebase get up to speed quickly, but it will also make adhering to the chosen architecture easier. If the motivations behind each choice is clear, it can make evolving the pattern to fit new needs a simpler process.
With these things in mind, you can now live life without the fear of a poorly made burrito.