18 months ago i wrote a simple pedometer app as described here. Since then i have had the opportunity to build a commercial enterprise iOS app primarily for the iPad. I would like to describe my thoughts and experiences in this post.
Since my last post, the react native documentation has improved significantly with more examples, especially the sections on how to bridge native modules.
React Native layout engine uses flexbox, so a good understanding is recommended. We tried to avoid using absolute positioning, in most cases you won’t need it. In the web ecosystem, there are a lot of ways of managing styles e.g. SASS, LESS, POSTCSS etc.
Early on in the project we had identified theming as an integral part of our app. After exploring several solutions, we settled on Shoutem Theme. It uses a HOC to wrap core and custom components, passes styles as props and allows inheritance of styles. There is also the ability to define variables and mixins.
One key feature is that we could define a top level component, apply a style and the children would be styled based on this parent style. A simplistic example is to have a <Row/> component with two <Text/> elements, the first <Text/> element takes 1/3 of the space and the second 2/3 of the space like this:
<View styleName="content"> <Text>Label:</Text> <Text>21 hours ago</Text> </View>
My general approach to most UI projects is to build a styleguide/ component library within its own sandbox. Any new features or widgets would then be developed in isolation and tested here. Once ready and tested, we proceeded to use it in the main application.
One rule of thumb that i tried to enforce was not to let the styles in the theme be too specific to an application component, for example instead of ‘photoGallery‘ and ‘fooGallery‘ use ‘gallery‘.
Let’s face it native app development whether its Android or iOS is very mature. There are hundreds of reusable components/modules out there, as well as tried and tested patterns. React Native is not quite there, lets look at some examples:
An example would be implementing a swipeable row which is quite common in iOS apps. The core RN framework provides a very complex and crude implementation that was not quite suitable and still considered experimental at the time of writing. We ended up using react-native-swipe-list-view.
I found using ListView cumbersome. ListView required a datasource which was too complex for what it needed to be. I needed to use ListView with ImmutableJS then, so ended up using react-native-immutable-list-view
As most software there will always be bugs. Font scaling on RN Text Inputs didn’t work. This is described in this issue. To get around this until there is an official fix, i retrieved the scale multiplier from the AccessibilityManager as described in this post. This multiplier was used to calculate the fontSize in the shoutem theme for TextInputs.
fontSize: base * getScaleMultiplier()
A lot has been said about react native navigation. For the pedometer i used react-native-router-flux as that seemed the best solution at the time. Also react-native-router-flux was using a forked version of RN 0.26. The official RN documentation recommended NavigationExperimental, which is what i initially tried. However integration with Redux was not so straightforward. My initial solution involved using HOC to define navigation components. I also looked at ex-navigation and wix’s react-native-navigation, which uses native navigation, as solutions.
I was close to using the solution from wix which looked quite stable Then early this year (2017) react-navigation was announced. Although still in beta we found react-navigation to be simple and effective for most of our needs.
It will be quite interesting to see how airbnb’s native-navigation solution which is built using ‘native’ navigation components turns out once it is production ready.
In terms of a clear navigation strategy the jury is still out.
Upgrading to New React Native Versions
With the introduction of react–native–git–upgrade, i tried to upgrade the version of react native on the project. The first time was quite a painful experience, their were a lot of conflicts in the *.pbxproj file. This can be quite daunting for developers who don’t have an understanding of the Xcode project format. Also the first time i performed the upgrade, the Apple TV projects were added to the project which was superfluous.
Things to watch out for before upgrading your react native project:
- Read the react native release notes for any breaking changes.
- If you are using any third party libraries, be aware that any they might not support the newer version of react native. Most popular libraries such as react-native-vector-icons tend to have a quicker turnaround times.
- My advice is to wait at least a few weeks before performing an upgrade.
- Do not attempt an upgrade close to a production release!
File Access and Data Transfer
One thing we found out was that downloading files such as images was problematic and non-performant. This is because the image is downloaded in the JS thread, then serialized using Base64 since react native bridging does not support transferring blobs across the bridge. Luckily, react-native-fetch-blob solves this issue.
To Bridge or Not to Bridge
The answer is ‘yes’. Whilst you might be using a lot of third party RN projects that provide bridging solutions e.g. react-native-camera, you may find that occasionally you may have to get your hands dirty with Objective-C or Swift. My team needed the zoom features of the camera which was not implemented, so my colleague chermie forked the project to provide this functionality.
TouchId and keychain capabilities were required for the app. There was not suitable 3rd party RN component, so i had to build our own. I am not an Objective C expert (not yet!) so i wrote the bridging code in Swift which i found much more intuitive. A very crude implementation can be found here. To use the Keychain API, i needed to either implement a custom wrapper or use a 3rd party implementation. I looked at several and eventually decided to use KeychainAccess.
Which brings me to another point:
On the react native side dependencies are managed by npm. However, on the native side you may be required to add native libraries and link these libraries. There are several ways to do this:
- Add a static library
- Use CocoaPods, in this case you will have to create a workspace for your native iOS project
- Use Carthage
- Create a RN module with support for react-native link
The app had to work offline, this meant synchronizing a storing large amounts of data.
One option was to use SqlLite. From my previous experience, i was wary of using a sql database for a number of reasons:
- Given the complexity of the data, without careful design its easy to end up with a spaghetti schema.
- Most front end developers are not experts in SQL.
- Migration is always painful
After careful consideration, Realm was chosen for a number of reasons:
- Object Database which makes it easier to use.
- Provides support for react native.
- Migration can be as simple as updating the schema version or implementing custom logic.
- Realm currently doesn’t support compound keys, one workaround is to use a string field as the primary key and combine multi keys together into the string.
React Native has come a long in the last year and is a great choice for building Android and iOS applications using a single code base (sic). However this not always the case, given the complexity of certain applications, some native development will be required. There will also be some form of leaky abstraction where an understanding of the underlying native platforms will be required. I also don’t expect a mad rush of native developers adopting React Native. Well not just yet.
These are just some of the experiences i faced when building a commercial react native application. I would love to hear your comments and experiences.
And BTW, the journey continues…