Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AndroidX Migration Plan #129

Closed
matt-oakes opened this issue May 24, 2019 · 98 comments

Comments

Projects
None yet
@matt-oakes
Copy link
Member

commented May 24, 2019

Introduction

React Native 0.60 will move from using the Support Library to AndroidX. These are mostly the same library, however the artifacts names and package names have all changed. Google has stopped supporting the support library (ha) and will only be releasing updates to AndroidX.

Details from Google.

With this change, React Native apps will need to begin using AndroidX themselves. They cannot be used side-by-side in one app, so all of the app code and dependency code needs to be using one or the other.

A normal native app can use the Jetifier tool to migrate. This goes through all of their dependencies and changes any references to the support library to AndroidX.

Jetifier will not work for React Native projects as it only works on "packaged artifacts". Almost all React Native dependencies come in the form of a reference to the library source code. These are not migrated by Jetifier.

The Core of It

We are going to have lots of reported issues where developers Android apps will not build because some of their dependencies are using AndroidX and some are using the support library.

The "best" solution is for all libraries to release a version which has migrated to AndroidX and the app developer can then upgrade all of their dependencies at once. This would mean, however, that users who want to get bug fixes for that library would need to migrate their app to AndroidX. This, in turn, means that if just one of their dependencies is not migrated to AndroidX, they are locked into using <=RN 0.59 and cannot use new versions of libraries which have migrated.

Most React Native libraries do not need to use the support library, however, there are many that do and not all are actively maintained.

Discussion points

How can we help developers with this problem?

@mikehardy

This comment has been minimized.

Copy link

commented May 24, 2019

Thanks Matt!

the ideas I have seen so far are:

I'm interested in the AAR ideas but I'm unsure of the exact mechanics to generate an AAR, distribute it, and how to document AAR integration into a project.

@matt-oakes

This comment has been minimized.

Copy link
Member Author

commented May 24, 2019

convert module to AndroidX, then use some tool (bob?) to generate AAR for distribution and run reverse jetifier

Would this require changes to the way libraries are referenced in the build.gradle files? If so, this could be confusing to developers where Gradle is already pretty opaque and hard to understand.


Another idea I was to have a postinstall script which "migrates" dependency source using sed and a CSV mapping file which Google provides.

This could work, but also has the possibility to just break more things than it fixes.

@mikehardy

This comment has been minimized.

Copy link

commented May 24, 2019

I believe this would require different dependency referencing yes, but this is also something that CLI could likely handle in an unlink/link phase, reducing the problem set to consuming projects performing an upgrade (or can CLI run scripts on upgrade?)

If signaled by a semver major with instructions for projects that could not migrate, it might at least work. It's my understanding that we don't have any solution candidates that handle all cases, so even having this idea sussed-out to a "could at least work" status would be a positive.

@yeswanth

This comment has been minimized.

Copy link

commented May 27, 2019

The above solutions might be tough for someone not familiar with Android. I would also like to see a list showing which libraries are compatible, so that users can make a conscious call on whether they can safely upgrade to 0.60 or not. Maintaining this list might be difficult, but it could be a good official approach (not just for this particular use case, but can be useful for others too).

@mikehardy

This comment has been minimized.

Copy link

commented May 27, 2019

For unrelated reasons I just ended up chewing on a library dependency that had updated to AndroidX and it was unusable in a react-native 0.59 context. That library emitted an AAR so it was possible for me to reverse-jetify it (if we had the infrastructure for that to be easy for people) and that would have possibly solved my problem. However I had to fork another library dependency and update it then point package.json to my github fork and that made me think - even if we build an AAR reverse-jetify infrastructure (which I would argue would solve a lot of problems) people that have one foot across AndroidX (by using RN0.60+some reverse jetifier AAR magic) but one foot not across it (by relying on libraries that don't support it and don't provide any workarounds) will not be able to quickly fork+modify libraries that have gone AndroidX because any AAR+reverse-jetifier infra will necessarily be some post-processing that doesn't work in a package.json+git-reference context.

I consider this an edge case, but it is one that affects me so I thought it was worth mentioning. There may be nothing that can be done, some breaking changes are like that.

I'm still thinking AAR distributions and some reverse jetifier magic will help the largest number of people but I haven't proven that out yet as feasible or acceptable

@matt-oakes

This comment has been minimized.

Copy link
Member Author

commented May 28, 2019

Just to note that this is the sort of issue which will be opened if we don't find a way to make this migration work:

react-native-community/react-native-netinfo#107

@mikehardy

This comment has been minimized.

Copy link

commented Jun 3, 2019

In the linked issue from react-native-firebase above, a user commented that this allowed them to use react-native-firebase in an AndroidX app, I have not tested it but it might work?

UPDATE: this was tested (see below) and DOES NOT WORK. I wish it did, but it does not. I am leaving it here so we know what doesn't work, but don't waste your time (sadly)

Edit: For anyone else, the workaround is this:
Add gradle.properties into the android folder of each incompatible module, with this content:

android.useAndroidX=false
android.enableJetifier=false
@EskelCz

This comment was marked as outdated.

Copy link

commented Jun 3, 2019

@mikehardy it got me through react-native-firebase, react-native-google-signin and react-native-maps errors. For react-native-navigation it didn't work and I'm stuck, thinking about reverting the whole 'upgrade'. Only difference I see is that react-native-navigation has gradle wrapper inside it and disabled android.enableAapt2 because of some building issues.

I'm new to android so this whole thing drives me crazy. Seems like a move to promote flutter on part of google.

@mikehardy

This comment was marked as outdated.

Copy link

commented Jun 3, 2019

Shame it didn't work all the way - I wouldn't be so cynical though, AndroidX the layer beneath flutter I believe, and it's one of those painful ecosystem-wide migrations that need to happen once in a while. I'm not aware of anyone on the pure-Android side that has problems with it because in general jetifier works great, using dependencies by source inclusion is extraordinarily rare in the Android world...except react-native :-)

@matt-oakes

This comment was marked as outdated.

Copy link
Member Author

commented Jun 3, 2019

I’m going to look into this as it’s clear this is going to be a pretty big pain point for the 0.60 release. I’ll report back with any updates I have on progress.

@matt-oakes

This comment was marked as outdated.

Copy link
Member Author

commented Jun 4, 2019

@mikehardy Just to note, that workaround does not work. I've just tested on my machine with the NetInfo module and I get the same results as without it.

@mikehardy

This comment was marked as outdated.

Copy link

commented Jun 4, 2019

Dang - thanks for checking @matt-oakes - not happy to hear it didn't work but always happy to hear concrete reproducible results - I will edit the comment so as not to steer anyone wrong

@rozPierog

This comment has been minimized.

Copy link

commented Jun 4, 2019

Hi guys,

the jetifier only works on packaged artifacts

So no way that it gonna change source code of our apps, and React-Native modules/packages are treated in Android as part of a source code, not as libraries. Only way to move RN package to AndroidX is to update imports, and gradle to new androidx.* format. But this will break compatibility with older versions of RN (<0.60) so I would recommend creating new major version of library with AndroidX support and annotate it with Breaking Changes explaining why it wouldn't work on RN <0.60.
I was researching jump to AndroidX for the last few month so if you have any questions I'll try to answer them

@mikehardy

This comment has been minimized.

Copy link

commented Jun 4, 2019

Only way to move RN package to AndroidX is to update imports, and gradle to new androidx.* format. But this will break compatibility with older versions of RN (<0.60)

I was the under the impression compiling libraries pre-distribution into AARs, and then distributing them would allow the jetifier to work while maintaining backwards compatibility from a single (not branched) code line (though it would be a breaking change as people updated how they included the library).

I'm not aware of concrete results showing whether we could migrate a library to AndroidX then compile an AAR and reverse-jetify it and include that in a non-AndroidX/pre-RN0.60 app, but in theory it should work and just needs proving (then if proved, tooling)

My purpose in making this point is to see if there is some way to have a single non-branched library codebase service both sides of the RN0.60/AndroidX divide

@rozPierog

This comment has been minimized.

Copy link

commented Jun 4, 2019

I was the under the impression compiling libraries pre-distribution into AARs, and then distributing them would allow the jetifier to work while maintaining backwards compatibility from a single (not branched) code line

That… should actually work. Let me test this right now

@mikehardy

This comment has been minimized.

Copy link

commented Jun 4, 2019

@rozPierog I am really excited to hear results, either way. Here is the inspiration for the "reverse jetify" idea, in case a library moves to AndroidX but wants to produce an artifact consumable in non-AndroidX apps: https://ncorti.com/blog/jetifier-reverse

@rozPierog

This comment has been minimized.

Copy link

commented Jun 4, 2019

It seems to work! I've tested it with app on RN 0.59 and react-native-netinfo migrated to AndroidX and witf RN 0.60-rc.0 and react-native-netinfo migrated to AndroidX. It was pretty straightforward process. Only downside that I see is that it requires additional tooling, jetifier-standalone, and something to create build.gradle with

configurations.maybeCreate("default")
artifacts.add("default", file('react-native-netinfo.aar'))

but other than that I think it might work.

What I've done:

  1. Open react-native-netinfo in Android Studio
  2. Refactor > Migrate to AndroidX
  3. Gradle sync
  4. ./gradlew assemble in react-native-netinfo android project folder
  5. download jetifier-standalone
  6. extract it to convenient place
./jetifier-standalone/bin/jetifier-standalone -r -i react-native-netinfo/android/build/outputs/aar/android.aar -o react-native-netinfo.aar
  1. In a folder where now react-native-netinfo.aar is located create build.gradle with
configurations.maybeCreate("default")
artifacts.add("default", file('react-native-netinfo.aar'))
  1. open setting.gradle of your RN app and add
include ':react-native-netinfo'
project(':react-native-netinfo').projectDir =
        new File(rootProject.projectDir, '../../../RNN')

where '../../../RNN is relative path to your .arr + build.gradle folder
10. In RN apps app/build.gradle add

dependencies {
    implementation project(':react-native-netinfo')
...
}
  1. Done!

It seems that it can be fully automated with simple script

Would love if someone repeated those steps and reported if it's working for his project as well

@matt-oakes

This comment has been minimized.

Copy link
Member Author

commented Jun 4, 2019

@rozPierog I experimented with that as well. If a library included a file like react-native-netinfo.aar in the android folder of their NPM package, would the linking code need to be updated at all to pick it up? From your message above steps 9 and 10 suggest it will work with the same code as before but will pick up the car if available. Is that correct?

If so, then we can just ask that libraries add the code to generate a reverse jetified AAR file in their NPM package to support both AndroidX and the support library. This won't be needed for all libraries as only some currently make use of the support library.

@rozPierog

This comment has been minimized.

Copy link

commented Jun 4, 2019

@matt-oakes I have a feeling that everything is dependent on build.gradle that is inside projectDir if we move .aar to root of android project folder there will be conflict of build.gradles, maybe, maybe, adding those 2 lines to main build.gradle would work, but I don't really know. It seems that the best option is to create something like library-name/android/aar where you can store .aar with build.gradle. But that's just a speculation. I cannot check if thats true right now.

@mikehardy

This comment has been minimized.

Copy link

commented Jun 4, 2019

Sounds like there are some subtleties to work out, but is it possible to summarize this as "if a library, using AndroidX or support libraries, builds an AAR in the right way, a react-native app using either AndroidX or support libraries can use it". i.e., this can work for all cases?

If so, this is a tremendous result and would motivate the tooling if any was needed. Excellent @rozPierog and @matt-oakes

step 5 and 6 - "download jetifier-standalone and extract it etc"

I just did this to make it easier, I hope? https://www.npmjs.com/package/jetifier (after npm i jetifier, npx jetifier-standalone should be available). So hopefully playing around with this is easier for people

I haven't tried the steps 7-10 yet but I will say that as a library consumer, if I convert my project to AndroidX I am most likely already following a set of instructions. If there was something I needed to alter with my existing dependencies I would not be surprised at all. If there was a script to do it, better of course, but not vital so long as it is possible

As a library maintainer, if there was a package I could use that automated AAR construction (optionally with de-jetification, or maybe always both with standard suffixes?) that verified I had the proper stuff I would be thrilled. Seems like bob should do it (or similar)

@rozPierog

This comment has been minimized.

Copy link

commented Jun 4, 2019

Thanks for uploading jetifier to npm!
I've created pure gradle script that automates whole thing can't really tell if this will be good approach or not.

@mikehardy

This comment has been minimized.

Copy link

commented Jun 4, 2019

@rozPierog ! working code is always the winning approach :-). And that code looks good to me

Might be best to package this up as well, maybe we could add it to the jetifier thing I just put together (I can give you all the github and npm perms there - update: I just went and sent you the invites, I hereby trust you haha) and we can document that people should include the reference and just call the method in their gradle scripts vs copy?

That would let the community collaborate on the implementation. This could become important as the blog post on reverse jetifier I linked mentions that sometimes the reverse process requires disambiguation, so maybe there will need to be some config mechanism or something. Or maybe not, but if it was distributed as part of an npm package then everyone could benefit.

@mikehardy

This comment has been minimized.

Copy link

commented Jun 4, 2019

I would just change the jetifier package version scheme to use real semver so we had space to work, vs my current / first-try style where it's just matching upstream now

@rozPierog

This comment has been minimized.

Copy link

commented Jun 4, 2019

Thanks for the invites! Sadly that's all the time I have today for open source work so this is my closing statement:

Some good news some bad news:

  • Good:
    I've just tested de-jetified aar on clean RN 0.60 project and it seems to be working (previous test was on updated project)
    update: or not? I have some errors regarding AppState, don't know if this is related
  • Bad:
    Shiny and new Autolinker need to be modified to accommodate libraries with aars. Therefore we need to standardize localization of aar within android project folder.

I feel like we should promote this discussion on react-native-community/releases#116 so that more people would help/look at this problem/solution.
I'll have very limited time in the next two days so I would appreciate if someone would help with this (move gradle script (or rewrite it to something else, like pure shell or js) to jetifier repo, made documentation on how and when to use it)

@mikehardy

This comment has been minimized.

Copy link

commented Jun 4, 2019

anyone that wants to collaborate - but no pressure, and I think we have time - a couple weeks? - just let me know and I can send invites to the jetifier repo. Or do your own package or whatever works. I also have limited time, but I'm obviously interested. I'll cross-link to the releases issue and I know @kelset is busy herding cats there already, and is here. Tremendous work @rozPierog

@khat33b

This comment was marked as outdated.

Copy link

commented Jun 24, 2019

@mikehardy

This comment has been minimized.

Copy link

commented Jun 24, 2019

@khat33b okay - I’d like to see the make-demo.sh modification that produces the error, not a generated repo - I’ve logged khat33b/rn-androidx-demo#1 - let's discuss it there, and once we have something we can carry it back here

@FrankGoortani

This comment has been minimized.

Copy link

commented Jun 25, 2019

I'm stuck with this and need to do a release on my app. Is there a way NOT TO MIGRATE to Androidx? my dependencies (including firebase) already have androidx references and they are breaking my app?

@mikehardy

This comment has been minimized.

Copy link

commented Jun 25, 2019

@FrankGoortani sure there’s a way -> do not upgrade those libraries. If you upgrade to the newest firebase libraries for instance, you must move to AndroidX. So don’t do that. Use the versions immediately prior to the upgrade. Similarly, for any other libraries that have started converting their dependencies to AndroidX, do not bump those versions.

@laurent22

This comment has been minimized.

Copy link

commented Jun 25, 2019

@FrankGoortani, I think you have two options:

@laurent22

This comment has been minimized.

Copy link

commented Jun 25, 2019

@mikehardy, I think the issue is mainly for people who use react-native-notification. Even if they don't explicitly upgrade to AndroidX, rn-notification is going to use the latest Firebase. So a solution is to lock the dependencies as mentioned in the Stackoverflow answer.

@mikehardy

This comment has been minimized.

Copy link

commented Jun 25, 2019

This is an error in the library and should be fixed with a pull request that allows you to specify a version in your app/build.bradle ext block: https://github.com/wix/react-native-notifications/blob/master/android/build.gradle#L24 - example https://github.com/react-native-community/react-native-camera/blob/master/android/build.gradle#L78

@JofBigHealth

This comment has been minimized.

Copy link

commented Jun 25, 2019

We've had some success doing the following on RN 0.59.x

Switch android/app/build.gradle to use androidx

E.g. using the migration table here to convert the legacy build artefacts to the AndroidX equivalents

-   implementation 'com.android.support:appcompat-v7:27.1.1'
-   implementation 'com.android.support:support-v4:27.1.1'
-   implementation 'com.android.support:support-media-compat:27.1.1'
-   implementation 'com.android.support:multidex:1.0.3'

+   implementation 'androidx.appcompat:appcompat:1.0.2'
+   implementation 'androidx.legacy:legacy-support-v4:1.0.0'
+   implementation 'androidx.media:media:1.0.1'
+   implementation 'androidx.multidex:multidex:2.0.1'

Change 1st party code to use AndroidX

E.g.

- import android.support.v7.app.AppCompatActivity;
- import android.support.annotation.Nullable;

+ import androidx.appcompat.app.AppCompatActivity;
+ import androidx.annotation.Nullable;

(Maybe not needed) Patch gradle.properties to turn off androidx for those that don't support it

A postinstall that adds

android.useAndroidX=false
android.enableJetifier=false

To all relevant instances of

${rootDir}/node_modules/some-react-native-project/android/gradle.properties

We did this for react-native-fast-image prior to discovering jetifier so it may not be necessary.

Run jetifier/jetifire as part of postinstall

yarn add jetifier --dev
npx jestify

Hopefully that helps someone even if it might be slightly off topic.

@FrankGoortani

This comment has been minimized.

Copy link

commented Jun 25, 2019

Thanks @laurent22 and @mikehardy for your response! I'm trying them now. FYI, my problem has been with @react-native-community/netinfo

@mikehardy

This comment has been minimized.

Copy link

commented Jun 25, 2019

@JofBigHealth I think you are getting lucky with the combination of jetifier and your postinstall de-jetifier of projects. If those projects bundle libs the standard (gradle plugin) jetifier won’t transform them, but the npm source jetifier will have transformed their source code. If you test it and it works okay, but seems iffy to me. Did you add that “remove jetifier from react-native modules gradle” step in response to something specific? An actual library you could point to that I could add to https://github.com/mikehardy/rn-androidx-demo/blob/master/make-demo.sh

@JofBigHealth

This comment has been minimized.

Copy link

commented Jun 25, 2019

@mikehardy Thanks for the script - very clear example. Interesting to see so many people manipulating code with sed and postinstall. I don't have time to produce something similar right now but I'll see if I can fit it in towards the end of the week using the third party libs we use in our project.

Did you add that “remove jetifier from react-native modules gradle” step in response to something specific?

The gradle.properties patch might not be required; it was a step I wrote prior to running jetifier in order to get react-native-fast-image working and I've not subsequently tested it without that change. I'll update my comment to reflect the uncertainty.

@mikehardy

This comment has been minimized.

Copy link

commented Jun 25, 2019

Some new learning in combination with react-native 0.60. I no longer see failures with AndroidX + jetifier + RN0.59. But with AndroidX + jetifier + RN0.60 I see errors sometimes, like this one with react-native-maps: react-native-community/react-native-maps#2927

Some packages that directly depend on the appcompat support libraries will need to have the whole library name overridden in android/build.gradle ext{} / safeExtGet{} style apparently. That can be patch-packaged and libraries can be patched, but that’s unexpected. Not sure what to do about that, but I think I can get RN0.60 working with about any non-migrated libraries now at least, with that style.

@mikehardy

This comment has been minimized.

Copy link

commented Jun 25, 2019

Library / module maintainers: It just occurred to me that you should update your issue templates to ask if the app has converted to AndroidX in the android issue section. @kelset can that go in the .github stuff? I know nothing of that

@FrankGoortani

This comment has been minimized.

Copy link

commented Jun 25, 2019

@laurent22 , @mikehardy Thanks again. With locking the versions and checking the Androidx references, I managed to bring my app up again!

Run this inside android folder and there should be no result:
./gradlew :app:dependencies|grep androidx
inside gradle.properties:

googlePlayServicesVersion=16.1.0
firebaseVersion=17.6.0
android.useAndroidX=false
android.enableJetifier=false
@khat33b

This comment has been minimized.

Copy link

commented Jun 27, 2019

For people facing library "libjsc.so" not found error, the solution is in this link.

Add the following code in your app/build.gradle:

// On top of the file before the android block
def useIntlJsc = false
// inside dependencies block
 if (useIntlJsc) {
        implementation 'org.webkit:android-jsc-intl:+'
    } else {
        implementation 'org.webkit:android-jsc:+'
    }
@mikehardy

This comment has been minimized.

Copy link

commented Jun 30, 2019

@matt-oakes @kelset I think this might be close-able?

Jetifier just took a pull request that results in sub-second times for most normal projects and we haven't seen a demonstrated failure in a few days. It has a nice test suite matrix for all supported node versions and linux and macos at least (sorry windows, but we do know it works there)

I'm using it in reverse mode on a work project even :-), and it is documented for common user and maintainer problems, with an active partner (@m4tt72) that has github and npm access and 2 backups (which is good, because I'm going to go on vacation for most of July)

The general plan is:

  • release RN0.60 with the AndroidX breaking change
  • instruct users to add jetifier as a devDependency, run it once as npx jetify, and hook it in package.json postinstall via call to 'jetify'
  • that's it

Further instructions for minority-usage are for people that stay on RN0.59 but their libraries start moving:

  • install jetifier as a devDependency, run it once with '-r' flag, and hook it in package.json postinstall via call to 'jetify -r'
  • that's it

🤞

@matt-oakes

This comment has been minimized.

Copy link
Member Author

commented Jun 30, 2019

Agreed. Closing this now 👍

@matt-oakes matt-oakes closed this Jun 30, 2019

@kelset

This comment has been minimized.

Copy link
Member

commented Jul 1, 2019

Perfect, thanks for the help everyone!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.