Past a certain point of project size and complexity, the solving of everyday problems of app development, like visuals and logic, become secondary to the real challenge of scalable software; structure.
Every coder knows the feeling of dread as, after weeks of sticking new features onto a codebase, there comes a creeping feeling of a massive deck of cards, needing only a slight breeze of change to shake it to its foundation.
The depth of the foundation determines the height of the wall
The above is an old Japanese proverb, and couldn’t apply better to software development. The more time you spend planning and designing the structure of your project, the further you’ll be able to take it before it turns into a dreaded mess of dependencies and mixed responsibilities.
Architecture is an often discussed topic with my team at Shape, with each new consultancy project a chance to improve upon our designs. Recently we’ve also been driving to find an architecture pattern to use in both our Android and iOS projects, as we believe a lot of hard earned knowledge gets lost in translation between the two platforms. Its far from unified at this point, but with the adoption of similar styles and Reactive programming, it’s moving in that direction.
This article is written from the perspective of an Android dev, and while Android specific classes are mentioned, the pattern can still be applied to iOS.
This article was also shared on Medium.
Our Architecture; ViewModels and Components.
- Activities; Our Activities contain the minimum of logic possible. No menus, no layouts. They are simply containers for our ViewModels, whose only real responsibility is Activity to Activity transfers. An added bonus to this bare bones usage is the ease of transfering to a Single Activity Architecture. More on this later.
- ViewModels; ViewModels contain the core of our view logic, their role is to collect data from the component layer and, through Databinding, plug that data into the view. They have no need for the Context god object, and a much simplified lifecycle compared to Activities or dreaded Fragments.
- Views; With the adoption of Android Databinding our View class has been boiled down to a simple XML layout, with custom attribute binding adapters defined in View specific binding classes (ImageViewBinding etc.).
- Components; Components are tightly paired to each ViewModel, and contain the core business logic to manipulate the API data for it’s ViewModel. An example would be a ProfileComponent that pulls data from the UserApi and ProjectsApi to combine into a custom data class tailored for the ProfileViewModel to present.
- APIs; APIs wrap the backend’s endpoints, and as such provide access to subject specific data. They are also responsible for managing any other API calls needed to provide said data, like logging in before getting user info. The API layer is where all under-the-hood networking specific to the app is found, including retries, authentication/re-authentication, and caching logic.
- Caches; The Caches are simple data stores for each API. They encapsulate expiration logic, and any long lasting storage being written to disk (OAuth tokens and the like).
- Network Library; Insert your networking library of choice here For us it’s hands down Retrofit, especially now RxJava observables have their own adapter.
This style has been refined over half a dozen projects since I started at Shape, and is in continual improvement based on trial and error and knowledge accumulated along the way. I’d like to go into some of the why of our architecture, and detail some of the major points that have shaped our choices;
This was the driving force behind the adoption of MVVM, from the very beginning. Fragments and their lifecycles are some of the worst APIs in the Android framework has to offer in my experience. It’s all so complicated, and unnecessar complication is the bane of a developers existence.
DataBinding is Awesome
View manipulation is a long running chore of the Android developer. It started with peppering our code with findViewById, then once everyone realised the actual overhead of those calls everywhere, we moved to Android Annotations, Butterknife or similar for our View injection. While some of those solutions were an improvement (don’t get me started on AA…) they still required a lot of boilerplate, and didn’t give us any optimization of actually setting the data on those views. The wonderful Android Databinding library does just that. With Databinding you gain a reference to your ViewModel, or any other Java class, inside the XML layout, and with Binding adapters you can even perform complex manipulation of the data for presentation in a plug-and-play fashion.
Modularisation is Smart
Compile time is important, and having one monolithic codebase in a single app module will give you the worst compile times possible, as every small change will require the entire codebase to be recompiled. If you have yet to start modularising (is that a word?) your Android application’s code, please, please do so. The benefits are insane. With the layer based approach of our architecture splitting the code into individual modules just makes sense. Our current practice is to have 3 modules; the ubiquitous app module, a component and API business logic module, and a shared / data module that contains all JSON de-serialised data classes and utils classes like Loggers. A minor goal of the modular approach is to have Java only modules for the logic and shared code, as from a logical point of view the Android framework has no business being in those classes. This has yet to be achieved, with Retrofit and RxJava requiring Android framework classes, but it does help to keep it in mind.
Caching is a Must Have
No one likes spinners. In the day and age of skeleton loading screens and lazy loading content, having to vegetate in front of a loading screen is not acceptable. Caching API responses needs to happen, and our architecture pushes it up to being a central part of how our apps work. We’re still trying out different caching implementations, one of our developers advocates for returning the cached version of a call, then running the call anyway to update the cache and UI for example, whereas I prefer a timed approach to avoid the network overhead. Every approach has its benefits and drawbacks however, so never stop experimenting for the one that fits your usage best.
Reactive functional programming is changing the way developers code, especially in the mobile application space. Gone are the days of callback hell and listeners in every class. We’re pulling more and more RxJava into our projects, and the benefits are massive. Classes of a hundred lines are stripped down to just over a dozen. The excitement in the power of Rx was palpable in the office when we first started using it, and while I’m no fan of marble charts, I’m a big fan of Rx. For now our Reactive code is limited to data manipulation and API calls, but I doubt it’ll stay that way for long.
The Future: Single Activity Architecture
Certainly the most bleeding edge and controversial of the architecture styles I’m advocating for is that of a single activity for the entire application. While still in the experimental phase, it’s a style I and others fully intend to adopt sooner or later. I’ll repeat the fantastic Realm talk I linked above just to show we’re not the only lunatics thinking of this, and attempt to explain the reasoning behind such a rad idea;
- Activities are built to be islands with no dependency on other Activities or components. This causes problems with state, as none can be guaranteed at any point in the applications flow.
- Any information passed to an Activity must be serialised and relatively small.
- Editing or modifying the Activity backstack is a nigh on impossible nightmare.
Single Activity Architecture is another step towards unifying development styles between Android and iOS, and the benefits are pretty huge;
- ViewModels represent our “Screens”, not Activities. Our ViewModels are POJOs, so no serialization is needed.
- We control the backstack, we can go forward or back however and wherever we like.
- We build our state onto a single Activity, so we always know what we can expect and what we can’t.
Now obviously it’s not all sunshine and rainbows, the downside to SA architecture is that we have to handle what the framework would normally do for us. But I believe the benefits could outweigh the disadvantages, and I’m certainly game to try.
And that brings us up to date with our architectural journey. I hope that this article has been thought provoking if nothing else, and that it might prove useful for any devs out there looking for insight.
Feel free to recommend or share this, and be sure to leave a comment with your thoughts. Until next time, happy coding!