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 · 105 comments
Closed

AndroidX Migration Plan #129

matt-oakes opened this issue May 24, 2019 · 105 comments
Labels
🗣 Discussion This label identifies an ongoing discussion on a subject

Comments

@matt-oakes
Copy link
Member

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
Copy link

mikehardy 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
Copy link
Member Author

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
Copy link

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
Copy link

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
Copy link

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
Copy link
Member Author

matt-oakes 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-netinfo/react-native-netinfo#107

@mikehardy
Copy link

mikehardy 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 has been minimized.

@mikehardy

This comment has been minimized.

@matt-oakes

This comment has been minimized.

@matt-oakes

This comment has been minimized.

@mikehardy

This comment has been minimized.

@rozPierog
Copy link

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
Copy link

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
Copy link

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
Copy link

@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
Copy link

rozPierog 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
Copy link
Member Author

@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
Copy link

@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
Copy link

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
Copy link

rozPierog 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
Copy link

mikehardy 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
Copy link

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
Copy link

rozPierog 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
Copy link

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

@FrankGoortani
Copy link

@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
Copy link

khat33b 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
Copy link

mikehardy 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
Copy link
Member Author

Agreed. Closing this now 👍

@kelset
Copy link
Member

kelset commented Jul 1, 2019

Perfect, thanks for the help everyone!

@WebMobi59
Copy link

I've failed to build react native project after migrating to AndroidX. I followed the @mikehardy 's guide.

image

Here is my package.json file.

{
  "name": "ujama",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "react-native start",
    "flow": "flow; test $? -eq 0 -o $? -eq 2",
    "lint": "eslint"
  },
  "standard": {
    "parser": "babel-eslint"
  },
  "dependencies": {
    "@babel/core": "^7.5.4",
    "@babel/plugin-proposal-decorators": "^7.3.0",
    "@babel/plugin-proposal-function-bind": "^7.2.0",
    "@babel/runtime": "^7.5.4",
    "@expo/ex-navigation": "git+https://github.com/hmolotsi/ex-navigation",
    "@react-native-community/netinfo": "^1.4.0",
    "@ungap/url-search-params": "ungap/url-search-params",
    "babel-plugin-transform-flow-strip-types": "^6.22.0",
    "babel-preset-react-native-stage-0": "^1.0.1",
    "buffer": "^5.2.1",
    "email-validator": "^1.1.1",
    "flow-bin": "^0.48.0",
    "jetifier": "^1.6.3",
    "lodash": "^4.17.11",
    "lottie-react-native": "^3.0.4",
    "moment": "^2.24.0",
    "native-base": "^2.12.1",
    "parse-address-string": "^0.0.3",
    "prop-types": "^15.6.0",
    "pubnub": "4.20.1",
    "query-string": "^6.1.0",
    "react": "16.8.6",
    "react-addons-create-fragment": "^15.6.2",
    "react-native": "^0.60.3",
    "react-native-alphabetlistview": "git+https://github.com/i6mi6/react-native-alphabetlistview",
    "react-native-autocomplete-input": "3.5.0",
    "react-native-background-fetch": "2.5.4",
    "react-native-boundary": "^1.0.5",
    "react-native-button": "^2.3.0",
    "react-native-cached-image": "1.4.3",
    "react-native-calendar-events": "git+https://github.com/samratshekhar/react-native-calendar-events",
    "react-native-calendars": "^1.17.2",
    "react-native-camera": "^3.0.0",
    "react-native-communications": "2.2.1",
    "react-native-contacts": "4.0.2",
    "react-native-country-picker-modal": "^0.7.1",
    "react-native-credit-card-input": "^0.4.1",
    "react-native-device-info": "^0.16.0",
    "react-native-fast-image": "^5.2.0",
    "react-native-fbsdk": "^0.8.0",
    "react-native-firebase": "4.3.8",
    "react-native-form": "^2.1.2",
    "react-native-geocoding": "0.2.0",
    "react-native-gifted-chat": "0.3.0",
    "react-native-google-places-autocomplete": "^1.3.9",
    "react-native-google-signin": "^1.2.1",
    "react-native-hyperlinked-text": "^1.0.2",
    "react-native-image-crop-picker": "0.24.1",
    "react-native-image-fit": "^0.9.10",
    "react-native-image-view": "^2.1.4",
    "react-native-image-zoom-viewer": "^2.2.25",
    "react-native-loading-spinner-overlay": "^0.5.2",
    "react-native-maps": "0.25.0",
    "react-native-material-ui": "^1.22.0",
    "react-native-mauron85-background-geolocation": "^0.5.2",
    "react-native-modal": "^5.1.0",
    "react-native-modal-datetime-picker": "5.1.0",
    "react-native-pathjs-charts": "0.0.34",
    "react-native-photo-browser": "git+https://github.com/hmolotsi/react-native-photo-browser",
    "react-native-progress": "^3.4.0",
    "react-native-prompt-android": "^0.3.3",
    "react-native-qrcode-scanner": "^1.0.1",
    "react-native-qrcode-svg": "^5.1.1",
    "react-native-rate": "^1.0.9",
    "react-native-shared-preferences": "1.0.0",
    "react-native-sms": "^1.5.2",
    "react-native-snap-carousel": "3.8.0",
    "react-native-swipeout": "^2.3.3",
    "react-native-tableview-simple": "git+https://github.com/Hanumanthraya/react-native-tableview-simple",
    "react-native-vector-icons": "^6.4.1",
    "react-native-webview": "^5.8.1",
    "react-redux": "^5.0.7",
    "realm": "^2.24.0",
    "redux": "^3.7.2",
    "redux-actions": "^2.2.1",
    "rn-fetch-blob": "^0.10.15",
    "sendbird": "3.0.97",
    "utf8": "^3.0.0"
  },
  "devDependencies": {
    "@babel/plugin-proposal-function-bind": "^7.2.0",
    "@react-native-community/eslint-config": "^0.0.5",
    "babel-eslint": "^7.1.1",
    "babel-jest": "^24.8.0",
    "babel-polyfill": "^6.26.0",
    "babel-preset-react-native": "^1.9.1",
    "eslint": "^6.0.1",
    "eslint-config-airbnb": "^14.1.0",
    "eslint-plugin-import": "^2.16.0",
    "eslint-plugin-jsx-a11y": "^3.0.2",
    "eslint-plugin-react": "~6.9.0",
    "eslint-plugin-react-native": "3.1.0",
    "jest": "^24.8.0",
    "jest-react-native": "^18.0.0",
    "metro-react-native-babel-preset": "^0.55.0",
    "react-test-renderer": "16.8.6"
  }
}

Here is what I did.

  1. Migrate to AndroidX via Android Studio 3.4.2
  2. npx jetify
  3. react-native run-android

How can I migrate to AndroidX successfully?

@rezasazesh
Copy link

@WebMobi59 looking at the error, do you have the following code inside android/app/build.gradle?

either

android {
  ...
  defaultConfig {
    ...
    missingDimensionStrategy 'react-native-camera', 'general' <-- insert this line
  }
}

OR

android {
  ...
  defaultConfig {
    ...
    missingDimensionStrategy 'react-native-camera', 'mlkit' <-- insert this line
  }
}

looks like the 'general' or 'mlkit' is not specified for react-native-camera

@jdnichollsc
Copy link

jdnichollsc commented Aug 29, 2019

@matt-oakes @mikehardy As we discussed before to add gradle backward-compatibility, It would be awesome if the maintainers of RN plugins can add these changes: https://twitter.com/jdnichollsc/status/1162451046544879616

The original discussion is here: proyecto26/react-native-inappbrowser#96

Also, maintainers please remove these properties from the plugins:

android.useAndroidX=true
android.enableJetifier=true

Because the idea is to enable that from the project itself.

Let me know what you think and thanks guys! 👍

@pnramya

This comment has been minimized.

@mikehardy
Copy link

@pnramya sorry, that has nothing to do with the AndroidX Migration Plan, which is complete at any rate. You should probably look for support with react-native-dates

@react-native-community react-native-community locked as resolved and limited conversation to collaborators Oct 18, 2019
@elicwhite
Copy link

Does this still need to be a pinned issue on this repo?

@kelset
Copy link
Member

kelset commented Jan 8, 2020

probably not, AndroidX has been out for a while now

@elicwhite elicwhite unpinned this issue Jan 8, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
🗣 Discussion This label identifies an ongoing discussion on a subject
Projects
None yet
Development

No branches or pull requests