I've been planning to write this article for a while, but time is really short recently, especially with yesterday's Airbnb Trips launch, where the vast majority of the screens are using React Native. It was very exciting and challenging to accomplish that. Here, I'd like to talk about some of the pain points we stumbled on and how to fix them.
I was a full stack engineer in a previous life, but for the past few years, I've been a mobile engineer working exclusively with Android. It took me a while to get used to all of the terminology, tools, frameworks and idiosyncrasies that surround it. Being a technology born for the web, React brings the opportunity for front-end web engineers to be able to easily jump into mobile and ship beautiful and performant apps with little effort.
However, that is where the first problem lies. I see a lot of people who work with React Native have little to no little knowledge about mobile engineering and are not aware of its pitfalls. One piece of technology that you can't really escape from, even when doing React Native, is Gradle. It's the standard and official build system for Android, it's based on the language Groovy and is a pretty powerful and flexible tool. See, Gradle not a perfect build system, nor is it my favorite technology, but it gets the job done and it lets you customize your build scripts however you want by having a Groovy DSL.
React Native on the other hand, which is built by Facebook, now has an active community around it that has little to no knowledge about how Gradle works and what its best practices or standards are. A big source of this problem is the fact that Facebook uses Buck internally, so does relatively little to guide the community in this area.
1. Dependency Management
Gradle uses Maven under the hood to manage project dependencies. Like most dependency management software, it includes a central repository for downloading dependencies, called Maven Central. Despite being ugly and not user friendly (and not nearly as nice as the NPM website), it gets the job done really well and is a really mature and stable system (and, in my opinion, much better than NPM, but that's beside the point :)).
Here's where the first problem resides, though. Facebook doesn't distribute React Native Android artifacts through Maven Central. It rather uploads them as part of the NPM package ands expects users to point to node_modules to fetch their binaries. This is not the standard "Android way" of managing dependency, and is incredibly counter-intuitive and error prone.
To make matters worse, there are some really old versions of React Native on Maven Central (the latest one is 0.20). This, combined with the fact that they also encourage people to use a dynamic version (which is not recommended and dangerous) for React Native, causes lots of confusion to developers, especially the ones who are new to Android.
"You're wrong! I don't trust you!"
Ok, see for yourself then. I help maintain the react-native-maps repository and if you search for "Gradle" issues, you will find a lot of people very confused who are seeing runtime crashes and don't know why. That's why.
Don't use dynamic versions on Gradle dependencies.
I urge Facebook to reconsider the decision of not uploading React Native artifacts to Maven Central and start doing it. It would help for a more healthy ecosystem with fewer subtle bugs. Also, Facebook, please stop encouraging people to use dynamic versions. Thank you :)
Contrary to popular belief, defining an explicit version with Gradle is equivalent to a "greater than or equals to", instead of "exactly this version must be used". Example:
Your app's build.gradle file:
Project Foo's build.gradle file:
With this configuration, Gradle will resolve the version 0.35.0 of React Native for your app, instead of 0.34.0, because it is a transitive dependency of Foo with a higher version. By default, it will always pick up the highest version used across your entire dependency tree.
You can work around version conflicts by using a few tricks. Here are my favorites:
exclude module: 'react-native'
This way, you're saying: "whatever version of React Native Foo depends on, I don't care about it, don't bring it into my own dependencies."
transitive = false
This way, you're saying: "whatever stuff Foo depends on, I don't care about any of it, don't bring them into my own dependencies. That means React Native and anything else Foo depends on. If you use transitive=false or exclude module, you'll have to explicitly add a dependency on the excluded library or you'll get a runtime crash!
This is saying: "whatever version of React Native other libraries are using, I don't care, use this one instead".
2. Library project template
To be honest this is one problem I'm not exactly sure how to fix yet (mainly because I'm not as proficient with NPM as I am with Gradle), but we're planning on tackling this one as soon as possible and will update when we have it on Github!
With this article/rant, I'm hoping to clear up any misconceptions with Gradle and how it interacts with React Native as well as trying to raise awareness of these issues so that they can be addressed by the Facebook and the community. Unfortunately, some of these issues can only be addressed by Facebook, since other people cannot upload artifacts to Maven Central on their behalf, for example.
Anyways, I believe this is just the beginning of React Native and we're still in the early days of what is becoming an incredible ecosystem!