Our Experience as iOS Developers with React Native
Perspectives from Four Softvisioners
Everybody must have heard so far about React Native – the famous framework built by Facebook that caught the attention of so many developers.
What is React Native and how did it all start?
It was rapidly adopted, having at this point more than 1,772 contributors, 14,936 commits and an impressive number of 277 releases so far.
Lots of iOS developers were very enthusiastic about it and many companies and apps (such as Pinterest, Skype, Uber, Tesla) quickly migrated to the new framework. Having a single code path managed by a single team which saves both time and money, being able to run the app on multiple platforms sounds like a great idea, doesn’t it? Well, the world is divided in this case: while some might argue that having a cross-platform app is the right way to go, from our perspective, cross-platform might be a bad decision for big applications – for simple apps, that do not have so many features, choosing cross-platform frameworks should be the right decision. But for larger applications, from our perspective and on long-term, native development is the right way to go.
This is not supposed to be another article on pros and cons of using React Native vs Native, but instead is a story of our experience with it, native iOS developers moving to React Native, and what difficulties we encountered.
Switching to React Native
The project we were working on had already a native iOS version in App Store, contained a lot of features, people were using it daily. The client wanted to support Android devices too and because they already had people with experience in React Native and functional programming, they decided to re-write the app from scratch in React Native, this way providing support for both Android and iOS. The plan was to port all the features from the iOS application to React Native, but first, the React Native code would run only on Android. Once the Android support would work as expected, we were supposed to add support for iOS too.
For those of you who did not port an app from one language to another, it might seem easy – all the features are well defined, you know what it should look like and what the behavior must be, and so on. For those of you who did that, well, you know that it is not as easy as it sounds. Our journey with React Native took only a few months, after which we went back to native iOS, so the following points are from our perspective and interaction in those few months.
Probably one of the biggest issues we had when switching to React Native were the tools. If on native iOS the XCode had pretty much everything in the same place, on React Native we had to use lots of tools.
As IDE we used Visual Studio Code – contrary to our fears, it was pretty cool working with it – probably one of the fewest things that went well. One of the small inconveniences with VS is the fact you have to install the SDK you want to use, in our case Android SDK, since it does not come with the SDK installed as XCode does. In the matter of code formatting and code analysis, if XCode comes with everything embedded, for VS you have to install additional tools to help you with code formatting and code analysis. We used TypeScript for code analysis and Prettier for code formatting. None of these are difficult to set up, but it’s much easier with XCode where everything is installed at the same step and everything is set up in the same place.
As for the simulators, we had to use Genymotion (the project was designed to compile at the beginning only on Android devices – the reason for which we used Genymotion). And the fun started. Since on Android there are a lot of screen resolutions, we had to configure each simulator – luckily the project had to run on some specific Android devices with specific resolution, so we had to configure only a few of them. Even though it is not related to React Native, from our point of view, Android simulators were not so user-friendly, quite laggy and difficult to manage, compared to iOS Simulators. Once the iOS support will start, the engineers will have to install simulators for iPads too, their simulators will double in numbers, to say the least.
Although most React Native projects use Redux as the state manager framework, for our app we used a custom library that provides state management for React Native similar to The Elm Architecture and Redux. It has composable update functions and Effects built-in.
Functional Programming is encouraged when developing a React Native application. The way the framework was build supports this approach on writing code and an attempt at Object Oriented Programming raises challenges. Switching from Object Oriented Programming to Functional was difficult, especially because we were rewriting an existing OOP style application. This made the development process slower as we had to rethink and change large architecture layers of the native iOS app.
One of the hardest parts of going with a functional approach was in the State Management area. Immutable state is one of the key principles of Functional Programming and because of it we had to add multiple lines of code that would handle object properties updates. For example, in native iOS, if you want to update a property you just assign it the new value. But in React Native you would have to add specific code that will take care of propagating that change throughout the components stack.
One other challenge was keeping files as small as possible. Even though you can create helper files for methods related to business logic, you would still need to keep a large amount of code in the Component-specific file. We would keep the code for rendering the UI as well as the code that handles application state updates (button actions, UI updates, etc) in the same file. In native iOS, you have the XIBs or Storyboards to configure the UI, View specific files to handle the UI customization and the Controller files that should handle only the application state updates.
Classes are not so common in React Native as well. Classes could be created, but it is suggested to only migrate a Component to a Class when the situation requires it as it might affect app performance. Also, inheritance should be kept at a maximum of 2 layers and it may raise undefined behavior.
Looking at the UI part, while on iOS we have the concept of XIBs and Storyboards which make our life easier, on React Native there is nothing like this. Instead, the UI needs to be designed with something similar to HTML and CSS – the only difference would be that the fields names are written with camelCase instead of dash style (textAlign vs text-align). From our perspective, this was not something we really enjoyed and seemed rather complicated. Even though Flexbox is a cool way of doing UI, in our opinion XIBs and Storyboards are more intuitive and easy to use because they offer a visual interface for the engineer.
As for animations, React Native does not offer great solutions for creating complex animations. It has an Animated API but it is not as good as what native iOS offers.
Another notable difference between native and React Native is that if on iOS there is an easy access to Camera, Touch ID, GPS, ARKit (for augmented reality), React Native does not offer support for these. So if the app you are working on must make use of one of these APIs, then there is no middle way for this – you’ll end up implementing them natively.
For our project, an architectural decision was made to use our own custom Navigation code. There was an existing React Navigation framework but it provided only basic functionality and it did not reproduce native user experience. Because we did not have a framework that would provide a native feel to the Navigation, implementing it was a bit of a challenge. One of the issues we had was implementing a flow where a Component needs to be presented after its parent Component was closed or dismissed. For example, showing an Error Alert for a failed action after the Screen that triggered it was closed.
There is a framework called NavigationIOS which is meant to mimic the UINavigationController behavior but cannot be used on Android as well, so using it would defeat the purpose of a single cross-platform code base. Also, there are more third-party libraries that provide tools for navigation but those would require added dependencies and possible unknown or unstable behavior. Because we did not get to work with these frameworks we can’t have a conclusive opinion on their performance.
If you want to add multithreading to your app you have two solutions: you can implement Native Modules for Android and iOS which will instantiate and execute secondary threads or you can give some third party libraries a try. There are a few libraries that seem to use web workers to parallelize code executions but we did not use those so we can’t make a statement on their performance.
The project was always some React Native versions behind due to the fact that when a new upgrade was made, the entire app had to be re-tested for possible issues. However, not every React Native upgrade was possible and sometimes we had to wait for the next React Native version release so we would not introduce other issues into the app.
Here are some of the issues we faced:
- The upgrade to React Native v0.51 brought a bug which caused the aliasing of border on views on Android: https://github.com/facebook/react-native/issues/17267 . This was fixed in v.0.56. It was not reported as being an issue on iOS based on what others have reported.
- Text Input was somehow notably slow: https://github.com/facebook/react-native/issues/19126 and https://github.com/facebook/react-native/issues/20119.
- React Native sliders produce randomly crashes. This has happened quite a few times in production and it is a known issue since 2016: https://github.com/facebook/react-native/issues/9979.
- There were some issues with ellipsis on text input and labels. We were using a lot of labels and text fields for which when the text was too big, we were displaying ellipsis at the end before truncating. But there seems to be some issues with ellipsis working on Android, as they are not being displayed. Issue reported here: https://github.com/facebook/react-native/issues/14845 and are still opened.
- There is no DateTime picker available for Android, only for iOS. Our app had several date-time picker components and when we had to implement it in React Native, we had to use separate components for Date and Time instead of just one.
- We had an integration with a 3rd party and we were loading an URL that had some dropdowns in a web view. On Android tablets, the dropdowns were not working: https://github.com/facebook/react-native/issues/12070. We ended up requesting the 3rd party to change the dropdowns into radio buttons.
- The ‘repeat’ property in order to create a pattern image did not work on Android. It was added in v0.56 (https://github.com/facebook/react-native/commit/0459e4ffaadb161598ce1a5b14c08d49a9257c9c).
These are only some of the issues that we had to deal with but React Native has currently more than 600 open issues.
The Framework is still in active development and expects changes that you would need to integrate into your app. This was hard for us as we were focused on shipping features and we were most of the times behind with the Framework upgrade.
After some time, you can get used to the new language, using Functional Programming over Object-Oriented Programming and more. But for us, one of the biggest challenges was debugging. Most of the time it was a real pain. In order to debug your React Native code, you would need multiple debug tools. Code flow inspection (breakpoints, object values evaluations, etc) would have to be done through the Chrome Developer Tools and the UI debugging (component hierarchy and layout) would be done using React Developer Tools. For native iOS development, XCode provides all the necessary tools and everything can be done easy and quick.
Here are a few examples of React Native vs XCode debugging:
- Breakpoints – In XCode you can add the breakpoints right along with your code, but for React Native you would have to find your code file in the Chrome Developer Tools. For a big project, with multiple nested folders and files, it can take you some time to navigate between them and add all your needed breakpoints.
- Hot Reload – React Native comes with a very nice feature called “Hot Reload”. It is meant to speed up the build time as it just reloads the piece of code you changed and it would not require a new app start. That sound nice, if it would actually work 90% of the time. Also, on many occasions, the React Native code won’t update correctly in the Chrome Debugger. On the other hand, in XCode, a new build is required, but you are sure all your changes get included in it and the debugger contains what you expect it will.
- Component Hierarchy – Even though React Developer Tools is easier to use than Chrome Debugger it still requires more work and time from you than XCode would. If you want to find the UI information of a certain view, you have to first enable a debug tool in your simulator, try and select the view you want to analyze and hope that its information will show up in the React Developer Tools app.
- Component layout – One nice thing about the React Developer Tools is the ability to customize certain UI properties of a component live. So if you want to find the correct layout formula for your requirements, you can give it a try in the debugger instead of rebuild after each change and hoping it will work as expected.
So in our opinion, even though XCode might also have some issues, it is still a more solid and reliable tool than the React Native Debug tools. Technology keeps moving towards faster and easier development so each development framework/language should have a strong IDE with integrated Debug tools that could speed things up.
The devices we used for running and developing the app were custom devices which are not found on the top/general market as Samsung, HTC etc. These devices are special devices for POS used in restaurants having Android 7.0.0 or 4.4 depending on its type.
On these devices, we encountered UI latency and slow responsiveness. I.e. when the user took an action the transition to the next screen was made very slow and took too much time. Another performance downfall was that by default, React Native re-renders the whole component trees. A solution would have been to specifically mention to only update the component in certain situations resulting in faster rendering and faster response of the device.
Other examples of performance downfalls, that we didn’t encounter but other developers are complaining of:
- “Swift application used less energy in total during the same testing than React Native app according to the Energy Usage Log” – https://codeburst.io/react-native-vs-real-native-apps-ad890986f1f
- “React Native app were lagging more than swift when I was resizing and dragging map” – https://codeburst.io/react-native-vs-real-native-apps-ad890986f1f
Our experience with React Native didn’t pass the point where to implement support for iOS platform too. This might be an interesting opportunity which will allow us to better compare the two platforms since we have more experience with iOS app development than with Android.
Another interesting thing to try would be to do some performance measurements (CPU, GPU, Memory) with both code bases, Objective C and React Native, and compare them.
At the end of our journey we would like to share a few pros and cons that we see with React Native:
- Cross-platform which should reduce the development time and the team size; although statistics say than you cannot share more than 30% of the code
- Hot reloading which allows you to inject new versions of the files while the app is running
- Still lacks some components which you will have to build yourself, also things that are trivial in native may be difficult and take a lot of time to implement in React Native
- Migrating native developers to React Native might be overwhelming for them
We also think that experienced mobile developers are best suited for writing seamless mobile applications in React Native, because they have a mobile-oriented thinking and can use their knowledge to write native modules. There are very few or maybe none complex applications that can be written without native code, especially if native features are used (sensors, camera, push notifications, etc).
As a conclusion, everyone should try cross-platform frameworks and make an opinion of their own. We don’t think that you should feel compelled to use React Native or Flutter if that doesn’t suit you.
Diana Loredana Mihai, Denisa Patricia Moldovan, Rares Laurentiu Soponar, Iulia Carmen Dragan