React Native - Application and Criticism

Most often, when choosing this language, it is expected that the development of one application for two platforms will take half the time than the development of two applications. But in the end it turns out that development takes as much, if not more, because of the difficulties hidden under the outward brilliance and marketing. We will talk about some of the similar difficulties that we have encountered in the last few months of working with React Native.







React Native adapts Javascript for mobile development. This is achieved by the fact that he uses several collectors to build projects - Metro Bundler, which interprets the JS code and represents the resources and the collector of the target system. In our case, it was a gradle for Android. In theory, the React Native app should run quite simply. The react-native run-android command enables the Metro Bundler and builds the application for all connected Android devices and emulators.







In reality, it turned out that even at this stage there are difficulties. An error “Unable to download JS bundle” constantly appeared on our project, which meant that bundler could not translate the code into the native one. As it turned out later, due to the fact that it did not start. StackOverflow confirmed the hunch and suggested that you should run bundler in a separate thread using the react-native start command. This allows you to restart bundler only if package.json has changed, because the procedure does not slow down development much.







Package.json is a file containing a set of external modules for the application. At npmjs.com there are a large number of different libraries for React Native, expanding the functionality and simplifying development. Many libraries (for example, Firebase) use native functions, and therefore must be associated directly with native code. To do this, use the react-native link <library-name> command, which should configure these relationships with native code.







Due to the fact that all libraries are written at different times, they use different versions of the SDK and require a different approach. Sometimes it happens that the libraries are incompatible with each other, or the latest version of the library is experimental, and the developers themselves advise downgrading to the penultimate one. Quite often, link does not configure all the required dependencies. So, for the aforementioned firebase, you need to add many additional libraries in native code, connect various external repositories, modify mainApplication.java (and this is only for android!). For Firebase, there is a fairly clear instruction for performing these actions, but for other libraries it is not always present.







After the connections with the native code are configured, you can build the project in the hope that the connected library will work. When assembling, it is worth remembering that if you get an error, then you should make sure that it arose precisely because of your actions, and not because of a collector error. For complete confidence, you should perform the following sequence of actions:







rmdir node_modules /s /q && npm cache clean - force && npm i
      
      





This command will delete the node_modules folder and then reload it. This is one of the longest tasks, therefore it is worth using it extremely rarely. On some projects, node_modules can occupy up to several gigabytes on the hard drive, and therefore reinstallation will take time.







 rmdir android/app/build /s /q
      
      





During development, it was noticed that the often unsuccessful build is a consequence of the fact that the collector cannot create (or delete) the folder from the debug directory. This action solves the problem that react cannot delete the folder on its own. But at the same time, generating files for this folder from scratch again will take extra time.







 react-native start-reset-cache
      
      





Launch the Metro Bundler. This tab should remain open throughout the debugging process. If an error occurs, the error log may appear here. Most likely, if an error occurs, this process will end, and it will need to be restarted again.







 react-native run-android
      
      





Install the application on a connected device or emulator. Most build errors happen here, and some of them are understandable, but some are rather irrational and are "cured" by restarting the whole process.







Imagine the build process with a sequence of commands for one project (already having realm, redux, react-navigation, about ten more libraries) after connecting Firebase.







 react-native start react-native run-android >>    debug react-native run-android >> , metro bundler  react-native start react-native run-android >>    debug react-native run-android >>  !  metro bundler ,   JS-   react-native start >>   restart         - 
      
      





Needless to say, it takes a really long time? And this is not a one-time process: by the moment described, this procedure was required after almost every change in the program code. With each new library, the project becomes less and less stable, and this process can change, most often for the worse. Debugging an application is one of the most important functions for a developer, and in this case its speed decreases quite a lot.







Speaking of debugging. The React Native debugger has problems not only with launch. Correction of errors found as a result of the test is also a rather painful process. In react-native, the JS code is translated into Native code, but is obfuscated during translation. So if you don’t want to see errors like "null pointer exception in zzz.yyy ()", then you need to use the built-in debugger, you can’t just read exceptions in logcat. On error, the debugger displays a red "screen of death" with its description, more or less pushing towards the path of correction. But there are problems with this part.







Well, when the error looks like this:













It is really clear what is happening here - instead of the expected array object, the variable this.state.noteArray.map is undefined, which raises the notorious TypeError. You can fix it by switching to app.js: 14 and checking the value in this variable before use.







Worse when the error looks like this:













So:













Or so:













Images were taken from the Internet, but we saw them live. And despite the fact that they are shown in runtime, this error is not due to the fact that something was done incorrectly in your code. This may be due to the fact that you installed the library incorrectly, or if your imports have incompatible dependencies, or something went wrong in the native code, and React tries to catch the error. Each mistake is individual and is solved very differently. It’s good that there is StackOverflow and at least some kind of debugging mode.







Even worse, when the error is not reproduced in debug. We faced this situation when trying to build an application with a new version of React with support for x64-architecture for Android. When installing an application with a debugger, everything works fine. But as soon as we build the tester on the phone, everything stops working and breaks as soon as it comes to interacting with the database. To debug non-debugging in a hurry, we use console messages, which in this case were the react toastAndroid component. This component displays a short text on reaching a certain line of code. Methodically, preferably dividing the code in half, we localize the function in which the error occurs and find out that the Object.assign ({}, item) method does not work in the new version of React. It was lucky that it was possible to replace this function with a shorter {... item} while maintaining the functionality of the application, but the search for this error cost about a dozen hours of work.







After a small study was conducted in search of reasons. As it turned out, React Native uses different Javascript engines for interpreting JS code in debug and production versions: for debugging the Chrome JS engine, and in JavaScriptCore. Yes, React Native does not translate JavaScript into native code, but interprets as it runs. At the same time, the debugging engine works much more stable, and therefore bugs are increasingly sneaking into production. For example, this article shows how date formatting works under different conditions. Returning to the error: it turned out that after updating the React Native version, the production production web engine lost support for Object.assign (). But the debugging engine remained the same.







Perhaps the worst option is the case when the application breaks in random places, only in the production version and without any logs from React Native. Example: after installing the release version of the application on the phone, it "works for a while", and then "turns off without an error or warning at a random moment." Moreover, the error is not reproduced on all devices. In the end, by trial and error (and by detecting that the aforementioned Firebase Crashlytics does not send the corresponding errors), we managed to catch the fall logs that looked like this:













This text does not even apply to our application; it was not even marked in red. But after we got it and went to the forums, we found that the new version of React Native is broken. And the previous one was broken. On the official Issue Tracker, the error "Android crashes: signal 11 (SIGSEGV)" existed for two months, and to our luck two days before we turned there (!) An experimental solution was proposed that fixed the error.







It is ironic that some developers who had to deal with Android Studio were at a loss that the IDE has options such as build / clean project or file / invalidate caches. This is required in order to get rid of the abnormal behavior of gradle, from false error messages and warnings, from synchronization errors. The developers asked: "why should we do the work for our IDE, in such situations, these commands should be executed automatically." And they can be understood, but at the same time, modern IDEs do all the difficult work behind the scenes. And these developers simply did not work with React Native.







All of the above are isolated cases that have occurred over the past few weeks. Here we do not describe the complexity of running applications with Expo, with setting the code style in babel / eslint, we do not scold Javascript for excessive flexibility, we do not tell how debugging due to the redux / realm linkage was almost completely lost on one of the projects. Considering the described difficulties of support and development and the fact that for two systems it all multiplies by two, it’s worth considering whether React Native is really profitable? After we completed our third project in this language, we decided not. How do you think?








All Articles