AndroidX, previously called the Android Support Library, is a
collection of libraries developed by Android upstream which are
bundled into an app at build time, rather than taken from the system
on the device. Some are quite central to a typical Android app.
Upstream has renamed the whole thing to AndroidX, and this goes
with a renaming of tons of classes. (Mostly to get them out of
`android.*`, which was always very confusing because that's the
same namespace used by the actual system packages.) Docs:
https://developer.android.com/jetpack/androidx/migrate
This could have been sticky, because lots of apps and lots of
third-party libraries refer to these core libraries, and in a
distributed ecosystem it's impossible for everything to update at
once. And because these are often so central, a given build of an
app must use the same version of them throughout -- including from
any third-party libraries it uses. So for example if some
third-party library has updated to use AndroidX, then an app cannot
switch to that version of that library until it too has updated to
use AndroidX.
Fortunately, the planners of this migration recognized this could
be a problem, and found a way to make the constraints run only in
that one direction. When an app updates to use AndroidX, it can do
so even while it's still using third-party libraries that are still
built to use the Android Support Library. The key is a tool called
"Jetifier", described in detail here:
https://developer.android.com/studio/command-line/jetifier
To use it, we set the flag `android.enableJetifier=true`, and then
(quoting the AndroidX migration doc linked above):
The Android plugin automatically migrates existing third-party
libraries to use AndroidX by rewriting their binaries.
So the migration path is, apps go first; then libraries update at
their leisure.
---
For us, the most important third-party library (third-party from an
Android perspective) is React Native. RN took the AndroidX update
in v0.60, so we have to do the same before we can complete that RN
upgrade, i.e. zulip#3548.
The Android upstream way to update a given project (as described by
the doc linked above) has two prongs:
(a) The project's own source code can be updated automatically
(barring fancy use of classloaders, etc.) by Android Studio,
with the feature "Refactor > Migrate to AndroidX...".
(b) Third-party libraries get updated when they're linked in, by
rewriting them as part of this project's build.
Both of those appear in this commit. But in an RN context, there's
one other important set of code: third-party RN libraries get
pulled in as part of *our* Gradle build, with build.gradle lines
using `project`, like so:
implementation project(':react-native-vector-icons')
This means they're not covered by (b). This contrasts with RN
itself, which is pulled in as a binary artifact:
implementation "com.facebook.react:react-native:+"
So for those third-party RN libraries, we need to edit the *source*
code, like (a), before it gets built. Happily, someone has written
a tool to do just that, and we use it:
(a') Third-party RN libraries get rewritten in place in
node_modules/, after `yarn` installs them and before the
Gradle build. This uses a different "jetifier" tool:
https://github.com/mikehardy/jetifier
Specifically, the latter "jetifier" will crawl node_modules; find all
.java, .kt, and .xml files; and apply a list of string substitutions
(about 1900 of them) to rewrite old names to new ones. That sure is
kind of heuristic... but it's good enough that it will in fact find
any normal import of the affected classes, and on the other hand the
patterns being substituted are all long boring fully-qualified Java
class names, unlikely to be found in spurious places. For us, I
also inspected (below) the exact edits the tool makes.
---
In this commit, we take the upgrade.
(a) I used "Refactor > Migrate to AndroidX..." in Android Studio.
This updated a few spots in our own source code.
(b) The same auto-refactor added the `gradle.properties` settings
(in particular enabling Jetifier, the Android-upstream tool)
that handle libraries we incorporate as binary artifacts,
including RN itself.
It also updated dependencies in our `app/build.gradle`. This
made the `supportLibVersion` variable unused, and I deleted it.
(a') We add `jetifier`, the NPM-world tool, as a dev dependency,
and invoke it in our `postinstall` hook. This means any run
of `yarn install` (or bare `yarn`) will end by running it.
I inspected manually (by making copies of `node_modules`, and
comparing with `git diff --no-index`) the effect of `jetifier`.
All the edits look appropriate. Excluding a few that have no
effect, the edits appear in the following packages:
react-native
react-native-image-picker
react-native-webview
react-native-photo-view
rn-fetch-blob
@react-native-community/netinfo
@unimodules/react-native-adapter
unimodules-image-loader-interface
expo-constants
expo-file-system
expo-permissions
(FTR the other edits were in react-native-notifications, which on
Android we don't use or build, and in react-native-device-info and
react-native-sound, limited to IDE metadata plus copies of parts of
the support library itself in build-intermediate directories.)
So once we've upgraded all of those to versions that use AndroidX
in the first place, the `jetifier` tool should become a no-op and
we can delete our references to it. (It won't go away entirely,
because RN v0.60 has `react-native run-android` run the tool; but
it'll stop mattering.)
I tested the app in both debug and release builds, and exercised
relevant dependencies: picked and uploaded an image; opened the
lightbox; downloaded an image there; entered and left airplane mode
to see the "No Internet connection" banner. Together those cover all
but the unimodules and expo dependencies; I'm not sure we actually
use each of those anywhere at all, but I exercised the core unimodules
machinery by going to the diagnostics screen to view the app version,
which uses an Expo unimodule. Everything I tested works.