Network layer in React Native
Unfortunately, not everyone was able to attend the conference and/or listen to my talk, so for those who’s interested, I’d like to provide an article explaining the concept I shared.
Nowadays, it is hard to imagine a mobile application without any kind of network communications. Currently, my phone contains more than 40 applications, but there is only one that doesn’t use any of them — calculator. For me it is a solid indicator that it is almost inevitable to avoid writing network-specific code in an average React Native app.
Meet our heroes!
So it turns out that React Native should supply fetch, XMLHttpRequest and WebSocket APIs to JS runtime. Although, there are no such libraries that would work in appliance with WHATWG specifications (e.g. mimic web behaviour) for React Native target platforms (iOS and Android). That implies React Native should’ve re-implement existing standards and it did.
XMLHttpRequest (XHR) is an object that is used to interact with remote resources. It was originally designed by Microsoft long long time ago and later on adapted by other browser vendors. Despite the naming, nowadays this object is capable of transmitting different data types (not only XML) and work over various protocols based on TCP.
In React Native,
The back-end part of
XMLHttpRequest is called
Networking. It is an abstraction on top of iOS and Android operating systems that provides a unified API layer that front-end can work with. Implementation of this layer is different for each platform. While on iOS
Networking module takes advantage of built-in
URLSession, Android version works on top of third-party
OkHTTP library that supplies a certain abstraction for low-level Java networking API.
Therefore, once developer make a request, it goes through React Native’s
Networking module and underlying operating system right to the requested resource.
Fetch is a modern concept equivalent to
XMLHttpRequest. It offers a lot of the same functionality as the
XMLHttpRequest, but is designed to be more extensible and efficient. It operates
Response objects that can be re-used in the following requests.
In modern browsers, this API is a part of
XMLHttpRequest implementation, so it implicitly uses React Native’s
Networking module under the hood. That implies some nuances that we need to be aware of. For instance, one of the known
Networking issues is impossibility of transferring any binary data over the wire that currently leads to workarounds (like working with base64 representation of binary data) and/or third-party solutions (like react-native-fetch-blob). You can learn more about these and other implications in the second part of my ReactNativeEU talk.
WebSocket is a communications protocol, providing full-duplex communication channels over a single TCP connection.
You might already use WebSockets for developing your React Native applications. Every time you start an app in development mode, it creates a WebSocket connection between packager and the app itself, so every time you edit your JS files (or other static assets), it sends a patch signal to the app so it can react to the changes.
As far as WebSocket is somewhat different from a standalone request, there is a separated module called
WebSockets that performs as a back-end for the technology. Not hard to guess, that by analogy with
WebSocket also has two different implementations across operating systems. Android is being consistent using
OkHTTP library while iOS uses a self-written implementation on top of
The second part of this article is dedicated to cover the most common networking issues in React Native. Debugging network requests, sending binary data over the wire and credentials management — today we’ll unveil them all.
Debugging network requests
Due to implementation specifics, CDT is not capable of handling network requests. If we consider web platform,
WebSocket APIs are backed by the browser itself which gives it an advantage of having spies that makes it possible to trace network requests. However, React Native doesn’t use Chrome to make networking requests (all network requests goes through the
Networking module no matter what), so it can’t trace them.
There is a way to avoid using React Native networking polyfill (so all requests in CDT will go through the Chrome platform and therefore will become traceable), but that would bring inconsistencies between your runtime environment in CDT and your app. For instance,
fetch will become capable of sending binary data in CDT, but throw an error in RN app. This is a thin ice and therefore not recommended to use. Hopefully, there are many alternative ways you can use to debug network requests.
Using TCP traffic monitoring tools
This approach works if you develop using emulator. All your requests will go through your PC/Mac which means they will be regular HTTP requests* and therefore, become traceable.
By “regular HTTP requests” I mean HTTP requests that are similar to the requests we make in our browsers. HTTP protocol is based on TCP, so most of the traffic monitoring tools will be capable to trace them down.
Monitoring TCP traffic becomes a little bit more complex when we start developing using real device. In order to monitor traffic from mobile, you need to configure a proxy (it is possible with tools like CharlesProxy (docs), mitmproxy (docs) and some others) and manually re-route application requests through it.
Using XHRInterceptor / WebSocketInterceptor
There is another way of tracing your remote requests: by using interceptors. React Native ships with two interceptors for XMLHttpRequest and WebSockets accordingly. Each one in meant to be used for tracing an appropriate API: XHRInterceptor for XMLHttpRequest and fetch requests, WebSocketInterceptor for WebSocket connections.
These APIs are not documented or exposed as a part of the object, exported by
react-native package. If you would like to use either of them, there is no other way than relying on current relative import path (which may change in upcoming versions of React Native).
Hopefully, you don’t have to do any of these by your own. There is a pretty great tool brought to us by Infinite Red called Reactotron. It is a swiss knife for React(+Native) development that provides lots of helpful functionality like debugging network requests, redux actions, sagas and much much more. You can easily find a full list of features on its website.
At this moment, the only solution React Native provides by default is “Networking monitor” that you can enable by shaking your device in development mode. However, the functionality of this tool is very limited to basic tracing of the request. Hopefully, there are lots of approaches in the community that may slightly improve your experience in this area.
Sending binary data over the wire
Another big topic related to networking issues is about transmitting binary data in React Native. It is not a secret that React Native never supported transferring files out of box. We use various workarounds like converting binary data to base64 strings or third-party solutions like react-native-fetch-blob, but the underlying issue stays unsolved. This part will unveil some internals of the
Blob management, consider workarounds and potential solutions we can apply to React Native to bridge the gap.
Blobconstructors which makes it impossible to represent binary data. However, all modern browsers enhance a default runtime environment by adding their custom implementations so you can work with file inputs and binary data straight from JS. With these implementations actual binary data is stored on the browser/OS side and the only part which is exposed to developer is a reference wrapped into
Blobobject. Unfortunately, JS environment (and Networking module that exposes provides backend for
fetch) of React Native doesn’t support either of these objects, so there is no “default” way of getting a reference to the file from your code.
Convert binary to base64
As far as React Native is not capable of transferring non-primitive data over the bridge (so only serialisable data structures), the only way of getting there is to convert binary format to a string. Base64 sounds like a good solution until we consider a round-trip request:
Furthermore, we also need to render an image. Therefore we supply base64 string as a URI parameter for React Native’s
<Image /> tag. At the moment of runtime, JSX tree that React Native compose will be sent to the native thread where the native UI lives, therefore the base64 string we supply will be traveling over the bridge again causing memory spikes and additional performance issues.
Now, when we considered potential issues of using base64, I’d like to focus your attention on the alternative approaches we can use for transmitting binary data in React Native.
Can we do better?
Of course, the problem outlined above is not new and probably every developer who ever had to transmit binary data faced it. This problem inspired many developers to give birth to numerous third-party solutions we can use. Although, the underlying issue is still not solved.
The only solid solution for this problem would be to bridge the missing bits of the standard. Hopefully, some time ago Satyajit Sahoo and Mike Grabowski from Callstack.io created a PR to that add these bits to the
WebSocket module. After six months of discussions, rebases and reviews, it has been merged and from now on, the latest versions of React Native supports transmitting binary data over WebSocket protocol (🎉!). Although, it solves only a part of the problem: you still can’t send binaries using
Networking module. This part of the issue is still under consideration, but you can keep an eye on the progress of the second PR here. Once it is merged, the problem of transferring binary data in React Native will be closed for good.
React Native is a complex platform in terms of composition of different patterns, libraries and architectural solutions. Network layer is only a one of many interesting topics that powers the platform, although for some reason not covered by the community at all. Hope this article shed some light on the network layer and pushed your knowledge of the platform a little bit further.
Have any questions left? You are very welcome to ask them on Twitter or by commenting this article below.
All these amazing illustrations drawn by Nicolas: email@example.com.