Visualized by Ilyas Fahreza

React Native at Traveloka: Bridging the Past and the Future (Part 1)

Junius Ang
Traveloka Engineering Blog
11 min readFeb 11, 2021

--

Editor’s Note:

In this two-part series, Junius will take us on a tour about React Native adoption, implementation, as well as implication to bridge native and non-native front-end developments through bespoke libraries or frameworks in order to harmonize their coexistence.

Junius Ang is a software engineer and a multiplatform infra lead overseeing and caretaking all matters related to React Native with his team.

The goal of this article is to share our updates since the last time we wrote our series on React Native (RN). So, before we publish more articles to explain our recent works and milestones, it would be great to catch up with what has happened in the last two years, followed by a glimpse into those recent works with the reasoning behind them, and what we have in mind for the future. Any detailed explanation on a specific work will be shared as a separate follow-up article, whose link will be periodically updated in Part 2.

2 Years Of District System

District System, Now Stand For the Test of Time

Multiapp or multiplatform development usually costs more than their single counterpart. Both initial cost and maintenance cost will keep rising without a proper way to share existing works across projects. In 2018, we introduced a concept called District System that has been working for us surprisingly well. In a nutshell, a District System consists of multiple independent district-apps connected to a single core repository called district-core. We build each app from the same template and put in place a strict rule of “no product context in the core library” in order to maximize reusability. In other words, district-core is a collection of codes that can solve multiple products’ use cases while decoupled from any product or implementation. If we never built the District System, it would have been quite impossible to have four people as a central team to effectively manage one RN hybrid, five RN apps, a codebase sharing between RN and web platforms while pushing our implementation forward.

As core libraries are used by almost all of our front-end interfaces, it doesn’t feel right to not explain a bit about district-ui. As our most successful library, It hosts all our standard components that adhere to our in-house design standards called Momentum Design Guidelines. We are pretty serious about standardization effort because for example, there is no way we could remember different ways to render the same button for six different apps. Therefore, we use district-ui as a foundation of all core libraries that come after to ensure all UI-related code follows the same standard. Hence, it is not uncommon for other libraries to have dependencies to district-ui, whether it is RN or React libraries. A tidbit worth mentioning is that the capable & collective minds of RN engineers, web engineers, as well as designers co-maintain the standard of district-ui library.

The District System is more or less working as we originally intended until today & there is not much we could improve conceptually.

The biggest test to the District System on compatibility is actually how easy we could share the work used for creating hybrid apps with a full RN app.

Almost all of our finished research on District System has a purpose to solve hybrid development problems and is strongly attuned to our Traveloka app’s native platform’s condition. The more work that we’re unable to easily share, the more we drift away from the core idea of the District System. So, we decided to abstract our work further, beyond the default expectation of the District System so that our solutions can be applicable not only internally within the District System and our heavily customized RN hybrid implementation, but also externally.

Stronger Love for Strong Typing!

A friend of ours once said, “No need to make everyone positive toward RN, just make them neutral.” One of the main features of RN is fast development iteration with fast refresh / hot reload, making it very attractive to many native engineers. Well, except ours as they still harbor hesitation about the “flexibility” of JavaScript language being regarded, understandably, as a double-edged blade and to which I also agree, coming from a native app development background, where strong typing & code completion are exclusive features that can easily make RN outright unappealing.

To improve the situation & ultimately boost our RN engineers’ confidence with their code, we have begun the conversion process from JavaScript to TypeScript and in some apps/products, 90% of the code is already converted. We also nearly complete our migration within the district-core libraries to offer better code completion.

Product sharing between Platform, Good Idea Bad Timing

Back in 2018, we thought it was a good idea to share a single codebase between various products’ apps and their m-webs across our platform. We launched a pilot project to share a content-rich product initially intended for apps to m-web platform (refer to Building Content-Rich Pages on React Native to read about the design). We got at least 80% of the code shareable between platforms. Long story short, after a couple of releases, we decided to stop this initiative. I could argue that it looked promising initially when we implemented it. But over time, problems began to arise, especially when it’s already too late to realize that the concept blocked us from doing Server Side Rendering (SSR) as the main requirement of the product. Beside that, we could still point out several flaws with our approach, which you can learn from as followed:

  1. In our RN navigation, we registered all routes in a single place, which caused a problem in m-web because all routes and its content were essentially loaded at the first page load. It is against our principal to only load what we needed.
  2. Given our first experience sharing code between platforms with RN-web, the pilot project took a couple of weeks until we could render any page without an error of either missing component or unrecognized RN code. But on a deeper level, we never thought of the idea of sharing the same product’s code across multiple platforms to be feasible, causing a lack of proper preparation back then. But starting from that point onwards, we realized that RN has the potential to become a Single Source Of Truth (SSoT) platform that we can benefit from.
  3. Excessive platform-specific codes. There were a lot of if-else blocks to split native and web implementation at the page level. Highly ineffective but relevant considering the impetuous product sharing initiative and the absence of shareable component-level code.
  4. Conflicting dependency hell, the nastiest of these four problems. This mess essentially locked a particular library version to an older one until we could find a fix that worked for both platforms or modified the webpack config to mimic the missing desired behavior. These handy hacks usually came with a price that blocked upgrades in different parts of our dependencies.

Although we’ve stopped the initiative, we never really abandoned the idea. In our retrospective, we tried to reimagine it from different perspectives. Maybe we leaped too far in this area without proper preparation. Here are our considerations for the next iteration:

  1. Find another pilot project that doesn’t require SSR. It would be a good comeback to rekindle the hype.
  2. Improve abstraction for navigation on each platform for seamless and natural operation.
  3. Ensure fully ready standard components in both platforms that have the same properties and behaviors to make the integration less painful and faster.
  4. Standardize components’ behaviors based on Momentum Design Guidelines so that we don’t need to use hacky solutions that could limit our options, not to mention long-term interoperability & applicability. In a certain situation, with a single RN code, we could use a wrapping concept to utilize a “pure js” approach inside the underlying web implementation, while going more natively in native implementations. We could also remove most of the platform-specific if-else blocks after all components’ properties and behaviors achieve parity between apps and web platforms.

District UI

I decided to put district-ui in this dedicated section as a tribute to everyone who’s been involved in making (& improving) this library to be the best in the district-system. As the name implies, it is a collection of our versatile UI components that we’ve used in multiple scenarios. This library was made because in our initial conception, we saw RN as the preemptive tool to solve a potential problem of multiple apps development in a more efficient way. In the past, we only needed to develop a single app but around early 2017 as we did our initial RN research, we predicted, correctly in hindsight, that overtime, as the business continues to grow, we would need to develop more than just one app to cater to various business partners whose demand pattern continues until today. The logical follow-up question: should we develop the very same component in each of that app? A resounding no. Then, could we just fine tune it to meet each app’s requirement? As it turned out, yes. Personally, I believe that an app is not defined by just how round the edge of the button it has, or the checkbox’s checking animation. Standardized and consistent UI components would provide not only our users with a similar (and therefore, familiar) experience across apps, but also our engineers with indistinguishable component usage, props, behaviour, and output of any app they work on.

From which direction should we take the components design inspiration?

Without a clear direction for the components concept, we got a lot of resistance from potential RN clients because our components had no identity that prompted follow-up concerns such as do our components adhere to Google’s Material Design or Apple’s Human Interface Guidelines?, why is there a single material design component in iOS that looks “out of place” to the rest of the components? At that time, our design team fellow also had a similar thought: Is it possible to represent some components in a similar way between platforms? It is really inefficient to design the same page for each platform, like search_page_android, search_page_ios just because they follow different design platforms offered by their own respective platforms.

Momentum Design Guidelines, our long lost brother!

Figure 1. Showcase of Momentum Design Implementation

The Momentum Design Guidelines (MDG) is something that we’ve yearned for a very long time. At last, we could settle on a robust design guidelines potentially suitable not only for all platforms and all apps, but also acceptable by all designers. Before settling down with MDG, we’ve adopted several other guidelines before that we ended up with multiple variants of the same components. Such predicament had put a friction on the MDG adoption campaign because of the reluctance from people who would have suffered the most: our native platform engineers, whose repeating concern was using yet another guideline. As an android developer who took part in adding one (out of five) component variant, I totally understand their frustrations and would have the same apprehension if I were in their shoes. But, I foresee a compelling future with MDG. So, with the interdependency between RN engineers and design teams, we decided to get into a mutually beneficial partnership, where we would adopt the MDG into our district-ui and they would help maintain and drive its utilization. On the district-ui side, we won’t need to worry about the disparity and inconsistency between native and RN or RN Android and RN iOS. For them, the partnership will provide them with a pilot implementation as a Proof of Concept (PoC) to show to other platforms that MDG is ready to cater to our standardization needs.

Acknowledging that code sharing between product’s app and web wouldn’t be successful without a standard implementation of components in both platforms, we expanded district-ui coverage further to reach our web platforms. The idea is we could reuse the same components on the web given that they also use React & RN-web. Although it is impossible to share everything, at least, we could create a SSoT to unify development so that changes on one platform would be transparent on another. Thus, we began the migration for web platforms to source their UI components from our district-ui. It hasn’t been an easy endeavor if I might add, but the ingenuity of our web engineers has made the migration possible.

As we reach a certain scale of MDG’s adoption across multiple fronts, it becomes easier to convince our native platform teams to adopt it. It is more convincing now as the RN inside our traveloka apps, B2B apps, web’s, and designers’ tools are already using the MDG and it is going well. Although we are still migrating old implementations in our native page products to MDG, we no longer create platform-specific designs because everyone already speaks the same design language now.

District-icon: RN implementation of Traveloka Icon-kit

Normally, when our developers receive a new page design, our designer provides the icons we use in the page through a design previewing platform, which has worked well in most cases. But this practice may cause unused, outdated, and most troublesome of all, duplicated sets of icons such as an ic_back_button_new or an ic_back_button_v2. Our fellow engineers and designers created the Traveloka Icon-Kit, a single repository to not only version control all of our icons coupled with an integrated tool to create, coordinate, and distribute the latest improvement to our icons pack, but also simplify icons implementation; whenever our designer pushes a new tooling update, it will also publish a new version of our icons pack called the district-icon. When a particular icon is no longer referenced inside our code, the tree-shaking capability will exclude it from our bundling process. When designers update an icon, they only need to click the publish button, which will trigger a workflow to update the master library. Developers, in turn, just need to update their local library to get the latest copy, effectively solving all three aforementioned icon problems (at least in RN and web platforms).

Although it seems like a nice solution, there is still one lingering flaw of non-universal format across platforms that we are still currently addressing namely, a VectorDrawable format in Android, a pdf in iOS, and an RN-svg class for RN. If every icon needs its own format in each platform, there will be two implementations of the same icon that we ship just for the application. Theoretically, using native resources is possible but not that easy. The harder part is letting the compiler know that an icon is not only referenced in the native part, but also in RN. Remember our tree-shaking capability I mentioned before? In Android, as an example, we have a similar capability called resource shrinking that effectively prevents any unused resource to be included in the APK when there’s nothing that points (refers) to it. Sadly, such reference counting is currently only available natively. So, if we have a resource called ic_back_button that is used only in RN, it will not be included in the final APK because the resource shrinker will be unable to find any reference to it in our Android source code. Unless we could find a way to tell our native part that we need certain icons, we don’t have a perfect solution for this yet.

Continue to: React-native at Traveloka: Bridging Past and Future (Part 2)

--

--