The app lets you search for and book cheap flights tailored just for you.

kiwi logo
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

The app helps organize your travel in a simpler and safer way by offering the best prices for your airline tickets and it also guarantees to cover missed connections

The client has a mobile app for both platforms - iOS and Android - with totally separated codebases. Within these native applications, some screens are presented as a WebView. The goal was to switch those views into React Native using a single codebase. Thus, integrating React Native into two fully native production apps.

cover cover cover

Several problems were presented. The major ones were:

  • 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 they 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 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. For example, translations were coming from Native, but they are handled in a different way from Android vs iOS.

cover cover cover

Brownfield techniques

  • Loading React Native was slow

    Init the bridge in advance and only one time (share/reuse it)

  • Navigation

    Write a native module to handle specific cases for Android and iOS

  • 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


Bridging on time and when necessary

Loading React Native screens was slow

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

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.

We override the ReactNativeHost class and we configure anything related to our ReactInstanceManager there. In the Application level, that is, a class extending, 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().


Packaging and distribution

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

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.cover

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.


Reusing Native code

The client wanted to be able to reuse some existing Native code in React Native without having to re-implement it. For example, translations were coming from Native, but they are handled in a different way from Android vs iOS.

Outcome and benefits

Faster for the users 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!

Let's work together

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