Kiwi.com app is running on three main platforms — web, iOS and Android — and was written with different platform-specific technologies. We were curious about the possibilities of sharing code across multiple platforms and dug into React Native (and React Native Web). Read on to find out what we learnt on the way.
At Kiwi.com, we are trying to make travel better. Our service allows searching for travel connections (by airplanes, trains, buses) and combines data from more than 500 carriers. Many of these carriers do not usually collaborate with each other, which gives us the opportunity to find better possibilities and provide cheaper tickets. With the Kiwi.com guarantee, we also protect travelers in case of delays or cancellations.
Our motivation for cross-platform development
Whole maintenance and updates are quite hard to manage if you have different codebases, teams and technologies. We wanted to explore the possibilities of code sharing while keeping our eyes on modern technologies.
Many of the top tech companies (such as Facebook, Microsoft, Twitter, Uber and others) bet on React Native, and we wanted to ascertain whether this technology would enable us to deliver new features faster and streamline implementation of the same features on different platforms.
That was our main motivation for digging into React Native and React Native Web.
React Native Web is a third-party compatibility layer that brings React Native code to the web. Very simply said, React Native Web converts React Native elements to web elements (like
React Native in the production Kiwi.com app
The concept of sharing code across platforms is very tempting, but it’s not so easy if you already have an existing app written in native languages. That was our case and we had to prepare our existing native apps for proper React Native use.
Later, we launched the Hotels section of the Kiwi.com app, which is completely based on React Native. Before React Native, we provided Hotels in the app only through a web-view, which typically offers a worse user experience than the rest of the native app.
The React Native portion of our production app works well, and is quite visually consistent with the rest of the app — it’s difficult to notice that it’s not true native code. The React Native bundle is loaded before it’s needed, because we have to provide the same user experience as in the rest of the native app. If we would not load the React Native part in advance in the background, users would notice the difference, as it would take some time to load React Native for the first time.
And the best part is that we built this whole Hotels section as an open-source project — just take a look at the repository on GitHub!
More cross-platform, please!
Sharing code between two mobile platforms is pretty cool, but we didn’t want to stop there. What if we could also deploy the app on the web?
We wanted to experiment with web compatibility through React Native Web, as was mentioned above. For that purpose, we decided to create a new product called Margarita, which was designed from the beginning to support all 3 major platforms (iOS, Android, web) with one codebase.
This time we tried to use Expo, a zero-configuration tool for better developer experience. Many problems linked with using React Native are solved or simplified, including upgrade difficulties.
In Margarita, we shared approximately 80–90% of the code across 3 platforms. Some components have platform-specific templates, but most of them are similar.
We wanted to provide users with the same possibilities on the web and mobile platforms. Components like a calendar for range date picking are customized for both mobile gestures and web usage. It’s also quite cool that you can develop and test a majority of the app directly in your web browser instead of using real mobile devices or emulators.
Even with similar components across platforms, we took care of the usability of our apps. It’s important to create your own wrappers around some atomic components and customize them on the web.
For example, if you want to use images, you need to prepare a wrapper for Retina-ready resolutions on all platforms. Or, if you want to create a small touchable component on the web, you will have to add some buffer around the element to account for the imprecision of human fingers on mobile devices.
Basically, it’s a simple clone of the Kiwi.com app, which was divided into four main parts (search, booking, payments, and booking management), plus we created our own GraphQL proxy around the Tequila REST API.
If you plan to create your own travel company and you are looking for some inspiration, take a look at Margarita (disclaimer: not the whole flow is finished yet).
Cross-platform Orbit design system
As our products grew, we needed to internally improve how we build them. That’s why we created Orbit, our open-source design system focused on travel projects.
Orbit is a collection of UI components, design tokens (colors, typography, metrics, etc.) with wide possibilities of theming.
Even though we started with React-only components for the web, our plan with Orbit was always to support more platforms. With React Native, we’re making an important step to also support developers building mobile applications.
At first, our goal was to build one set of components that could be used on all three platforms — iOS, Android and web. We chose a similar dev stack as used for Margarita — React Native components, and tried porting them to the web with React Native Web.
However, after some extensive experiments, we’ve decided to remove React Native Web from our dev stack. For now, we will focus only on delivering React Native components for use on iOS and Android.
Specific issues connected with this decision will be described below in the section about the hard parts. In general, it would require too much effort to replace the whole behavior on the web part (which is currently finished) with a new React Native Web approach. We were limited by the need of not affecting Orbit users in their daily work, which would be very hard to achieve due to time constraints.
The hard parts
As you probably noticed, nothing is just black and white. There are a lot of technical challenges and tradeoffs you have to consider if you want to create an app for multiple platforms.
We were facing the following issues:
Lack of many CSS features
The lack of CSS features was probably the biggest issue for us in when enriching the Orbit design system for React Native Web. If you need a specific UI with unique behavior, this approach is probably not for you.
By default, React Native doesn’t support many CSS features. There are no CSS transitions, animations, you can’t use media queries, pseudo-selectors, cascades/inherited CSS properties (which doesn’t have to be wrong) and even some CSS properties are missing.
Of course, there are ways to solve those issues usually with JS-based solutions (see what Evan Bacon does), but it takes time and it’s not as easy as in CSS.
Android is the new Internet Explorer to React Native
Do you remember the “good old times” with Internet Explorer 6? Sometimes, Android brings those memories back to life with its several really annoying problems.
Fixing issues for this platform is definitely the least favorite work in our team — especially when using an ultra-slow Android emulator.
On Android, there are problems with absolute positioning, gesture events, modals, different components APIs, the developer experience, touchable elements out of the parent’s scope, bad support for shadows, and performance for animations, etc.
It’s difficult to debug an app on three platforms on hundreds of mobile devices and dozens of web browsers.
Not everybody uses the newest Chrome web browser as we do. Also, you have to take care of mobile browsers which are not the same as their desktop versions. These issues are solvable, but very time-consuming.
The more platforms you have, the more complex the testing is. And it’s not just about automatic testing. You have to test all platforms on your own during the development. Developer tools are fine on the web, but on mobile everything is more complicated. E2E testing is also not fully cross-platform, so normally you have to use more than one testing story for one task.
If you use React Native Web, there is a different approach to using CSS. There are a bunch of classes for every element, and by default, everything is rendered as a div element. However, the reason for using those classes as atomic rules is optimization. Current developer tools are not ready for bleeding-edge technology, but hopefully, they will be in the future.
In general, React Native works great for us. It is getting more mature, and even though there are some problems, it’s getting better and our apps built with it are increasingly stable.
We are now able to create apps for three platforms with only one codebase, and the learning curve of React Native is very easy for former React developers. But it’s necessary to consider the tradeoffs that come with developing an app for too many platforms.
React Native Web is fine for specific use cases. It worked perfectly for us in the Margarita project, but if you have to take care of design details and animations a lot as we do in Orbit, it’s not the easiest way. In the case of React Native Web, you can apply the 80/20 principle — you will spend 80% of the time on 20% of the functionality.
Sometimes it’s fine, but if you need to be a perfectionist, React Native Web requires too much effort to cover all the small details you can do on the web.
That’s why we decided to leave React Native Web in the Orbit project for now. Nonetheless, React Native Web and other multi-platform technology is still very interesting to our future approach.
Would you like to work with us at Kiwi.com? Check our open positions.