Kiwi

The Kiwi.com App Upgrade: Developing a Fast-Loading App with a Native Feel for both Android and iOS

In a few words: It’s been amazing. We think they are great developers and we learned a lot from them. They communicate efficiently with the stakeholders and provide helpful feedback and suggestions. Even though they are external contributors, we consider them as colleagues and part of the team.

Robin Cussol
Frontend developer at Kiwi.com
Kiwi

INTRODUCTION

<cyan>React Native<cyan> integration

The kiwi.com app helps organize your air travel in a simple and safe way. It offers the best prices for your airline tickets and guarantees to cover missed connections. The client had a mobile app for both platforms - iOS and Android - with totally separated codebases. Within these native applications, some screens were presented as a WebView. The goal of our Kiwi project was to switch those views into React Native using a single codebase. In other words, it was all about integrating React Native into two fully native production apps.

appstore
playstore

CHALLENGE

Issues <cyan>to solve<cyan>

We identified several problems; the major ones were as follows:

Loading React Native screens was slow

The user opened the app and when it was time to click a button to navigate to a React Native screen, the user had to wait for the JS to load. If the user navigated back and clicked again, they had to wait one more time.

Navigation

The app had problems integrating the native side of things with react-navigation. On Android the major problem was the back button interaction. On iOS, the swipe back gesture.

Packaging and distribution

  • Kiwi had a repository with the JS code that was being released to npmjs
  • A different repository was creating a library for Android consuming the npm package
  • A different repository was creating an iOS framework consuming the npm package
  • The problems with this approach were that the responsibilities were spread among 3 repositories and 3 owners.

The main problem here was keeping the React Native version in sync as we needed to update it in the 3 different repositories at the same time.
The client was also interested in having CodePush.

Reusing Native code

The client wanted to be able to reuse some existing Native code in React Native without having to re-implement it.

Kiwi app screens

APPROACH

<cyan>Techniques<cyan> to adopt

We decided to go for a brownfield approach and apply the following techniques for the following issues:

  • Loading React Native was slow
    Init the bridge in advance and only one time (share/reuse it)
  • Navigation
    Init the bridge in advance and only one time (share/reuse it)
  • Packaging and distribution
    Move everything to a single repo, automate releases (maven on Android) and generate a .framework on iOS. Add Codepush
  • Reusing Native code
    Write native modules that can use the dependency injection pattern

SOLUTION

The singleton pattern for <cyan>Android and iOS<cyan>

Kiwi app screens

Loading React Native screens

The solution for both platforms is written in a different way but the idea is the same: Using the singleton pattern.

iOS
We initialize the RCTBridge when the app starts (not when the user navigates to a React Native screen) and we create a singleton to share it whenever it is needed.


Android
We override the ReactNativeHost class and we configure anything related to our ReactInstanceManager there. In the Application level, that is, a class extending android.app.Application, we store there our reactNativeHost instance to be reused and shared. This way, it does not rely on a Context that can be destroyed (like if we were initiating the ReactInstanceManager in some Activity). We also make use of createReactContextInBackground().

Kiwi app screens

Navigation

iOS
On iOS, we needed to disable the native swipe back gesture if we were in a nested react-navigation screen (so it could be handled in the JavaScript side). On the other hand, if we were in the first React Native screen (not nested), the gesture needed to be enabled. Therefore, we created a native module so we could enable/disable native gesture from React Native. For such, we used the interactivePopGestureRecognizer.

Moreover, in order to go back from a React Native screen to a native one, we expose a method in our native module to close the current view controller (which is basically calling popViewControllerAnimated.

Android
The Android Activity that hosts the React Native View needs to implement DefaultHardwareBackBtnHandler from the react package. This is explained in detail in Integration with Existing Apps documentation.


However, we should also be able to navigate back from a React Native screen to a native one using any kind of button (like the back arrow on the Toolbar). For that, we wrote a Native Module for React Native to be able to go back to native whenever is necessary. This is as simple as calling the .finish() method of an Activity.

Packaging and distribution

The following would be triggered by GitlabCI in every merge to master so it was fully automatic.

iOS
Here, we had a script that would package everything into a .framework. These were the requirements from the iOS Native team, so they didn’t have to use CocoaPods and build React. We uploaded that .framework into Github Releases and the native team just had to copy and paste it into their project.

Moreover, in order to go back from a React Native screen to a native one, we expose a method in our native module to close the current view controller (which is basically calling popViewControllerAnimated.

Implementing code on iOS diagram

Android
On Android there were more steps:

For every native dependency, build the library and deploy it into a private maven repository For every new version of React Native, build it and deploy it to a private maven repository We wrote generic classes and the code responsible for the bridge, navigation, etc. and we made a library out of it, that was published to maven too. Here we were also building the JS code and attach it as an asset The native app was just consuming the library without having a direct dependency to react-native.

We also implemented CodePush so GitlabCI was able to release a new version whenever conditions matched.

Implementing code on Android diagram

OUTCOME & BENEFITS

<cyan>Faster for the users<cyan> and more developer friendly

  • React Native screens load without delay
  • Navigation works as expected
  • Native code can be reused in React Native
  • Everything in one repository and React Native version in sync.
  • CodePush is working which means not waiting for the App Store anymore when there are JavaScript changes

In short: Delivering a brownfield solution for both platforms which keeps a natural native feeling without having to rely on a WebView. Plus it can work offline!

contact

Let's work together!

Need help with React or React Native? Let us know!

I hereby agree for sending me by Callstack.io Sp. z o.o. with seat in Wrocław, by means of electronic communication to the e-mail address indicated by me, commercial information, within the meaning of the Act of 18 July 2002 on the provision of electronic services. For details see our Privacy Policy.