The author of this article is Hristo Borisov, an Android developer at Paysafe. He is responsible for the loyalty program of the company’s mobile apps. For him, programming is like climbing a high mountain, covered in clouds – you have no idea what is above them – it could be a gentle slope or the Matterhorn. But when you get to the top you have learned a lot of new things and you are ready to use them on your next bigger adventure.
In recent years mobile solutions are rapidly growing. More and more desktop users switch to mobile and the latest studies show that the share of mobile traffic on the web even surpasses the desktop traffic (53% mobile against 47% desktop in 2020)! With 5G becoming more and more reality we can make an assumption that the mobile will probably become the new standard. That’s why here at Paysafe we take mobile development very seriously.
Paysafe is a company that provides a wide range of secure payment solutions for businesses of all sizes and end customers from all around the world. Part of our portfolio is two digital wallets – Skrill and NETELLER. Available in over 170 countries and 18 languages and with over 100 payment options and 40 currencies they give the end customers a simple and secure way to upload, transfer and withdraw funds all through the Paysafe ecosystem. Buying and selling cryptocurrencies, issuing a prepaid card to withdraw cash or make payments, real-time notifications for your transactions and international money transfers are also part of the supported features. Besides web versions of both wallets, there are also native Android and iOS mobile applications available to download from Google Play and the App Store.
The mobile applications development process
Currently, at Paysafe, we have around 30 mobile engineers (both iOS and Android) who are working on our mobile digital wallets Skrill and NETELLER. The engineers are separated into several teams consisting of 2 or 3 people for each platform and every team is responsible for the development and the support of their own set of features. Each team is also supported by a UI/UX designer, a product manager, and an engineering manager (which sometimes may be shared between several teams) and work closely with our backend teams.
We use the agile-scrum methodology with 2 weeks sprint and at the end of each sprint, we release new app versions in Google Play and the App Store. Usually, we use a phased roll-out approach for our releases – at the beginning we distribute the new version of the apps to a small percentage of our users and later we increase that percentage when we are sure everything is running smoothly. This way if something goes wrong with the new version we can take measures to stop its distribution at an early stage.
In order to know if something is not working as expected on the mobile apps we use several tools:
- Firebase Crashlytics – realtime crash reporting tool which detects crashes and non-fatal errors on the mobile clients
- Splunk and AppDynamics – application performance monitoring tools which observe the requests to our servers, monitor the state of the backend endpoints, and collect performance metrics
In case we find something that is not working correctly but we don’t want to stop the release of the whole app we use feature flags configured in Firebase Remote Config. This powerful technique allows us to modify application behavior without changing and republishing the code and with several clicks, we can enable or disable broken parts of the app.
Some other things that we use from the Firebase tech stack are the analytic tools to better track the engagement and the retention of the users and the Cloud Messaging solution which we use for sending push notifications.
Previously when I talked about team structure did you notice that I haven’t mentioned anything about quality assurance engineers (QAs)? Well, that’s because we don’t have any QAs in our teams! This doesn’t mean we don’t test our products prior to the release. Just the opposite – because there are no dedicated people responsible for the testing of the apps we use a strict set of testing techniques that minimizes the number of bugs released on production. Every change in our codebase should pass a strict code review process, should have all the business logic covered by unit tests, and the UI behavior should be covered by automated UI tests. At the end of each sprint, we also perform manual regression testing of the apps in order to catch any corner-case bugs which somehow found a way to pass through the unit and UI tests.
We use Bamboo as continuous integration and deployment tool to ease the process of app building, testing, and deployment and we use SonarQube for static code analysis. We also use Git for version control and we choose Gitflow as our branching strategy.
Android applications development at Paysafe
Skrill and NETELLER are applications full of features. New features are being added regularly and old features require continuous support and improvements. That’s why it is important for us to maintain a strong and robust codebase. In order to do that we have chosen MVP as the go-to architecture of Paysafe’s Android applications. MVP stands for Model-View-Presenter and is one of the most popular patterns used for Android development. It allows separation of business logic from the UI which makes unit testing easier. Here are the main components and functions:
- Model (or Data layer) – responsible for CRUD operations on the application data. It should not contain any business logic. Communicates only with the presenter. It is comprised of three main components – Services, Data Access Objects (DAOs), and Repositories. Its idea is to serve as an abstraction of the way we handle application data. The data layer provides a reactive interface to its clients and we use RxJava/RxKotlin/2 for this purpose.
- View – contains only presentational logic (setting colors, fonts, animations). It forwards all input events to its presenter for handling and it communicates only with it. We try to keep our views as thin as possible. Their task is to receive and follow the instructions, given to them by the presenter. We use Android Data Binding to simplify the initialization and the updates of the View.
- Presenter – contains the business logic of the app. Handles events from the View and updates the View and makes changes to the Model. The presenter is aware of the lifecycle of the view and issues updates only when the view is ready to receive them. A presenter should never hold any references to widgets or other UI elements so that it can easily be covered by unit tests.
- Service – it is used to perform network requests to our backend APIs. We use Retrofit HTTP client for this purpose along with Moshi and Gson libraries which take care of the serialization/deserialization part.
- DAO – used to encapsulate database logic for the app. We have two types of different databases – an in-memory database which is responsible for storing commonly used cached data across the app and a persistent database that is responsible for storing user accounts on the device. For the in-memory database, we use Room and for the persistent database, we use SQLiteOpenHelper.
- Repository – it is the highest level of abstraction in the Model layer. Its idea is to coordinate one or more Service and DAO instances. Presenters use directly the repositories and they don’t care where the data came from.
For multi-layered and feature-rich Android applications like ours, managing dependencies between different components could get pretty tricky. That’s why we use Dagger 2 as a dependency injection framework which does the work for us.
We also use some other 3rd party tools like webtranslateit which helps us with the multi-language support of the apps, Jumio SDK for identity verification of the users, Espresso for writing UI tests, and Mockito along with jUnit for creating unit tests.
Over the years we started to experience some flaws of the MVP architecture though. Often our networking response models (which were provided by the repositories) were full of data that was not ready to display on a mobile device screen. This was causing a lot of logic in our presenter classes to take care of mapping the received network data to UI data suitable for the view. Adding this logic into the presenters made their code really long and combined with all the other business logic there it was really hard to maintain and test. That’s why we came up with the idea to extract this mapping logic into mappers:
Now our presenters are much cleaner and easier to maintain and the mapping logic could be easily tested.
Another struggle that we had over the years was the support of two different digital wallets applications with two different code bases that have almost identical features. Whenever we released a new feature for one of the applications, we wanted this feature to be available for the other as well. Because this required a lot of code duplication and additional resources to be available we often skipped this step and as a result one of the wallet applications (NETELLER) felled far behind the other(Skrill). That’s why we finally decided we don’t want to support two codebases anymore and we will have only one – the one which had more features in it – and white label it and abandon the other. White labeling basically means that we take the app with more features and rebrand it to look like the other. This is easily achievable by using the build-in mechanisms provided by the Android SDK – product flavors:
We define two product flavors – skrill and neteller – and whenever we build one of them it will use the resources found in its corresponding folder:
So when we build the skrill product flavor, the branding_logo will look like this in the app:
And when we build the neteller product flavor, the branding_logo will look like this:
For more information about product flavors, you can check here.
One of the most interesting parts of Android development at Paysafe is to always follow the latest trends in the technology in order to create a product with the best quality and security standards, great stability, easy to use, and most importantly – beneficial for the users.
One of the challenges that we face every day is how to make things work and looks good on the wide variety of devices, screen sizes, languages, and customers from different countries. Some of the challenges are easily solved by properly following the best practices for building flexible and dynamic layouts (we make strong use of ConstraintLayout), but others depend on regulatory and business cases which should be handled very carefully. For example, it is mandatory to show the US customers checkboxes when they have to accept some sort of terms and conditions, but for customers from other countries, it is not. The starting point for the prepaid card functionality should be only visible to users which are from countries where we have support for prepaid card services. Deposit functionality should not be available for users that have deposit restrictions. We have a ton of corner cases in our apps that can be reached by the end-users based on a combination of their properties, capabilities, and restrictions and we should make sure we handle all of them properly.
Another interesting challenge in day-to-day work in the mobile teams is building a usable and user-friendly user interface with great user experience. When we add new functionalities or decide to give a fresh look to old ones, we conduct a series of brainstorming sessions with the UI/UX designers, product owners, and other stakeholders. The result from the sessions is several ideas and prototypes which we put into test with real users in a simulated environment. After we get the feedback from the users and the final design prototypes are ready, we start with the real implementation of the feature.
And probably the most important aspect of our products is how to make them highly secure. We use a set of tools that detects suspicious user activity and take measures to prevent abuses. We have a lot of authentication options – password, biometric, PIN, SMS and if we detect unusual login activity, we trigger our tools for device profiling or Captchas.
Some of the latest innovations that we introduced recently in our Android apps are:
- Kotlin support – we write all-new features entirely on Kotlin and whenever we have a chance we rewrite some of the legacy Java code on Kotlin too.
- 3D Secure 2.0 support – we created our own 3DS 2.0 library that handles all online card payments in our app. 3DS 2.0 is an authentication protocol that aims to reduce fraud, enhance security and introduce more frictionless flow in online card payments solutions. It is a big upgrade compared to the 1.0 version of the protocol which has a non-user-friendly UI and a lot of security issues.
- Integration with BrowserStack is a platform that allows us to run automated UI tests on a wide variety of real devices without the need to support internal infrastructure for this.
- Regular Android community meetings – every two weeks we have internal meetings where all the Android developers take part and discuss things to improve, legacy code optimization techniques, and the newest tools and frameworks for Android development.
Fintech mobile development is an interesting and challenging industry with strong focus on innovation and security. The rapid expansion of mobile technologies and the constantly changing business environment makes app development even more fun.
If you are interested in mobile development and fintech solutions, join Paysafe and we can make apps happen together.