React Native at Traveloka: Bridging the Past and the Future (Part 2)
Continuing from React-native at Traveloka: Bridging Past and Future (Part 1)
New Stuffs on the Block
Here comes Mono!
When we stopped our cross-platform product sharing between native and apps, there was still a need to share our works albeit on a smaller scale. Considering the many hardships in sharing a full product, we proposed a solution called Mono to only share components that have a product’s context needed in multiple apps or multiple platforms with the caveat of low reusability outside the intended use cases (in contrast to district-core created for components that have no product’s context). Another distinction of Mono from district-core, is the deliberate omission of centralized governance by one central team but instead, co-maintained distributively by any team that stores their code there. In the actual implementation, our Insurance product team, for example, could share their components between the apps and the web platforms. Then, our Customer Experience team could also share their Help Center building block from our Traveloka apps into our B2B apps and therefore, practically expanding their reach to all apps in a simple way. So far, we are still not sure where to store the Mono library given its still modest, but nevertheless, rising adoption. So for now, it remains stored in our main Traveloka apps’ RN monorepo project.
SmartKey: Simple, Useful, But Often Overlooked
When our Quality Engineer asked why our RN pages had no testID — a common components-referral variable identifier in native development — visible to them in Appium (mobile app automation framework), we then realized that adding a “compatible” ID in our RN development is uncommon.
Rather than adding those needed testIDs indiscriminately in all our components, we tried to rigorously reassess our situation. Could we add testID that is only visible in the staging bundle so there’s no performance issue? Could we even delegate or automate the repetitively necessary task of thinking of a name for and adding every testID manually? It turned out that both are possible! Hence we called our solution SmartKey because it is smart enough to address both of those questions. There will be a follow-up article on the simplicity and utility of SmartKey.
Final Boss Vibe, Bundle Splitting, and Independent CodePush
In Traveloka, we’re accustomed to a fixed software release schedule. So, when we utilized CodePush, a cloud service that lets RN developers “push” app updates directly to users’ mobile devices, with the self-imposed restriction, however, of disabling arbitrary deployment, which is actually the essence of the service but also its achilles’ heel as such a powerful tool together with a loose privilege inherited by anyone in the RN team can cause chaotic release schedule or widespread problematic updates, I often receive criticism from my external colleagues as to why we even bothered using it.
We ended up playing safe and shelved the idea that CodePush is the tool one can employ freely without any unforeseen consequences. With our complex hybrid solution, it is pretty easy to err. So, for some time, we advocate CodePush as the tool that can save the day in the event of a catastrophic official release due to crashing code or wrong access to a feature under development. But, we promised our stakeholders that we will research a way to make it safer.
Furthermore, although we haven’t faced a significant performance degradation in our hybrid solution yet, our bundle size keeps getting bigger with an unforeseeable tipping point. So, splitting our bundle becomes a more logical necessity. Basically, we split our singular bundle into several product bundles and a single shared bundle called vendor bundle. However, as our codebase grows over time, it will become progressively harder to split as such compartmentalization will essentially change how we run RN, how we will code in the repository, how we organize the code to ensure the absence of circular dependencies between bundles and the balance between flexible development (each product has its own CodePushable copy of same code) or lean development (code shared in vendor bundle but not CodePushable yet). By splitting into individual product-specific bundles, we can also consequently prevent simultaneous damaging deployment to all products caused by a single bundle and enable CodePush to deploy release per product because the code is already independent.
To make the split bundle concept simple, we have a vendor bundle containing all dependencies and shared components. Each product’s code must be stored in its own bundle such as an insurance.android.bundle or an insurance.ios.bundle. When we start the RN instance, it will load the vendor bundle first followed by the selected product bundle. Of course, We made a couple of modifications to load multiple bundles in the RN instance. When we want to push a change in a certain product, we only need to deploy its product bundle through CodePush that will ensure any update to the product bundle will not break other products and negate any need to coordinate and sync CodePush release schedules between the product teams.
We delivered our first split bundle to production in February 2020 and deployed the first independent CodePush in June 2020. All flagship products have their own bundle now with the exception of some single-page products, whose miniscule size of less than 10KB each isn’t worth the effort. Although the project has been quite successful, we still have homeworks to do in regards to vendor bundle CodePush and build time improvement.
The hardship we endured with this project was equivalent to fighting a game’s final boss metaphorically as the hardest and most time-consuming obstacle to overcome in order to finish the game. It is super satisfying when we finally beat it. We will share a detailed article regarding this “game play” separately.
Interoperability
After bundle splitting, our next plan is to improve the interoperability between native platforms and RN. This might sound redundant as RN already provided such interchange capability. So, why even bother? During our early utilization of RN in our production, it never intended to replace native pages but to complement them instead. In hindsight, We didn’t have two versions of the same page in RN and native, both platforms are coexistent and solving different problems, but usually our product teams would prefer both RN & native to have equal capabilities in the component level. So, the best option for now is to reuse anything that exists in each other platform. It also slows down our app size growth rate unlike duplicating all the code.
Doing a lot of work that involves consistently moving stuff back and forth between platforms reminds us of a similar behavior in nature called Osmosis and Reverse Osmosis from which we decided to derive references to the project from to make it easier to understand (and remember).
Chronologically speaking, we started the research to render RN in native page after we did the opposite, to render native component in RN. We thought that rendering native components in RN would be much easier, which turned out to be harder to perform in our hybrid solution. Because the rendering of RN in native page mimics osmosis’ biological behavior of being easier to perform and require less energy compared to its counterpart; the reverse osmosis, we decided therefore, to name the process of rendering RN in native page as Osmosis and the opposite as Reverse Osmosis, both of which I will go into in the next two sections.
Osmosis, rendering react-native as component in native page
RN has been used in all our platforms. So, if we could move a RN building block into native platforms as a component, we could leverage RN as a sharing platform to potentially save a lot of resources in each platform from maintaining duplicate stuff and redirect all maintenance effort into a single place. It is already possible to share code properly between RN and web. This project aims to bring Android and iOS into the fold as well. Of course, a proper balancing is still needed as putting everything in RN is counterproductive to our principal goal of complementing our native platforms.
Aside from satisfying the sharing needs, cross product referencing is also now possible so that our RN engineers don’t need to code natively in order to share some components to be used by native product’s flow. An RN component can be used by native pages like a normal native component through an Osmosis container. With the initial purpose of just rendering simple components, we have added a lot of functionalities such as rendering multiple Osmosis containers on the same native page, which in turn can communicate with one another without much problems. It is even possible to render Reverse Osmosis components (which is essentially a native component rendered in RN) inside an Osmosis container that’s inside a native page!
Reverse Osmosis, rendering native component in react-native page
Question: Should we create the same stuff in the react-native?
Considering RN was integrated at the later stage of our application development journey, we could have easily answered yes to the above question if we were to do it the easy way by just duplicating it in RN (providing exact capabilities resulting in bigger code without much additional benefit to our users) and be done with it. There’s no fun in doing just that. So here’s two challenges:
- Components can be used as-is without concern of simultaneous usage by RN. In Traveloka, each product team maintains their own codebase. So, the fewer constraints (or dependencies) coming from RN’s needs, the better it will be.
- Components require less human intervention over their lifetime in RN. Therefore, any simple modification requires no update on our part, including UI update. However, the tricky part is in specifying the component’s size. When we use native components, we need to specify width and height variables in RN by either setting `flex:1` or hard code them. Otherwise, the component won’t be shown. It would be more powerful (and less error-prone) if our implementation can determine component’s size dynamically.
So with those challenges, we began our research that led to a project we called Reverse Osmosis. As the prefix implies, it has the opposite characteristics of Osmosis as being harder to do and requiring more effort in nature.
We will publish a follow-up article on the Osmosis and Reverse Osmosis projects including their public repositories later on.
Future
Flutter?
Given the frequency of this question being asked, I think it warrants our thoughts about Flutter and RN. When we started using RN, we understood that it is just a tool. A. Maslow wrote: “I suppose it is tempting, if the only tool you have is a hammer, to treat everything as if it were a nail”. We don’t want to end up using RN like that. RN is not an omnipotent solution to all of our problems. So, it’s not wise to turn native and RN against each other but to complement one another instead because each has its own merits. So, in lieu of zealously pursuing total conversion from native to RN, we implemented just enough RN to negate the need to fully replace our existing native development.
Likewise, the same mindset of complementing instead of substituting should be applicable here for Flutter. It should not be adopted with a mindset to replace its counterparts, but to add value to our products. Since our RN implementation already addressed the code or resource sharing capability between apps and platforms, we are still figuring out the utility of Flutter.
Kotlin-multiplatform
One of our colleagues suggested that we explore Kotlin Multiplatform. At first, we thought it was another hybrid approach to create a product. However, after further exploration, we actually learnt that it can complement RN’s shortcomings of sharing and running business logic much more efficiently while RN itself is exceptional at sharing UI-related assets like we did in the Osmosis project. Finally, a complete multiplatform solution that handles UI and business logic uncompromisingly.
Hybrid from React-native
Doing hybrid development from existing native platforms turns out to be tremendously hard because a lot of novel fixes (hacks) and additional adjustments are needed to be made to reconcile with the existing ecosystem. Each platform was built independently without any foresight, understandably with hindsight, for supporting seamless identical native modules creation down the road. If there is a chance, we are eager to try the opposite approach of doing hybrid development from the RN template. Theoretically, it will remove most of our current problems and any need of native development can be answered with Osmosis or a full fledged native development. Any API or contract is made similar in both native platforms with cross platform sharing as a priority. Beside doing hybrid from RN template, we are also interested in enrolling the web as a first-class citizen in our overall thought process in order to materialize a web-first approach in component behavior development. I’m not sure how good the improvement from such a change of direction will make, but like Einstein said: “Insanity is doing the same thing over and over again and expecting different results.” Hopefully there will be something positive that we will be able to learn and apply from.
Throw Everything Into The Pot
“Could we create something like this?”~asked by one of our senior leaders back in 2016.
Back then, it seemed impossible, or rather unachievable, to create what was asked in the illustration above due to the lack of technical ground work to make that idea into a reality. That question still lingers on my mind to date and I take it as a personal challenge. So far, you have glimpsed into some of our foremost and established works that are individually serving their respective purposes well, including those that we haven’t shared yet. But what will happen if we combine all those distinct improvements to fulfill a higher purpose given that solely working on our goals without supporting the business is not justifiable?
Although, most of our previous research projects have fulfilled their business requirements and have also shown favorable results in laying a solid technical foundation for a project that can answer the question above the previous paragraph, we still approach every new project cautiously (but optimistically), considering the high probability of failure due to mis- or overallocation of our resources with the (delusive) expectation of a good outcome. I’m not going to spill much but that ambitious project will revolve around dynamic development. Time will tell, but surely if when we succeed, it will eclipse our accomplishments in the Osmosis project or even in the District system! In the meantime, let your imagination run wild on the kind of dynamic development we will be making.
Coming up next:
- RN at Traveloka: Native UI Components
- RN at Traveloka: SmartKey, a Simple Solution for Automation Testing
- RN at Traveloka: Split Bundle, Enhancing RN Bundle
- RN at Traveloka: Independent Product CodePush
- RN at Traveloka: Osmosis Project Overview
- RN at Traveloka: Osmosis Project on Android & iOS
If React Native and Front-end Development in general spark excitement and curiosity within you, I invite you to check out our available engineering positions at our career page.