Backend-Driven UI on Native iOS apps

Andrea Scuderi
Just Eat Takeaway-tech
9 min readNov 7, 2023

--

Challenges, opportunities, testing strategies, experiences and tools on the Help module in the iOS Just Eat app at Just Eat Takeaway.com

Photo by Sincerely Media on Unsplash

Mobile apps are the ultimate way companies can interact with their customers. They are a powerful tool that allow the business to react to the market and to experiment with new features. Apps help keep the customers engaged and build a deep connection with them.

At the beginning of the apps era, most apps were simple wrappers of websites, but the user experience and the performance were not good enough to keep the users engaged. Users prefer to interact with native apps, rather than web pages, where the integrations with features such as push notifications, location services, biometric authentication, and photo cameras are seamless.

To deliver new features to the users, though, the native apps should be updated frequently with new versions of software that need to be downloaded and installed on the users’ mobile devices.

The iOS App Store and Android Google Store allow users to update the apps easily, but the release process of a new app version is time-consuming and can be expressed in days rather than minutes. Sometimes, updating the content without waiting for a new app release can be crucial to the business and companies have developed their own solutions to overcome this challenge. The strategy to update the visual content without releasing new app versions is called Backend-Driven UI.

Although it reduces the time-to-market, this strategy introduces interesting challenges during the development. The biggest challenge is indeed quality assurance, since many different variations of UI implemented by the backend require extensive tests on multiple devices. In this article, I’ll explain why Backend-Driven UI is challenging and how we came up with a solution and tooling we use on our Help module on the iOS Just Eat mobile App.

What is Backend-Driven UI?

Usually, mobile apps on the App Store rely on APIs to fetch live data and present it on the user interface. There are multiple patterns to achieve this and each of them presents pros and cons.

In the following sections we’ll go through the most common patterns used in mobile apps:

  • Value binding pattern
  • Content binding pattern
  • Component binding pattern
  • Code binding pattern

We’ll use the UI in the following image as an example, and we’ll see how the different patterns can be applied to it.

UI Design

Value binding pattern

The most common way to update the UI is to retrieve a data model from the API. The data model, in most of the cases, is a JSON containing pure data value.

The UI is built by combining static content and data model coming from the backend. Localisations and copies are hard-coded in the App. This is acceptable when the app doesn’t need to be updated on the fly and when the old versions of the app already deployed doesn’t harm the business.

API Response:

{
"refundAmount": 10.0,
"refundCurrency": "£"
}

Localised strings (Static content):

"title": "Refund on the way";
"subtitle": "%@ has been refunded to your Just Eat Pay balance.\nYou can use it the next time you order with us.";
"okButton": "OK";

Content binding pattern

Having the API returning only the data model means that an app release is required to display new copies. A way to overcome this issue is to build the entire content, including copy and data from a model received by the backend.

In the case of an App localised with multiple languages, this pattern shifts the responsibility of localisation from the app to the backend.

In this case, the app keeps a static implementation of the UI and navigation and binds the retrieved content to the UI.

API Response:

{
"title": "Refund on the way",
"subtitle": "£10 has been refunded to your Just Eat Pay balance. You can use it the next time you order with us.",
"okButton": "OK",
}

Component binding pattern

The previous approach allows you to change content on the fly but doesn’t allow you to change the UI on the fly. Changing the UI on the fly could be important when experiments are required or in case content and navigation need to be dictated by the backend business logic. With component binding, the UI is built using the model received from the backend. The model, which represents a UI component and the data, allows the app to build the UI interface and present it. A component could be a particular view or a small block and it could also define the navigation.

The following image shows the structure of a component UI.

UI Design with components

The following JSON shows the model of the UI component. In this example the navigation logic to the next component is defined by nextAction, which contain the path of the next API call.

API Response:

{
"component": "RefundComponent",
"title": "I'm unhappy with my order",
"header": {
"title": "Refund on the way",
"image": "celebrate-1"
},
"body": [
"£10 has been refunded to your Just Eat Pay balance.",
"You can use it the next time you order with us."
],
"actions": [
{
"title": "OK",
"nextAction": "/CustomerSatisfactionComponent"
}
]
}

Code binding pattern

A more extreme pattern is to define the UI and the business logic outside of the app and leave to the app the logic to run the received code. In this pattern, we could include the presentation of web pages inside the app or the use of custom frameworks such as ReactNative.

Simplified Web Response:

<html>
<head>
<title>I'm unhappy with my order</title>
</head>
<body>
<h1>Refund on the way</h1>
<p>£10 has been refunded to your Just Eat Pay balance.</p>
<p>You can use it the next time you order with us.</p>
<button>OK</button>
</body>
</html>

Even though this seems a clever idea when the requirement is to support multiple platforms with the same code, the reality is that using such patterns will have multiple consequences in terms of UX, maintainability and software development.

Big companies like AirBnb started this journey and most of them had to reconsider this choice by rewriting the app from scratch to support new native features, to keep users engaged and to avoid ending up with three different codebases, one for iOS, one for Android and one with the custom framework of choice. (*)

Benefits and challenges of hybrid Content and Component Backend-Driven UI

The nature of our business requires updating the content on the fly and quickly having the possibility to change some parts of the navigation without compromising a good UX.

For this reasons, we adopt a hybrid approach between content and component binding and coding the app using Swift, the Apple native language. This choice allows us to have the same pattern on both iOS, Android and web and to share the same business logic.

Having explained what are our choices, let’s consider the challenges and the opportunities:

Benefits

  • Quick changes in production can be back-propagated to supported old versions of the App
  • Easier experimentations and A/B testing
  • UI is built by well-known and tested components
  • Navigation logic can change by releasing a new version of the backend

Challenges

UI, Navigation, Localisation and copy changes:

  • A change of content must be tested to verify that the UI represents the content correctly
  • Navigation variations need to be tested to ensure the overall experience is consistent

Development workflow:

  • Developers don’t have full knowledge of the flows
  • The whole team needs to keep track of all the features and flows
  • UI features need to be evaluated by both frontend and backend developers
  • During end-to-end tests, QA needs to understand how the replicate backend conditions

Development complexity:

  • Designers need to be aware of the limitations of the UI components
  • A UI change requires to be supported by both frontend and backend developers

Quality Assurance:

  • Requires coordination between QAs, frontend and backend developers
  • It’s difficult to test the overall experience because it’s harder to mock all the conditions implemented by the backend
  • Documentation and knowledge of different scenarios is required

Testing the hybrid content and Component Backend-Driven UI

To test the Help module, we have implemented many unit tests, some UI tests and fewer end-to-end tests. As stated in my previous article we choose to implement the module using the MVVM + FlowCoordinators architecture. Unit Tests and UI Tests are performed using a demo app showcasing the module’s functionalities, whilst end-to-end tests are performed using the consumer app reproducing real-world conditions.

Unit Tests

  • The role of the unit test is to ensure that all the component variations are bound correctly with the UI
  • Each different UI variation must be tested to check that the UI components are built correctly
  • Each flow coordinator must be tested to guarantee that the destinations are managed
  • Test the business logic

UI Tests

  • The role of UI tests in our case is to verify that all the common flows are tested using a mocked version of the backend
  • Testing the Navigation is quite critical in this context as navigation could easily break due to the introduction of different flows
  • The UI can be visually tested by developers or by producing a sequence of screenshots or test recordings.
  • The mocked responses to produce the tests need to be synchronised with the latest version of the backend.

End-to-End Tests

  • The role of end-to-end tests is to validate that a new flow behaves correctly
  • Smoke tests are needed if there are no major changes

Syncing UI Tests and Backed Mocks with Production

One of the biggest challenges is making sure the UI tests are reflecting as much as possible in the production environment. Note that UI tests need to run in isolation to avoid network delays and flakiness. This means that API response mocks are stored somewhere and used during the UI Tests. To avoid drifting from the production environment is crucial to ensure that the mocked backend responses are up-to-date.

Mocking iOS Backend with Shock

During UI Tests, we use our open-source tool called Shock. The tool allows you to start a local server to mock API calls. The client, before starting a test will register the requests and the responses previously obtained from the backend. Updating the mocked responses at the beginning was done manually, by copying the JSON Response for each request, but it was time-consuming and error-prone as an update is required every time there is a new API change. To overcome this issue we implemented a tool that automatically records the responses from the real backend and updates the mocked responses during the UI Tests.

You can find more details on it by looking at the Shock demo app.

The next image describes the architecture implemented for UI Tests.

Recording UI Tests mocked responses using Shock

Mocking Backend API with Mountebank

The recording of responses from a production backend introduces many challenges such as logging in, being in the condition required by the tests, availability and more. To overcome the challenges of dealing directly with a production or staging backend we got a protected environment backend called QA in which we define test conditions that are implemented by underlying mocked servers using Mountebank.

Backend developers are in charge of keeping the QA backend up to date with new test cases and the latest version of the API implemented in production.

Backend-Driven UI Mock

Conclusions

Backend-driven UI is a powerful strategy to quickly change the content and the navigation of apps. It allows the business to experiment and to quickly react to the market. It also allows the business to have a single source of truth for the content and the navigation. This approach has various challenges in terms of development and testing and requires an extra careful approach.

The risk of breaking something after a underestimated change in the backend is high. To avoid surprises in production, is crucial to have a robust test strategy and keep all the team members aligned.

If you go down this route, keeping the UI tests in sync with the production environment is also a challenge that arises. To overcome this challenge we implemented a tool that automatically records the responses from the real backend and updates the mocked responses during the UI tests.

Should you adopt it? It depends! There are no silver bullets!

It’s important to have a business case to justify the overhead of the development effort and the communication among team members.

Just Eat Takeaway.com is hiring! Want to come work with us? Apply today.

--

--