React Native Packages Woes
As I hinted at in my previous post - I've been working in React Native more in more in both work and hobby over the past few years. What I've been finding as more increasingly stressful is how often an issue lies in a package instead of my code in the React Native ecosystem in comparison to say like the Laravel ecosystem.
This could be entirely my fault, but I want to run through some situations I encountered to try and understand why this happens.
If we start with GPS - I learned that Geolocation was leaving the core of React Native and instead urged to find a package for this functionality elsewhere.
Additionally, in response to community feedback about new App Store policy, Geolocation has been extracted.
Announcing React Native 0.60
So I wanted to find an alternative plugin and landed on what was recommended.
The package linked was having reports of ~90,000 downloads a month. So I was quite confident that this package would be a good replacement for what I needed due to the popularity in comparison to the others. It didn't have many reported issues and looked quite active. Not to mention if the global React Native community was converging on a community solution this would lead to a more stable solution.
A few weeks later I'm investigating the strangest issue that folks could not obtain GPS coordinates while in airplane mode. Since GPS is more of a receiver than a literal outbound request - it should work in airplane mode.
Long story short - it was a bug in the library. I was happy that it was getting resolved, but my issues did not end there. I could get a location at coordinate x and a few hours later after walking around an island get the same exact coordinate when another location was requested.
I figured that was a logic issue I created, but after a few months of recurring issues I could not resolve anything and it felt every upgrade to the GPS package was a dice roll if something unrelated would break. So I started digging into this package more so than my original investigation and some discoveries were made.
- It has 230 commits total.
- 188 commits from one author.
- 20 commits from a robot.
- 20 commits total from every other contributor combined.
This package is effectively a one man show that is probably used by hundreds of hundreds of businesses and people alike. I figured a package with such large usage would just be a bit more stable. Am I at fault for this in both a business and hobby aspect?
- For business - should we fund all packages we depend on that are crucial to an application's success?
- For hobby - should I try and contribute more than just issue reports and attempt to fix actual issues.
In this case - I took a different option and just switched packages to a paid alternative. I did this because I figured GPS from a constantly updating OS on Android/iOS was best left to a company who specialized it.
In another case I'm starting with an existing application that we inherit and I'm trying to triage why this application is not yet using Hermes. Hermes was 2 years old at this point - so I was a bit surprised to see it still using the older JSC runtime. I end up tracking it down to an dependency the application used.
react-native-queue uses realm which did not support Hermes till v11. So I believe this is a simple fix, but it appears react-native-queue is abandoned without a commit since 2018.
With some more research - this project only has 5 source files in total. So after ranting previously about how small projects should be more stable - I'm surprised to find this abandoned project has 30 issues and 15 pending pull requests with only 5 source files!
In the defense of this project, most "issues" are people begging for support instead of a true bug. So this time because this library has no native components and quite small I think leaving this package shouldn't be needed. So I engage with some discussion internally and with the community and we (work) end up forking it.
So we had 3-4 years of tech debt to fix.
- We added some GitHub Actions for CI.
- Upgraded to Realm 11
- Fixed some documentation and type issues.
- Linted entire codebase.
So in the case of this project - we forked and patched and kept using it.
Lastly, I want to bring up a package react-native-fs, which is 1 of 3 popular packages for file system operations I could find.
The linked package has not only file system operations, but a complete upload/download support. Which makes me believe the feature set has become so large that it explains the open ~500 bugs on the project.
However, I pick an issue and see a PR merged years ago for a section of code I'm investigating and its still left open. Now I understand more and more why people use robotic accounts for closing/merging tickets after PR merges. It doesn't seem that a clean bug tracker is a pillar for this maintainer.
So I'm back to investigating a reported issue where every once in awhile our copy file line just duplicates the last copied file. So I find the above pull request as the last pull request to change the block of code where I believe the issue is occurring.
My Android development is weak, but I understand the authors intent with merging something to move an expensive operation to another thread.
However, I start wondering why we are yielding during background thread processing. It seems like without extreme care with scoped/thread-safe variables that we could cause our threads to trade priority and mix and match variables.
So I remove with that line with a patch and it ended up solving our issue immediately. So I upstream that patch in form of a pull request and move on.
The pull request reminds me of some mistakes I made in Apktool in the beginning. Someone gives you a pull request and looks confident in a solution in an area you might not be. Soon you have code you didn't write and don't understand why its written in a certain way. This may have been the situation that led to this bug.
I do start wondering though if there is a better way for file-system operations. Like say we just take the most popular native implementations of file operations (square/okio - Android, ? - iOS) and write a wrapper for React Native. Would that be more stable than a highly basic and tested library for a few basic file operations? I'm not positive, but its worth the thinking.
Maybe this is just the fault of the React Native ecosystem. Think about it - a native library for React Native will have a complex iOS/Android build system, linking code from JavaScript to native and some various iterations of new/old architecture and Hermes/JSC.
With all that effort - how often do you see testing for Android, iOS and JavaScript within a library?
I know I rarely see it - since the effort to do real native testing isn't easy. Maybe the burden moves to React Native itself. Could they enable and produce an easier method for libraries to produce automated tests at the end-to-end level?
Perhaps its the development experience for libraries or a lack of knowledge on my part. You can open Android Studio and Xcode and still are left with a library that cannot find the React imports. Yes they will be there when your application is built, but boy it would be helpful to have this properly working during library development for the IDE support.
Perhaps its the debugging experience. How painful is it to attempt debugging native code and/or JS code at the same time? I know I can debug JS pretty easily, but even that is a bit of a challenge with all the async threads jumping in and out of a bridge to native.
Perhaps its a bit of all those things combined that results in React Native packages that are a bit less stable and maintained than I'd prefer.
I'll keep doing my little part of putting up pull requests and forking abandoned repositories when required.