People deserve more from their money. More visibility, more control, more freedom. Since 2015, Revolut has been on a mission to deliver just that. With an arsenal of awesome products that span spending, saving, travel, transfers, credits, crypto, investing, exchanging, and more, our mobile super app has helped 25+ million customers send, spend, save, and invest smarter.
Let’s take a look behind the scenes of the Android development at Revolut and learn how we deliver our iconic apps to millions of customers all over the world.
Our Android team is one of the best in the industry — and we’re proud of it. It’s one of our driving forces: over a hundred excellent Android engineers work on our key products, such as Revolut for personal customers, Revolut Lite, Revolut Business, and Revolut < 18 for teens.
At Revolut, we believe that building diverse, lean teams of brilliant go-getters who break down barriers, is the key to winning. That’s why we have around 70 strong teams that split their work across two main streams: Platform and Product.
The Platform stream consists of Design System and Mobile Platform teams, while the Product stream includes various product teams, such as the Onboarding team, Payments team, Trading team, Crypto team, Credit team, and more. Each team is made up of Product Owners, Designers, iOS, Android, Backend, and Frontend engineers who work together to deliver exceptional results.
To better understand our teams and how they contribute to our success, let’s take a closer look at their roles and responsibilities.
Mobile Platform Team 👨🔧
Our Mobile Platform team is involved in a broad range of areas, such as building infrastructure, managing releases, maintaining architecture, and owning internal tools and libraries.
The team upgrades projects configuration, particularly improve performance of Grade Builds, maintains architecture, manages dependencies and keeps architecture sustainable, modernises CI/CD pipeline, facilitates mobile release process, creates internal tools and frameworks (Automated Analytics tracking tool, Automated UI tests framework, DI declaration framework etc.), enhance app performance, and increase security.
Design System Team 👨🎨
The Design System team is the heart of our design culture. It’s responsible for building and maintaining the central source of truth for the developers, our UIKit (library of reusable components and guidelines).
They create high quality UI components, keep the designs consistent across the apps, customize themes and styles, optimise views, layouts, and overall rendering performance. They constantly improve user experience, create unified visual interfaces, ensure smooth transitions, and make our UI/UX not just convenient but also enjoyable.
Product Teams 👩💻
The Product teams play a critical role in every step, stage and release of a product. They’re like small startups; self-led, with the freedom to innovate and develop new products and features.
Such a team is focused on building a particular product, gathering requirements, implementing new features, collecting customer feedback, and making improvements. Each of our products, including Credit, Crypto, Stays, Rewards and others, build our one app for all things money.
The development process requires participation of all team members to achieve their goal. Planning, visibility, and structure in the development process, are integral components that allow the team to remove stress and implement quality functionality that satisfies business needs on time.
The process starts with the definition of requirements and acceptance criteria, which are validated and challenged by the team. Once requirements are clear, the scoping phase can begin. Scoping phase is covering an analysis of the requirements, identifying potential solutions, and building development plans.
After the scoping phase, all team members should be aligned, so the development itself can start. That said, new ideas or challenges may appear during the development process, meaning the already defined solution may need to be re-visited..
We adhere to Agile philosophy, and each team has a choice which Agile project management systems to use, usually a Scrum and Kanban, or a mix.
Project level architecture
All our projects are multi-module, meaning we organise our features as separate modules. At the same time, we pay attention to granularity level, and tend not to separate features that have high cohesion and are linked to the same domain area, because we know that modules that are too fine-grained make the overhead a burden.
Not all our modules are feature modules. There are Core modules for common needs, like network, databases, or android components, UIKit modules for design elements, Models modules for basic data structure, and Test modules. We reuse common components and basic functionality between feature modules in one project, and share it across our projects. It’s also applicable to features: we have several feature modules that we share across different products.
In case of feature modules, we follow an Api/Implementation approach, so each feature is represented as a lightweight Api module and a heavy Implementation module.
According to this approach, an Implementation of a feature should depend only on Api modules. This way, heavy Implementation modules don’t depend on each other, and can be built in parallel, to speed up the build time. This approach also makes a feature implementation less fragile, since it depends only on stable Api modules.
App level architecture
All our products are implemented with Clean Architecture principles. Revolut Business and Revolut < 18 are based on our own Kompot framework. Kompot implements the MVVM design pattern, provides unidirectional data flow, simplifies navigation between different parts of an app, and supports multi-module from the box.
But it’s not the only one: Revolut for retail customers, the oldest and biggest app, follows a different approach based on MVP design pattern. That said, the new approach with Kompot can also be used.
The way we work with UI deserves special attention, and we mostly build our screens atop the RecyclerView.
Our UIKit defines a set of design components that can be used for building UI. Most of these components are represented in our code as RecyclerViewDelegates, and we build each screen as a list of these premade components (delegates). You can find more details about how we recycle our screens.
We also pay extra attention to UX. According to our guidelines, screens should support Loading/Error/Empty states. In addition, transition between states should be smooth, without any visual noises. We also tend to render all content that’s already loaded, and only use shimmer skeletons for parts that are on the way, instead of blocking the whole screen with old-fashioned progress bars.
More importantly, we strive to show content on a screen as fast as possible, regardless of poor internet connectivity.
Here, Reactive data flow enters the scene. According to this approach, a repository provides a stream of the Data, containing typed content, a flag indicating that we’re loading content from the network, and optional error.
The repository also defines logic regarding content retrieving and storing. Usually, the content we get from remote sources is stored in local cache, so when we subscribe to a Data stream, our repository emits cached content at first, so we can avoid loading, and show screen state immediately. We store information in memory and persistent storage, so our customers have access to their financial data from everywhere, even in offline mode.
Tech stack recap
We write in Kotlin and tend to use Kotlin coroutines and flows, but RxJava 2 will probably always be part of our heart and code base too.
We also use:
- Frameworks: RxJava 2, Coroutines (Flow), OkHttp, Retrofit, Dagger 2, Facebook Screenshot tests, Kaspresso UI tests, Android SDK
- Database: SQLite (Room OM)
- Infrastructures: Bitbucket, Jira, TeamCity
- Utilities: Charles, Postman
QA & Testing
At Revolut, we don’t have QA engineers, and we almost never test our apps manually. But it doesn’t mean that we don’t verify our app’s correctness, or don’t identify possible issues, before releasing it to the public. We have several types of tests that help us consistently verify functional behaviour. Unit tests, Integration tests, Screenshot tests, and automated UI tests, allow us to get quite comprehensive coverage.
Additionally, our release lifecycle consists of several stages. In the first Alpha phase, we test our new products and features internally to make sure that the product meets business requirements and functions correctly without major bugs.
The following phase is Beta, where we publish beta app versions, used by external beta users, to help us identify bugs, and performance or usability issues, at an early stage, before public release.
Last but not least, we cover all major changes and updates in the code by feature toggles, managed by our internal release management system, Vader. This allows us to roll out features gradually and safely, and turn the feature off in case there’s an error.
Code review & Git flow
Developers create a new branch for any functional changes, rather than committing directly onto their local main development branch. After updating, committing, and pushing changes to the corresponding branch, developers open a Pull Request to discuss and review the changes before they go to the development branch.
When a Pull Request is opened, or any changes to the remote tracked branch appear, we perform CI/CD pipelines, including project building, running Unit and a set of most fragile UI tests, executing Danger verifications.
Danger is a tool that helps automate common code review routines, allowing us to create and share automated messages that remind developers of common mistakes, so they don’t run into them time and time again.
The Pull Request is ready to merge only if the project is successfully built, Unit and UI tests are passed, there’s no error from the Danger side, and the Pull Request has at least two approvals. All our code, modules, classes and tests are assigned to particular code owners (teams), allowing us to find owners of the code easily while developing, and more importantly, to assign reviewers, whose code has been changed to the Pull Request.
Release management 🚝
Our Release train departs each Tuesday at 9:00 am. At this moment, the automated code freeze happens, where all changes from the development branch go to the release branch. By this time, all involved teams make sure everything is ready, and check corresponding feature toggles. Starting from this moment, only critical fixes can be added to the release branch.
When a freeze has happened, we prepare a Google Play bundle, running Unit, and all UI tests. If everything is fine, we publish the first beta release. (We go through these steps and upload beta releases every day but Sunday.) At this stage, we collect the first beta customer feedback, and check analytics and crash reports.
On the following Tuesday, if all goes well, we merge the release branch to the store branch, and perform a staged public rollout of the release version. By this time, our release train transforms into a rocket, delivering a new version of the app to millions of users around the world, and a new release train comes into the station.
This is the last step of the app release cycle, but not necessarily a feature release. Even when a rocket is far away in space, and our new version of the app already on customer devices, we can manage rollout and availability of the particular feature remotely, using feature toggles powered by Vader.
We analyse customer data to create personalised models and enhance the customer experience. For instance, we categorise events to personalise the main screen for each customer, and offer features based on their behaviour.
To protect our customers’ privacy, we do not use third-party analytics tools. Instead, we’ve built an in-house UI analytics solution that includes both a frontend framework and a server. The frontend framework tracks user interactions with the app out of the box, without requiring developers to write any ad-hoc code. The server captures batched events of screens appearing and buttons being clicked, allowing us to analyse the user journey and behaviour to create a better experience for our customers.
If you’re excited by our processes, how we work, and the opportunities you could get from working with us, why don’t you check out our Careers page and explore openings in our Android team?