React Native Learnings
The goal of this article is to guide mobile application development teams who are planning to adopt React Native framework for their application. This guides on application architecture, design for React Native framework, some of the development items that we need to take care for React-Native framework and challenges that could be faced during development when compared with Native development.
React Native Benefits
React Native is a cross-platform development framework that provides native look and feel in mobile applications. Such a cross-platform framework includes the following benefits.
- PoC’s need not be done separately for each platform (iOS and Android). Thus it helps in quicker evaluations.
- The core business logic and unit tests need not be written specific to each platform
- UI Implementations need not be platform specific. Being platform specific needs extra effort to complete the functional requirements and customizations.
- Separate application design considering different patterns like MVP/MVVM for Android and MVC/VIPER for iOS need not be done.
- Single codebase to reduce maintenance effort.
Evaluation approach to React Native
React native can be used in multiple ways, Team would like to see how it can be integrated with existing native modules. Identified the following items need to be evaluated for react native adoption.
- The amount of code reusability possible across IOS and Android platforms based on the features required by the application.
- Integration of existing native modules which consists of UI and API specific to mobile platform are BLE, Camera, etc.. Complexity that can result when a common mobile application architecture is implemented across IOS and Android platforms
- The amount of platform specific customizations. eg. UI (List and customized layout with flexbox) and transition based animations
- Route Library evaluation to pick better navigation library based on existing application scenarios
- Deep linking behaviour for push notifications
- Redux Design pattern adoption
- JS Network Library (i.e Axios, apisauce, apollo-link) evaluation
The React-Native community-driven navigation library and it is purely JS based. It’s the result of many experiments of the initial react-native based applications from the react-native community.
State management library for react based applications. It’s core principles are Immutable data and predictable state update with pure functions. It helps to share the centralized state information with multiple components.
Redux-Saga helps to manage the side effects of asynchronous calls. It uses ES6 generators to make asynchronous code readable much like async/await feature.
Interface library for modifying control flow of GraphQL Requests. This is designed to make links composable with single purpose links. Used as a standalone client to consume API from Graphql server
Integrated with multiple native existing SDK’s to support BLE, Amazon Fling, Amplify, Firebase and Authentication SDK with Native UI screens.
Used as CI and CD server for Android, iOS. Configured pipe-lines for automation and code-push
This plugin helps to push product improvement changes to end user quickly and new features can be rollout for feedback. The main advantage is we can push JS bundle changes to users without releasing new APK/IPA to play store. Always use signing mechanism to avoid man-in-the-middle attacks
Has support for React, React native and Flow types. Easy to configure rules based on project and it helps team members to write consistent code
Package dependency management tool has capability to cache the downloaded packages
Application Architecture Diagram
- The targeted application interacts with GraphQL server for user data, It uses an apollo-link client which has the capability of chaining, split and channelizes the request based on operations
- Application completely relies on side effects of asynchronous operations that will go nicely with redux-saga. Designed multiple root saga’s to take control of actions based on the socket state (i.e ignore specific actions if there is no socket connection )
- The communication will happen over websocket link except for few critical operations which are routed to HTTP when a socket connection is unstable
- The application deals with very complex data, but want to store only the important data which is used across screens. As a result, it leads to skipping store update for certain events (step 6) which are either temporary results or useful only at that context.
- Step 6 is deviated from regular react redux based design but that is one of the best alternatives to avoid store updates with unnecessary/temporary data
(ex: Presenting alternative products or suggestions based on the currently selected product — As it is valid only given context and data varies drastically for each product. Storing the entire data in the store may not be a good idea as it leads to stale data). Identified many use cases like this in the application and avoided updating redux state based on context.
- Used Selectors, Reselect to transform data and memorize which will help in avoiding unnecessary renders
- Must be able to log the complete operation information with request-response details after processing for debug purpose (Configurable and can be removed in production builds)
- Should be able to configure the timeout for all operations and provide retry mechanism based on status codes
- Would like to queue requests and process gracefully to handle intermittent network issues as reconnection takes 2 ~ 3 sec
- Must be able to handle specific errors based on the operation and take a decision either to retry or not
- The operation must be queued to avoid intermittent network issues
- Must log only elapsed time and status for all the requests to evaluate geographical network latency
Identifying the better testing frameworks and tools are important to any mobile application to improve product quality, code coverage, and faster regression test iteration cycles
Unit & Integration Testing
Test Framework which has preset for react-native and pre-configured with react-native
- Snapshot — To track UI component Styling and Actions creators to avoid unintentional data changes
- Mocks — Mocking Native framework API’s and Timers
- Spy — To validate API invocations mainly used in behaviour driven scenarios
- Code Coverage — Integrated with JUnit to provide results with CI server
Library to test React components with shallow-render, event triggers and dom manipulations
Sinon is a standalone library (provides features same as jest i.e spy, mock, ..) but used only to advance timers as there was a problem with the jest in specific scenarios (i.e max timer count reached issue with Animation Views)
Had prior experience with Appium but evaluated Cavy and Detox for end-to-end tests. Finally chosen Appium as it helps to test in both platforms(Android, iOS) with existed native screens( Native SDK Integrations i.e Social login) in the application flow
JS Static Analysis
- Type Check: It’s important to have a static type checking which will help in large code refactors and improves confidence. It’s better to opt progressive approach with Flow
- Lint: EsLint has default styles and easy to configure rules. Has support for react, react native, flowtype
- As a cross-platform, it is well suited for both Android and iOS. No major challenges in the build configuration
- Identify the popular SDK (i.e Expo) and boilerplates (i.e Ignite, Pepperoni ) as it avoids configuring and installing platform-specific tools. Must consider its limitations(i.e Limited API ’s, binaries can only be built online).
- Consider using NativeBase or React Native Elements for cross-platform toolkits. In case if there are no custom styling components in the application
- Almost 90% of JS code is common for both platforms as there are no platform -specific UI (The % varies based on the UI mostly)
- One must not assume feature works the same way in both platforms. Developers must validate in both platforms and must consider platform specific behaviour differences in estimations
- Decide early, how the application to scale across different devices and screen sizes because it requires a lot of effort to implement screens based on window size
- Team members must aware native platform specific things to improve performance and behaviour customization (ex: Use layout inspector and identify the hierarchy to reduce redundant layers)
- It goes well if state or contexts are not shared between React Native UI screens and native UI screens.
- Integration of existing Native modules is not complex, but try to reduce the data transfer via bridge
- Gif animated Images are supported in iOS by default but not in android, must need to add fresco animated library
- Always use a unique key to improve the performance of List renders
- There is no support for testID in Android for automation purpose must depend on accessibility(Can’t automate application if the application targets users with a disability)
- Try to avoid multiple sources of data like Internal state(derived or normal user changes) and props as it makes decision making tougher.
- Always use Proptypes for the components and containers to track the input data type validations at runtime
- Avoid nesting views un-necessarily as it increases node depth and layout computation in the native side
- Try to build as much as dumb reusable components(avoid internal state) and lift state to containers to reduce complexity
- One must be careful when using certain lifecycle methods (i.e componentdidupdate, getDerivedStateFromProps) must follow best practices else might end up with multiple renders
- UX must consider the data relation to improve application performance(i.e avoid listening to multiple nodes as it might increase renders) and design screen layout accordingly
- Choose the navigation library based on application use case. Community suggests react-navigation as it is purely JS based solution for react-native and maintain clear separation between JS & Native screens to avoid complexity in routing.
- Avoid redux integration as there won’t be any support from the community in future.
- Always check active screen and avoid rendering of the stacked screens to improve performance (i.e props might change so that will trigger render more than one screen and containers)
- One must be very careful in designing reducers to improve performance based on the UI (i.e consider normalization to avoid deep nodes)
- Use selector / reselect instead of referring state path directly. Selectors help to transform data and give the flexibility to avoid hard dependency on the path
- Go with action creators instead of plain actions as it provides readability, abstraction and helps to decouple the multiple layers. Don’t worry about extra boilerplate code
- Consider offline behaviour and provide middlewares to customize
- Upgrading to newer versions are painful as it breaks features.
- Making synchronous calls between JS and Native modules are not possible. Must consider asynchronous APIs approach in the design phase
- React native always behind to native especially Android, One must wait until new release to adopt new API or features(Current android supported version is 26)
- In the long run package updating, 3rd party packages might cause compatibility issues with latest React Native versions and it leads to clones
- Some of the 3rd party packages are not flexible to adopt in every application, one must wait for PR to merge and release or clone and maintain as private packages
- Yet to identify the better tools to trace crashes in both JS and Native layers
- Improving initial screen load time (Takes time to load 25 MB js bundle file roughly 5~6 sec)
Should switch to React Native
It entirely depends on the application features like Navigation, Animations, Platform specific API usage will play a major role in success.
- Planning to start a new application with less divergence in functionality.
- Consistent UI design and feature irrespective of platform.
- Limiting native screens scope or well aware of native UI boundaries.
- Limiting context switch between Native and React native UI screen navigation
- Not using complex gesture-based features.
- Not sharing application state/data between native and JS screens.
- Very limited use of hardware specific feature API’s(i.e BLE, NFC, Audio/Video, Camera)
Benefited with React Native framework, as targeted application developed from scratch and following the best practices. Reduced overall development time and able to share single codebase (90 %) for both platforms.