When to Go Native: Astro’s Journey from React Native to Native

Aldo Wachyudi
astronauts-id
Published in
10 min readNov 23, 2023

The Astro mobile engineering team is in the final stages of releasing native Android & iOS apps to production. As we say farewell to our React Native codebase, it’s the perfect time to reflect on our year-long journey.

I’ll divide this retrospective into multiple articles. The first article will delve into the historical reasons behind our choice to adopt React Native and our eventual transition to native Android & iOS. Subsequent articles will explore the migration strategy, technical challenges we encountered, the final results on both platforms, and the future of mobile development at Astro.

Before we continue, I must provide a disclaimer: the decision to migrate to native apps is specific to Astro’s customer-facing applications. We remain open to the idea of using React Native for any future projects. As the saying goes in the crypto community, “Do your own research.” What works well for us might not necessarily be the ideal solution for your product or organization. With that said, let’s embark on this journey.

Why did we choose React Native initially?

In Astro’s early days, every resource was inherently limited. It resembled the startup scenario often depicted in movies or TV series. Everyone did a little bit of everything and every decision carried weight and consequences. The term “team” actually meant a “one-man” army. Our primary mission was clear: launch Astro to the Play Store and App Store. We wanted to move fast and.. hoped things didn’t break too often. ✌️

This made hybrid app solutions, like React Native and Flutter, appealing. They promised a “write once, run everywhere” approach, enabling us to target both the App Store and Play Store with a single codebase. We’ve been leaning towards React Native, primarily because of our team’s adeptness at React. But that is just one of several determining factors, which include:

Unified Codebase for Both Platforms

With a shared codebase, we ensure feature consistency across Android & iOS. This not only allows us to accelerate our development but also to validate ideas rapidly. One way to illustrate this is through the manual testing phase. While we perform rigorous testing before any release, the advantage of a common codebase means that in many instances, testing on just one platform (either Android or iOS) is sufficient, given their shared foundation.

Additionally, when there’s a demand for platform-specific functionalities, React Native facilitates these integrations via Native Modules. We’ve made our own Native Modules for some features, like payment and fraud prevention, and they work as expected. The third-party libraries that we use are essentially wrappers around Native Modules. For instance, behind the scenes, react-native-maps uses Google Maps (Maps SDK) and Apple Maps (MapKit) while React-native-fast-image uses Glide and SDWebImage.

In Android, react-native-maps uses Google Maps
In iOS, react-native-maps uses Apple Maps

Over-the-Air Updates

One of the distinct advantages of React Native is its over-the-air (OTA) updates, facilitated by Microsoft App Center’s CodePush. CodePush is a cloud service that enables developers to deploy JavaScript (JS) codes directly to users’ devices. Developers can even configure updates to be platform-specific (either for Android or iOS), target certain versions of the app, or focus on specific JS files.

The ability to roll out updates without waiting for the Google or Apple app review process is a significant advantage. This becomes especially valuable in the post-launch initial months, where encountering unforeseen crashes or bugs is not rare. The traditional update process could mean waiting for days, or even weeks, to deliver fixes to the end-users. With CodePush, these fixes can be almost immediate, enhancing the user experience for our early customers.

Developer Experience

React Native inherits best practices from React, which boasts a robust community and a mature codebase optimized for UI development. React’s components and hooks have popularized the declarative UI approach, influencing the creation of SwiftUI and Jetpack Compose. Furthermore, the architectural patterns of Redux in React Native have played a pivotal role in the creation of the Model-View-Intent pattern in Android and The Composable-Architecture in iOS. Writing code in React Native feels like working within a mature web ecosystem, but tailored for an app, without the complexities of app lifecycles or varying OS version’s behaviors.

While the development environment might not be as feature-rich as Android Studio or Xcode, the combination of Visual Studio Code and Flipper (and its plugins) meets our needs. Whether it’s viewing logs, inspecting Redux states, profiling an app’s frames per second, or introspecting HTTP traffic, these tools equip our developers with everything they need to be efficient and productive.

Flipper: the React Native apps debugger

Despite the undeniable benefits of React Native such as a unified codebase, OTA updates, and a strong community that were particularly helpful in Astro’s early days, we still find ourselves considering a shift to native Android & iOS. It may sound bizarre, given React Native’s comfortable advantages. So, what’s prompting this move towards the native realm? As we proceed, our motivations will become clearer.

The Shift to Native

A lot has shifted since Astro’s inception just a year ago. Our app has been downloaded by over a million users. We’ve achieved milestones we once only dreamed of. Yet, for a driven startup like ours, this is just the beginning; we envision even greater growth ahead.

The growth brings us to a crucial crossroads. The tipping point is when we have to deliver a project that affects many parts of our app. Despite our best efforts to make the experience as smooth as possible, like refactoring, caching, and optimizing lists, we still encounter performance issues. The situation is made worse because we’ve expanded our reach to users with more memory-constrained devices, where the issues are even more noticeable. This is the point where we feel that we’ve reached the limit of optimizing the code from our side. Surely, there is a way to optimize it further, but do we want to allocate more time and resources just for this aspect of development? Is there a way to ensure that in the future we don’t have to deal with this issue again? This is where native comes into play, and in the following sections, I’ll lay out the reasons why.

Performance

The better performance of native solutions compared to hybrid ones is well-established, so I won’t dwell too much on it. As a refresher, hybrid frameworks introduce an additional layer atop the native platform, inevitably causing overhead. This additional layer demands memory and CPU resources to process commands and adapt them for each platform. In the context of React Native, the bundled Hermes JavaScript engine compiles JavaScript to bytecode during build time, converting React components into their native counterparts. Depending on the use case, such as scrolling through a list or rendering animations, the difference in resource utilization can vary, ranging from 23% to 88% for CPU usage, and 26% to 81% for memory.

Transitioning to native offers several tangible benefits. Firstly, the application’s size will shrink, making it accessible to devices with limited storage. By optimizing memory and CPU usage, we can minimize intermittent crashes and enhance battery life — features now easily monitored by both iOS and Android users. Quicker app launches and a more fluid user experience can positively impact conversion rates. Each of these benefits is vital for the continued success of Astro.

Our early results confirm this assumption: all of our app performance metrics have shown improvements. On Android, the most significant improvements include a 46% reduction in app size, a 75% faster P95 start-up time, and a 40% reduction in memory consumption. On iOS, the most notable improvements are a 30.2% faster P95 start-up time, a 30.2% reduction in memory consumption, and a 17.5% smaller download size.

The rest of the metrics, such as frozen frame, slow renders, and CPU tps, have also improved. But let’s save that for future article when we’ve gathered even more data.

Rising Development and Maintenance Costs

As we grew our codebase, we needed more skilled engineers to maintain it. React Native’s minimalist approach means a typical app has to integrate many third-party libraries (in our case, we have used over 100 libraries). However, when dealing with hybrid platforms like React Native, things get a bit more complicated. The external libraries we rely on can have implications for both Android and iOS. If the creators of those libraries stop maintaining them or are slow with updates, it’s up to us to step in and address the issues — on both fronts. This means we have to make sure that the libraries we use work well together.

Engineers who are competent in all three platforms exist but are not as readily available as engineers who specialize in just one platform. Furthermore, when the engineers get stuck, native has many more resources to find online. For start-ups that prioritize velocity, this is an important consideration. After all, the codebase is only as good as the engineer who maintains it.

Additionally, React Native itself isn’t that convenient to maintain too. An illustrative example is ironically when we want to upgrade the React Native itself. Ensuring that all our third-party libraries, their underlying components, and dependencies aligned with the latest React Native version is quite a challenge. These efforts often result in weeks of tackling problems and prolonged testing.

We also find ourselves delving increasingly deeper into platform-specific territories. Whether it is integrating native modules, managing outages like the JCenter shut down on Android, dealing with iOS-specific crashes related to code pushes, or troubleshooting the infamous “connectAnimatedNodeseee: Animated node with tag does not exist” error on Android, our initial objective of maintaining a unified codebase has gradually lost.

Long Term Support

Consistent support for a technology stack is critical when discussing long-term products like Astro. In general, the less we rely on third parties, the greater our chances of long-term maintainability. It’s not surprising to see big tech companies stop supporting a project. For instance, Meta discontinued support for parse.com, Microsoft terminated RoboVM, Google withdrew support from Angular, and Firebase deprecated Dynamic Links.

Interestingly, despite its popularity, React Native is still at version 0.x. This is in stark contrast to other well-maintained Meta libraries such as React, which is at version 18.x, PyTorch at version 2.x, and the legendary HHVM with its last recorded version at 3.x. What’s more, Facebook’s adoption of Compose for Thread suggests a possible shift in their internal tech direction. While these concerns might seem speculative, the potential risks are too significant to be overlooked.

Live Edit feature in Android Studio

On the other hand, the native apps ecosystem keeps evolving too. Features that were once exclusive to hybrid frameworks, like hot-reload, are now available in Jetpack Compose and SwiftUI. Innovations such as the dynamic feature module can further reduce Android app sizes. Xcode’s parallelized builds mean faster incremental builds. And, of course, Swift and Kotlin continue to mature and innovate. Writing code natively isn’t as daunting or time-consuming as it was eight years ago.

Summary

In summary, the journey from React Native to native development isn’t a decision we’ve made lightly at Astro. It’s grounded in our long-term vision, the evolving demands of our user base, and the undeniable benefits of harnessing the full potential of native platforms. While React Native served excellently as a pivotal tool in our early days, our commitment remains to offer the best user experience, and that sometimes means making bold technological shifts.

But, it’s worth noting that this decision is just the beginning. In our upcoming articles, we will delve deep into our migration strategy, the inherent challenges we faced, the outcomes for both Android and iOS platforms, and what lies ahead for mobile app development at Astro. Join us as we continue to discuss this migration journey and share the lessons we’ve gleaned along the way.

Acknowledgements

This article has been a collaborative effort, much like the native migration project itself. It benefits from the expertise and insights of several contributors:

  • Patrick Alexander, for his insightful review, especially his recommendation to add figures and tables for better clarity. These additions have indeed made the complex concepts more graspable.
  • Matthew Rendy Tanudjaja, for his meticulous and detailed editing. His ability to fine-tune the technical language has enhanced both the accuracy and readability of this story.
  • Vincent Tjendra, for his constructive feedbacks on the content. His emphasis on quantifying our experiences and enriching the storytelling aspect has added an extra layer of depth, making our technical journey resonate more with our readers.

Finally, this article would not have been written without the relentless dedication and hard work of all the Astro team members, affectionately known as ‘astronauts’, who devoted their talents and time to bring our projects to completion. Special thanks to our engineering team: I Gede Agus Maha Putra, Jaenudin Mahfudi, Agus Setiawan, Ahmad Nur Salim, Zulfikar Rinjani, Abdusa Alam, Fadil Irshad, Ignatio Julian Tara, Helga Wijaya, Abd Halim Arumman, Yoas Febrianus Susantio, Bisri Nursa Fadillah, Wenly Pranata, Rizki Maulana, Fajar Febriyan, Arik Dwi Lestari, Katherine Oliviani, Niaclodia Sitepu, Anisah Nur Hidayah, Yudha Pratama, Sherliana Selvia Syahrayathi; and our product team: Harvey Tjiupek, Yosua Wasita, Rai Indhira Saraswati, Theresia Swasti, Bayu Ramadhan, Auzan Farizi, Andrew Le, Pinkan Olivia Irliane, Benny Alexandra.

Their inputs have been vital in refining this story, echoing the collaborative value we so cherish at Astro. Thank you for the support, team!

References & Sources

--

--