Native & React Native @ BigCommerce
Cross-platform tools "... allowed a team of motivated engineers who had never coded mobile apps to create an impressive full-functioning prototype that turned this exploratory project into a product driven venture for our team."
In 2018 BigCommerce decided to explore a variety of native mobile apps to expand our platform and better serve our merchants. We carefully evaluated the latest technology trends, the skills of our existing engineers, and the potential job market for hiring new engineers. Our research revealed there were four unique paths we could take to explore mobile development:
Coding native iOS apps in either Objective-C/Swift or Android apps in Kotlin/Java.
• Unrivaled speed/experience on mobile devices
• Full support for threading and performance optimization
• Solid development ecosystem: Xcode & Android Studio
• Easy dependency management
• Native look/feel comes right out of the box
• Having to create and manage two dedicated codebases
• Finding engineers that know both Android & iOS is difficult so teams are generally siloed
• Longer development lifecycle to launch features on both apps simultaneously
• Steep learning curve
There have been a number of tools over the past few years that have allowed you to create mobile apps by either leveraging existing codebases or writing languages other than native code. Examples here are:
• Faster development lifecycle
• Single codebase powers iOS & Android
• You can probably port your existing system to mobile with minimum fuss
• Suboptimal user experience
• Lack of access to the full suite of native functionality
• No support for true threading and optimization
• Debugging can be difficult
Bridge platforms fall somewhere between Hybrid and Native. They give you the ability to code in a non-native language which directly wraps native elements. Additionally when you want to step into pure native code you have the ability to do so and export that functionality via the bridge layer. Example here are:
• React Native
• Fast development lifecycle
• Parity with native speed/experience on mobile devices
• Full support for threading and optimization
• Single codebase powers iOS & Android
• Most code changes can be deployed without submitting a new app store build and purely by updating the JS/Dart packages remotely
• Newer technology so debugging can be complex and bugs can occur which will often require 3rd party library fixes to resolve
• Medium learning curve. You can get very far without touching pure native code but to truly drive this technology you need to work in the native and bridge layers of code
Progressive Web Apps
• Mobile users do not need to download an app and basically get a web experience on steroids
• Some native Android and iOS functionality is exposed via things like Web APK
• Your website is the exact same codeset as your mobile experience so there is a single codeset for all 3 domains: Web, iOS, and Android
• Styling and functionality are universal since CSS/JS experiences are relatively seamless across all modern browsers these days
• No need to submit builds to the app store, deploys are instantaneous
• If we need an app store presence PWA basically makes that a non-starter
• Not all native functionality is readily exposed
• Background processes, when the browser is closed, are not fully supported yet • Performance is still browser based so you will not get a full native feel
BigCommerce had previously attempted mobile apps in 2013 using the hybrid methodology and the results were mixed. While we were able to mostly use our existing codebase to power our application the look, feel, and functionality felt foreign on mobile devices and the user experience was viewed poorly by our end-users.
Lack of a mobile app was one of the top complaints our merchants voiced and we were eager to deliver a product that would meet their growing needs. Having learned our lessons we set out to try and kick the wheels on this venture again.
The selection process
These internal trends combined with the number of premier tech companies that (at the time) had advertised their usage of React Native (e.g., Facebook, Airbnb, Walmart, Microsoft, Uber Eats, etc…) led us to give React Native serious consideration for our mobile pursuits.
Prior to making a final decision we read a number of tech blogs and the trend seemed to be that companies that had started with pure native and transitioned to a React Native hybrid regretted the decision and backed out of it (e.g., Airbnb). Companies that started in pure React Native tended to enjoy the experience much more.
What went right?
Finding the right tools & libraries
React Native is still young and there are a few big names pushing the development space. Companies like Microsoft, Wix and Facebook are driving most of the open source documentation and libraries that can get you from 0-60 quickly.
If you decide to pursue React Native we cannot stress enough using the list of libraries & tools below. They were stable, performant, well documented, and greatly improved our experience:
• Fastlane & Match for automating your builds, code signing & releases
• React Native Navigation for managing your application flows
• Detox for greybox automation testing of your applications
• Firebase for quick prototyping of push notifications, cloud functions, and some quick storage
• Microsoft TypeScript + React Native Starter Kit to help you get up and running
• Ignite CLI for exploring how to structure apps and code them using React Native
Ejecting Early On
Like most React Native users we started by using expo-cli / create-react-native-app to speed things along. However we quickly realized that we had a high likelihood of having to blend native libraries and code to meet our business requirements. This led to us ejecting early on in the process so that we could write occasional swift/java code when needed. This ended up being the case for push notifications and interactive UI charts.
Manually adding 3rd party libraries to an ejected react native project sucks. Nobody has time to drag and drop Xcode projects and manually link libraries. If you are new to Xcode as a whole the entire configuration can feel foreign. It’s so much easier to use react-native link + Cocoa Pods to drive your dependency management for native libraries.
Initial prototyping was a breeze
Creating screens that look/work cross-platform was a breeze
One of the main selling points is that React Native will get you to about ~90% proximity match across platform without much effort. React Native has a Platform module and even allows Platform specific files (e.g., .ios.js vs. .android.js) that helps you target custom code, designs, functionality per platform where needed.
Where did things go wrong?
TypeScript documentation support was not the strongest….
Over time we noticed a lack of @types/* libraries for many open source dependencies we had planned on using. This meant that our engineers either needed to take the time to analyze the dependency libraries and write out the type definitions or embrace the variability of the Any type. When you are committed to typing, a dynamic language using Any just feels wrong, but when it comes to fast-moving prototyping, corners occasionally need to be cut to prove out ideas.
As the popularities of TypeScript & React Native increase this issue should self correct, but given that our package.json file was quickly bloating with dependencies this issue started to spiral out of control.
Dependency mismatches and struggles...
Perhaps the most painful negative we encountered was the fact that we were dealing with 2 tiers of dependency management. On top of the basic NPM packages managed via package.json we also worked with Native packages via Cocoapods. Where we ran into issues were when Native packages relied on specific Swift versions or configurations that clashed with other modules. The end result would be adding the JS libs, Native Libs and react-native linking would work but trying to debug the flurry of iOS or Android errors thrown when attempting to build the app would be endless. A particularly painful example we ran into was trying to get the React Native modules for Segment Analytics and React-Native-Charts-Wrapper to play nice together. Our team lost a few days just troubleshooting iOS build issues. We never even had a chance to see if there were Android issues since we ended up throwing the towel on several 3rd party libraries.
Code bloat & complexity with redux-observables…
As our app grew in complexity we noticed that the simple act of adding new API requests or flows into our app and storing the state so that it could be passed to any number of flows or screens became increasingly complex. It wouldn’t be uncommon to see 500-700 line pull requests for such a task. Additionally as our app moved from single flow to multiple flows the act of maintaining state between them became unmanageable.
This complexity is part of the reason why when we rebuilt our mobile app using native tech, we made a precise decision to pursue PromiseKit. Most engineers are familiar with the simple paradigms it follows: request().then().catch() etc.... You just can’t go wrong with it and we have yet to encounter a feature it cannot easily support.
Naturally you might ask “why didn’t you just swap out observables for promises with React Native?”. The answer to that is that it felt counter productive. Almost like we were fighting with the tool we were trying to use.
Cross-Platform eventually fell apart…
Android and iOS have many platform-specific quirks. An obvious example could be iOS alerts vs. Android’s toast implementation for notifications. There are React Native libraries that can bridge this functionality cross-platform but then you are essentially disrupting the natural behaviors to force a new standard. Given how closely users are tied to their phone behaviors this is a high risk.
Where are we now?
We are now in the merchant beta for our new iOS mobile app. In three weeks we were able to fully port and improve our React Native app to Swift and are in the process of beginning our cloning of Swift to Kotlin for our Android app.
TL;DR - The overarching question here is “do we regret choosing react-native?”
My answer to that is absolutely not. React Native allowed a team of motivated engineers who had, mostly, never coded mobile apps to create an impressive full-functioning prototype that turned this exploratory project into a product driven venture for our team. We will most likely use React Native again for other app prototypes we want to explore.
For any developer or team looking to explore mobile or quickly build prototypes, that has NOT worked on native mobile apps before, I would immediately suggest using React Native.
However, when it comes to a fully productized mobile app ready for showtime I would encourage the pursuit of pure native code and not chasing unicorns.
The “unicorn” of software development is one tool that covers all bases. Usually when trying to achieve this lofty goal corners are cut. For the sake of developer efficiency you lose some of the icing that makes the cake so appealing to end users. As a result there’s a laundry list of failed unicorns strewn throughout the annals of engineering.
Had BigCommerce not hired a strong lead engineer with experience in building native apps in Swift & Objective-C this decision to pivot from react-native to native would have been harder. Ultimately we still feel we made the right decision even if that means we need to hire specialized engineers to support both iOS and Android apps independently.
If you are interested in helping us drive mobile development at BigCommerce we are actively hiring. Please explore our careers page and drop us a line.