Mind the Gap
Photo by Denny Luan on Unsplash
The holy grail of ‘write once, run everywhere’ is arguably as old as the 70s. The idea that developers can ignore all those pesky device platforms and concentrate on coding features — the stuff that adds value, the software that’s eating the world — is seductive. When it comes to mobile app development, while there are many cross-platform technologies vying for adoption, there remains one clear standout: Facebook’s own React Native.
React Native began as a hackathon that took the already popular Javacript UI framework React and enabled it to render to Android and iOS. Released in 2015, it has since seen explosive adoption, and for good reason. Rather than write once, run anywhere, React Native espouses ‘learn once, write anywhere’. The idea being that a developer can craft mobile app experiences that retain the different strengths and look & feel of each platform, but do so with fundamentally the same set of technologies.
At Adapptor we’ve found this allows us to focus on building features rather than fighting OS plumbing. (I mean, have you seen this?) But React Native doesn’t quite deliver on the theoretical 2x speed-up it promises, due to the occasional Chasm of Doom™ which can be narrow but deep. Often manifesting as bugs or strange behaviour on one platform or the other, such checks aren’t surprising given the complexity of the architecture that enables the magic of React Native, and the vibrant ecosystem of third-party libraries that surrounds it. (There are some great articles on React Native internals.) At such times, visibility is crucial.
Photo by Zac Durant on Unsplash
Visibility into a software system reveals that important gap between what you think is happening, and what is actually happening. React Native has some great tools out-of-the-box, including the debugger. It enables breakpoint debugging, run-time layout inspection, observation and injection of redux state, and a lot more. But the debugger, being built atop Chrome developer tools, has one fairly obvious limitation — it can only interact with the JavaScript side of React Native.
Both Android and iOS are mature platforms in their own right, and Android Studio and Xcode ship with great debugging tools. If you’re coming to React Native from a web background you might not be aware of them.
PRO TIP: Keep an eye on the native side of your React Native app
For example, console logs are available from the React Native CLI with the log-android flag and its iOS counterpart, log-ios. For Android, the same information is available using the Android Debug Bridge’s logcat feature, and running it directly opens up a host of options not available from the react-native CLI.
For example, the following will produce a coloured tail of JavaScript console logs at Debug level and above, UI thread errors at Info level and above, and Error level and above for all other logs:
adb logcat -b all -v color ReactNativeJS:D ReactNative:I *:E
Or to automatically get logs from any thread in your app:
adb logcat -b all -v color ReactNativeJS:I *:E | grep -F “`adb shell ps | grep com.example.app | cut -c14–18`”
Now, if your app has a handled exception in JavaScript, you will get something like the following.
10:14:59.792 16754 16799 I ReactNativeJS: navigate to screen 2
10:14:59.831 16754 16799 W ReactNativeJS: Something really bad happened, but — hey! — this is JavaScript. Have a nice day.
And, if the exception is unhandled?
10:19:22.342 17217 17262 I ReactNativeJS: navigate to screen 2
10:19:22.411 17217 17262 E ReactNativeJS: TypeError: undefined is not an object (evaluating ‘{}.doesnt.exist’)
But the above situations will be visible in the React Native Debugger. If, on the other hand, the handled exception occurs in a native module, you will also see:
09:38:39.636 13120 13166 I ReactNativeJS: navigate to screen 2
09:38:44.001 13120 13120 E NativeModule: Something really bad happened, but you’ll be fine… probably.
09:38:52.679 13120 13120 D ReactNative: ReactInstanceManager.detachViewFromInstance()
And if the same exception is unhandled:
09:48:10.730 14455 14499 I ReactNativeJS: navigate to screen 2
09:48:14.190 14455 14455 E AndroidRuntime: FATAL EXCEPTION: main
09:48:14.190 14455 14455 E AndroidRuntime: Process: com.example.app, PID: 14455
09:48:14.190 14455 14455 E AndroidRuntime: com.google.maps.api.android.lib6.common.apiexception.f: java.lang.ArithmeticException: divide by zero
Log tails like the above can be scripted to be part of your development environment, and serve as an information radiator (to torture a term from Agile development) — a splash of red that catches your eye and alerts you to an otherwise unnoticed problem.
Bad behaviour can crop up in any layer of your React Native app — your JavaScript code, third party JavaScript libraries, third-party native modules, and React Native itself. Being aware of the tools you already have for seeing these problems can save many a frustrating hour.
This is the first in a pro tips series on React Native.