Native Navigation in React Native - Intro
This will be a multi-part post about the state of navigation in React Native applications, detailing roadblocks that we hit and the solutions we found.
To start, I need to give a little context. I work for a company called Instructure that builds software related to education. It started with the main tool being an LMS called Canvas in 2011. Since then, Instructure has branched out into a few additional areas. The web stack was originally a Ruby on Rails app, and has since expanded to include React heavily.
We (the iOS team) at Instructure recently shipped a new app, one geared towards teachers using Canvas. When we started the app, we realized we had a decision to make: Do we take this opportunity to try out the new and trendy React Native? We didn’t just want to try React Native because it was “trendy” - that was the least of our worries. One of the big immediate wins for us in particular was the amount of React talent we had internally. Not only that, but in small trial runs with it we found great wins when it came to productivity. The feedback loop was tightened so much that it kept distractions such as Twitter down.
Being in the education market, on the mobile team there were two very important thing to us: iPad support, and accessibility. Our software has to be accessible. And, 1:1 programs are very common on iOS in education, most of those cases using iPads. So before we started down this path we had to validate that this was even doable.
Being snobby mobile developers, we also decided early on that we would not settle for this fake navigation shit that many React Native apps go for. Faking navigation bars, animations, etc just doesn’t add points to your app for feeling high in quality. This also tied directly to the requirement we set that we needed iPad support - we needed
UISplitViewController, or something similar. To do so, we had a few different options. We immediately crossed out projects like React Navigation due to them not being native.
In the end we ended up with something that we are proud of. It looks like this:
Getting there however, took a bit of work. This series of posts will detail the native navigation approach we took and how we built our own solution.
iPad support in
plain old React Native
React Native has
NavigatorIOS. It is soft deprecated in favor of the more cross-platform
Navigator. We could use it, but ended up just using Wix’s React Native Navigation library. It was a bit more full featured and suited more for us.
Wix’s React Native Navigation
Wix’s React Native Navigation project was the only library offering native navigation at the time we started our project. Airbnb later released their own solution which I’ll go into detail below.
So, this is what we started our project using. It worked. The API was not pretty by any means, but they were trying to fix that by working on a brand new API-breaking version to clean things up. It was the only option at the time, and we thought we shove iPad support in somehow so this is what we started with. A couple months into development of our Teacher app (yes, we procrastinated figuring out iPad stuff until then), we finally decided to get iPad support in place.
We tried shoehorning in
UISplitViewController support, but found that React Native Navigation made some bad assumptions in it’s codebase: that every view controller should be wrapped in a
UINavigationController. Their v2 also made this incorrect assumption. This is incorrect because if you try to embed a
UISplitViewController inside a
UIKit will throw an exception in your face.
I tried fixing that assumption in our own fork of the project. It didn’t go so well. It was pretty spaghetti-like, and became very difficult. That is when we discovered the newly released offering from Airbnb.
Airbnb’s Native Navigation
Airbnb’s Native Navigation project looked really slick. It seemed like it would work for us, so we swapped out or navigation stack for this. We got our existing stuff up almost to parity, but there were a few bugs that we hit that were pretty big blockers. I submitted a number of pull requests to fix them, but the project kinda fell out of maintenance, and no progress was made at all on the library. Weeks later, we heard official word from Airbnb that it just wasn’t a priority at that time.
The other main issue with Native Navigation was the fact that it was written in Swift. React Native only ships as a static library - something that isn’t supported in Swift (well, until 4 rolls out). Due to this, the only supported way to pull in Native Navigation was to use Cocoapods, and compile React Native and all it’s dependencies such as Yoga as a dynamically linked framework. This may sound fine and all, but we started to use other 3rd party libraries that bridged things in iOS that React Native hadn’t yet. They all expected React Native to be linked in as a static library - and one cannot simply have both, else you’ll be faced with duplicate symbol linker errors.
So, what to do about this?
Building our own native navigation solution
We decided that it would just be easier to write our own. We could have full control over what we needed, and after having spent weeks playing around with the two main offerings, we had a fairly good idea of what the strengths and weaknesses were of each, and how to mesh those into one really good native navigation solution. The only drawback with this approach was that because we weren’t using React Native on the Android side, our solution would for now be iOS only. We were ok with that.
What we ended up with feels pretty solid. It was implemented in Swift, which in retrospective was a mistake. We just needed to get this app shipped - we had a strict deadline, and so we didn’t even build this project out in it’s own target. As I’ve been trying to refactor it and clean it up some, I ran into the same issue I did as Native Navigation - the roadblock with the installation process.
Swift 4 will support static library targets, which is nice. However, to include a static library in Swift, from what it looks like you will still need to define a module map for it to be imported in Swift code. What that means is that I need to write and maintain a module map for React Native because there isn’t one yet. Or, rewrite the library back into Objective-C - what it should have been written in originally.
The app we shipped was a major success on our team. It feels very high quality and we have recieved lots of great feedback about it. Users can’t even tell that it was built using a “hybrid” technology such as React Native. This is largely in part I think, due to the native navigation approach we did. Using native
UIKit types such as
UISplitViewController gave us some big wins - side by side multitasking was a breeze to implement. We literally flipped the switch to support it and it worked!
I’m definitely glad that we took the approach that we did. It proved that yes, you can build a high quality app in React Native. It just takes a bit of additional work.