A.K.A less code is more code
The Android team at Babylon Health is dedicated to helping users manage their health and easily contact a doctor. With those aspirations and the number of features increasing, the team has grown rapidly from 10 to 40 devs in a year.
With that growth came a few challenges. Today, we will look at how we tackled making UI scalable and consistent while keeping it simple to onboard our new team members.
Before we start I should mention that I gave a talk at Londroid (Android meetup in London) about this topic. Check it out if you prefer this format.
A sample project is also available to get started: https://github.com/mazzonem/ComponentsExample
Little pieces of UI that can be easily re-used. A row, a card, a button are all components that put together creates your feature. Those are inspired by modern declarative UI framework and made to simplify the way we build UI.
Save yourself some time, avoid recreating layouts
Our experience is similar to a lot of mobile teams we met and been part of over the years. We usually start small, create an app and gradually add features to it. A few months (or years) later, we look back at our
layout folder and see a giant list of files. Most of the time, that is synonymous of poor re-use and ultimately makes it hard to support.
By the same token, we also realised that our app was often re-using the same UI elements ( A card often contained a title with a subtitle below and an image next to it) but each feature team would regularly create a new layout when they needed one.
Our first intuition was to save ourselves the trouble and find a way to re-use as much as we could from our UI.
Our first step before looking at the code was making sure to communicate about this finding. Most likely, if it looked like a problem for us, it would have been for other teams. Maybe some shared UI element already existed or if not it would be the first step to joining forces.
The first team interested in this was our design team. Re-using UI across an already established app ain’t easy, so we worked together on finding repeating patterns in our UI and documenting them. The goal was to reach consistency across the app and remove duplications. It has also been the occasion for our design team to update our design system.
We started by merging similar components and slowly working our way up to creating a complete component library. That hasn’t been without mistakes, so we compiled a list of principles to make sure each component would be re-usable.
With that being said, don’t worry if you’re a one-person team or don’t have the resources to do that. We heavily inspired ourselves from the Material Design Guidelines and you can do too.
Our components are following a series of small principles to be as reusable, flexible and intuitive as possible.
Do not hold logic
For maximum re-usability, components should set the data you give them into the views, no more, no less. It needs to be predictable so everyone can use it with no worry. For example, if your component formats the data, it will be much harder to re-use. Leave that aside and just keep your components as plain as possible.
Abstract design decisions
Developers using these components do not need to know the spacing rules, the width an icon should have or the text styles that need to be applied. Keep those details in your layout and let people only set the necessary data.
Here is how we create a simple card and what it looks like.
Keep it simple
Seems like we mentioned simplicity already but it is important. Components can be simple ( A card with a couple of text views and an image) but they could grow much larger. Imagine someone wants to add an image at the start, a button at the bottom, an image on the left, etc. You will start to have a god component able to do everything but no one will support it and some will be scared to use it.
Be pragmatic about it, try to keep it simple and if a component starts to become too complex, split it.
Be stateless if you can
Specifically for our implementation and performance purposes, we tried to re-use those layouts/views as much as possible. This is why our views should not hold any state. An easy way to know if you hold state is to check for a call to a get method on one of your views, this will probably be a sign that you rely on your view to remember part of your state. Avoid it.
Make it easy to use
Your users are the other members of the Android team, make it easy for them to learn how to use components and create new ones. Part of our process includes writing documentation, presenting the system to our team, and listing all the components under the same activity to easily play with them. But the most important for us was to find a way to do it so that it seemed natural for any Android developer.
What does it look like?
Let’s get to it and finally see some code.
We will be defining this simple and flexible card component that can display a title, an optional subtitle and/or a start icon.
The following CardItem component will be defined as shown below:
As you can see, a CardItem component is simple to instantiate, only needs the minimum information (title, subtitle and start image). The design decisions are entirely abstracted and will stay consistent across the app.
For a more complete example, you can also have a look at the sample project on Github.
Under the hood
The following is getting deeper into our solution, you could use any other UI framework and I believe the enunciated principles would still be useful.
RecyclerView as core
Our first concern before making our code simpler was to keep our UI performance in mind. Combining that to having a gentle learning curve, we decided to create our screens based on a RecyclerView. Each component would be a ViewHolder that can be easily added or removed from the screen.
With that framework, we also get the recycling and other niceties provided by the API, as well as the knowledge of our Android team too.
The Groupie library
We’ve been using the Groupie library to make creating our RecyclerViews simple and also avoid re-writing the tedious part of RecyclerViews for every screen. Groupie handles the adapter, updates (diffUtil) and simplifies the creation of a viewHolder.
Each component (ViewHolder) is called an Item that just needs a layout and a bind method that sets the data into the layout.
Here are a few advantages of using this:
DiffUtil doesn’t need to be complicated
DiffUtil can sometimes be a pain to set up correctly and is often an afterthought. Groupie embeds this logic in its adapter, making it easy to update your list.
Where is the black magic? Well, each component (or item) can be compared through multiple elements and animate accordingly.
- Are the items the same? Use the layout and optional ID.
- Is the content the same? Just uses the equals method coming for free with Kotlin data classes.
So just make sure your items are data classes and optionally provide a stable ID, you’ll be all set.
Decorators and spacing
An early question we asked ourselves was: “Should each component handle its margins?”. On that front, we realised quickly that it would make every component more complex and rigid. Also, RecyclerViews already have a solution for that called Decorators.
You can keep your components simple and create an ItemDecoration for your RecyclerView.
Do you need different layouts if you’re on tablet, portrait, landscape? You can make use of Layout Managers to get your component displayed differently without having to duplicate your layout files or components.
As we know, Android gives you many ways to handle a similar type of information. For example, you can pass text as a String or a resource ID. To give the choice and maximum flexibility in the inputs of our components, we created a simple interface called StringValue (the same can be applied for images, ImageValue for us). This interface allows creating multiple ways to assign a value to a TextView while keeping individual components simple.
While in the beginning, this project looked like a lot of work, we found out that it was actually simple to break down our UI into a limited and reusable set of UI elements (components). The biggest challenge for us was to define this set of principles and rules to make sure this would scale and work in many ways while keeping our design consistent.
Our designers, developers and QA are now more confident in building new features, knowing that they will be consistent with our app and built on solid foundations. Building this part of our design system also clarify the contract between each team and make everyone's job a bit easier. Thanks to them for contributing massively to this project.
Creating new features can also be done without touching XML layouts when all the components already exist, a big advantage for productivity.
Other advantages are visible when it comes the time to update our UI, we now have one source of truth, making tweaking and theming very easy.
Comment below if you’d like more details on this and clap for more. If you’re interested in diving in the code follow this link.
Does this sound interesting?
If you want to help build the future of healthcare, you can check our open roles here.