react-native-community/discussions-and-proposals

[Android] Upcoming changes for libraries in React Native 0.73

cortinico opened this issue ยท 34 comments

TL;DR

React Native 0.73 will depend on Android Gradle Plugin (AGP) 8.x. This will require all the libraries to specify a namespace in their build.gradle file. We added a compatibility layer for libraries that haven't specified a namespace, but please consider updating your libraries nonetheless.

Details

I'd like to share some of the upcoming changes that will happen in 0.73, which is still a bit far, but we'd rather start earlier rather than later.

React Native 0.73 will depend on Android Gradle Plugin (AGP) 8.x, which brings a lot of improvements for Android apps but also a series of notable changes.

Most importantly:

Specifically, the last change is a breaking change and will make libraries that are not specifying a namespace incompatible with React Native 0.73 (your project won't build).

Support for namespace was added in AGP 7.3.x, which ships with React Native 0.71. Libraries that published a new version with a namespace declared for 0.71 or 0.72 don't need further update. So we invite library authors to do those changes as soon as possible so by the time 0.73 is out, most of the apps are adapted.

What you need to change

Library authors will have to update their android/build.gradle file as follows:

android {
+   namespace = "com.example.mylibrary"
    ...
}

and remove the package definition from their AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.example.mylibrary">
+          >
...
</manifest>

Compatibility Layer

We added a compatiblity layer inside React Native 0.73, that will make sure that namespace is configured for each libraries, even if you don't update the library. This will make sure 0.73 works also with older libraries that are not receiving may updates.
Still consider updating your library to keep up to date with the Android best practices.

Further reading

Official Google documentation on namespaces is here.


Please update your libraries whenever possible, adding the namespace declaration and let us know if this is creating any issues for you.

@cortinico Does removing the package="com.example.mylibrary" part affect support for older versions of React Native?

@cortinico Does removing the package="com.example.mylibrary" part affect support for older versions of React Native?

I tried to patch react-native-bootsplash package. it's work (RN 0.71.8)

@MasonLe2497 What about versions older than that? RN 0.71 is already the latest available release. What about versions before 0.69?

@a7medev I verified this by using updated version of Repack in the Release & Debug builds of apps using RN71.x and RN67.x. So far no problems :)

EDIT: I take that back โ€“ I messed up while pointing to the new local build of Repack in RN67.x project. In RN67.x the build of the test app using Repack version with this change fails with the following bug (which, tbh, makes sense):

image

In the generated PackageList.java file instead of a valid import of Repack, undefined is placed as a package namespace:

- import com.callstack.repack.ScriptManagerPackage;
+ import undefined.ScriptManagerPackage;

This then leads to the next error: cannot find symbol ScriptManagerPackage

@cortinico which min RN version can use this change?

Hey everybody, I double-checked the compatibility of this change with older versions of React Native, and unfortunately, it's not compatible with RN67.x. Sorry for the confusion ๐Ÿ˜ž . I already updated my old comment with details of what exactly fails.

This looks like a breaking change, and libraries should release a new major version targeting RN71 and higher.

@RafikiTiki thanks for coming back with the feedback. This is likely due to the RN CLI not able to parse namespace for RN 0.67, as it was added for CLI v10 which targets RN 0.71: react-native-community/cli@5fbb536

If we want to stay compatible with RN version policy, we likely need to backport this to 0.70 at least. We could go further I guess, but I'd need @cortinico let me know if this even makes sense.

@thymikee We develop an SDK for React Native and leaving all these versions behind can be a bit risky.
What is the best approach for supporting 0.73 while still being backward-compatible?

What about versions older than that? RN 0.71 is already the latest available release. What about versions before 0.69?

For completeness, by the time 0.73 will be out, 0.72 and 0.71 will also be out which supports the namespace directive. I've been discussing with other library maintainers to understand if we want to recommended a snippet that would allow to selectively call namespace only on RN >= 0.71.

And yes, removing package="com.example.mylibrary" from the Manifest also has impact on libraries which are running on RN < 71. If you wish to retain the highest compatibility it's better to don't remove the package directive from the Manifest and add the namespace directive.
Sadly users on 0.71+ will see a warning on console mentioning that package="com.example.mylibrary" can be removed from the library manifest's file which is not really actionable.

Nice! Thanks for starting this @cortinico .
What about the R class being non-transitive by default? Have already caught a couple of libraries with incorrect refs.

The good part is that it helps to reduce the app size, especially for big projects ๐ŸŽ‰

I try to add package prop to manifest tag.

task addPackage {
    doFirst{
        def manifestOutFile = file("${projectDir}/src/main/AndroidManifest.xml")
        def manifestContent = manifestOutFile.getText()
        manifestContent = manifestContent.replace(
                '<manifest ',
                '<manifest package="demo.library.com" '
            )

        manifestOutFile.write(manifestContent)
    }
}

My manifest changed, but i catch error "import undefined."
Can anyone help me?

Just in case someone runs into this too.
There was a fix in RN CLI to parse both namespace = "com.example.mylibrary" and namespace = 'com.example.mylibrary'. Before only double quotes worked.

react-native-community/cli#1830

RN CLI https://github.com/react-native-community/cli/releases/tag/v10.2.0 and newer parse namespace both with single and double quotes.

A bit late to the party... even though I rushed into the change for RN Image Picker..

A solution that isn't a breaking change (as pointed out by @markrickert by grabbing the snippet from swmansion)

def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION
if (agpVersion.tokenize('.')[0].toInteger() >= 7) {
  namespace "com.swmansion.gesturehandler"
}

For reference: https://developer.android.com/build/publish-library/prep-lib-release#choose-namespace

A found solution for RN < 0.71

def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')[0].toInteger()
def shouldUseNameSpace = agpVersion >= 7
def PACKAGE_PROP = "package=\"com.example.lib\""
def manifestOutFile = file("${projectDir}/src/main/AndroidManifest.xml")
def manifestContent = manifestOutFile.getText()
if(shouldUseNameSpace){
      manifestContent = manifestContent.replaceAll(
        PACKAGE_PROP,
        ''
    )  
} else {
    if(!manifestContent.contains("$PACKAGE_PROP")){
        manifestContent = manifestContent.replace(
            '<manifest',
            "<manifest $PACKAGE_PROP "
        )
    }
}
manifestContent.replaceAll("  ", " ")
manifestOutFile.write(manifestContent)

android {
 ...
    if (shouldUseNameSpace){
        namespace = "com.example.lib"
    }
}
atlj commented

Why is it necessary to remove the package attribute from the manifest file? Will it cause any conflicts? I've tried searching online for information, but I couldn't find any reliable sources. From what I've observed, having both the manifest file's package attribute and the namespace itself at the same time seems to work correctly, at least for AGP version 7.4.2.

My suggestion is to consider adding a check to verify if the major version of AGP is higher than 7. If it is, then we can add the namespace accordingly. This way, we won't have to modify the manifest file, which would avoid introducing additional technical debt related to reading, modifying, and saving the manifest.xml file.

Here's a suggested implementation:

def isAGPVersionGreaterThan(version) {
  def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')[0].toInteger()
  return agpVersion > version
}

android {
  if (isAGPVersionGreaterThan(7)) {
    namespace "com.awesomelibrary"
  }

// ...
}

I have prepared a draft PR for react-native-builder-bob, and once I figure out this issue, I plan to mark it as ready for review.

Why is it necessary to remove the package attribute from the manifest file? Will it cause any conflicts? I've tried searching online for information, but I couldn't find any reliable sources. From what I've observed, having both the manifest file's package attribute and the namespace itself at the same time seems to work correctly, at least for AGP version 7.4.2.

My suggestion is to consider adding a check to verify if the major version of AGP is higher than 7. If it is, then we can add the namespace accordingly. This way, we won't have to modify the manifest file, which would avoid introducing additional technical debt related to reading, modifying, and saving the manifest.xml file.

Here's a suggested implementation:

def isAGPVersionGreaterThan(version) {
  def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')[0].toInteger()
  return agpVersion > version
}

android {
  if (isAGPVersionGreaterThan(7)) {
    namespace "com.awesomelibrary"
  }

// ...
}

I have prepared a draft PR for react-native-builder-bob, and once I figure out this issue, I plan to mark it as ready for review.

The package name in the manifest will be ignored as far as I checked.

Why is it necessary to remove the package attribute from the manifest file?

Removing the package from the Manifest file is not a requirement, but users will see a warning for every library like the following:

> Task :react-native-library:processDebugManifest
package="com.example.mylibrary" found in source AndroidManifest.xml: /Users/username/project/folder/node_modules/react-native-library/android/src/main/AndroidManifest.xml.
Setting the namespace via the package attribute in the source AndroidManifest.xml is no longer supported, and the value is ignored.
Recommendation: remove package="com.example.mylibrary" from the source AndroidManifest.xml: /Users/username/project/folder/node_modules/react-native-library/android/src/main/AndroidManifest.xml.

which is not really actionable for the app users, as that's a file of your library.

Kudo commented

hi there!

we are doing some react native nightlies testing at expo and find more AGP 8 breaking changes. would rather write down here to share with the community.

  • since AGP 8 does not generate the BuildConfig.java by default, if you use BuildConfig in your java/kotlin code, please add the following code to build.gradle.

    android {
      // ...
      buildFeatures {
        buildConfig true
      }
    }

    the build error message would be something like: Unresolved reference: BuildConfig

  • since AGP 8 does not generate R resources from transitive dependencies, please fix your code to reference correct R resources from correct package. for example, we use exoplayer inside expo-av, we should change the resource here from R.drawable.exo_controls_pause to com.google.android.exoplayer2.ui.R.drawable.exo_controls_pause. though you could also set android.nonTransitiveRClass=false in build.gradle, but that's not recommended.

  • since AGP 8 now uses the "Gradle Java toolchain support". please set jvm version only if AGP < 8.
    originally you may have the following code in build.gradle

      compileOptions {
        sourceCompatibility JavaVersion.VERSION_11
        targetCompatibility JavaVersion.VERSION_11
      }
    
      kotlinOptions {
        jvmTarget = JavaVersion.VERSION_11.majorVersion
      }

    now you could change to that

      def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION
      if (agpVersion.tokenize('.')[0].toInteger() < 8) {
        compileOptions {
          sourceCompatibility JavaVersion.VERSION_11
          targetCompatibility JavaVersion.VERSION_11
        }
    
        kotlinOptions {
          jvmTarget = JavaVersion.VERSION_11.majorVersion
        }
      }
  • there might be some other errors. for all the other AGP 8 breaking changes, please refer to the release note

Diff to support AGP 8 in libraries without breaking backward compatibility: https://gist.github.com/satya164/e508e1be04650a68d76a993f5384ffd0

pvegh commented

Consider/don't forget enabling BuildConfig explicitly in modules where a BuildConfig field is set, like described here
software-mansion/react-native-svg#2143
This makes is possible to not having to disable an optimisation, which is skipping BuildConfig java class generation for unnecessary modules, the default in AGP8+

๐Ÿ‘‹ I've been doing testing on old RN versions to test retro-compatibility for my lib (specifically RN 0.67.5 with AGP 4.2.2) and my builds succeed even without adding the if condition on AGP version around the namespace="my.lib" and buildConfig true declarations in the build.gradle - meaning adding them does not break the build.

Has anyone seen specific issues around this or is this just extra caution?
The less complexity I can add the happier I am :)

@louiszawadzki the build will work (for the manifest) but there'll still be warnings due to using deprecated property from the library when user builds their app. and the namespace in the build.gradle library needs to be in if or it'll fail the build on AGP < 7.3. the diff I posted above accounts for both scenarios.

There is a pretty huge list of modules: #40772

Is there also any changes to Fabric ?

I have react-native-safe-area-context and react-native-screens failing with errors:

e: file:///Users/ivanignatiev/GitHub/***/node_modules/react-native-safe-area-context/android/src/main/java/com/th3rdwave/safeareacontext/SafeAreaView.kt:8:37 Unresolved reference: FabricViewStateManager
e: file:///Users/ivanignatiev/GitHub/***/node_modules/react-native-safe-area-context/android/src/main/java/com/th3rdwave/safeareacontext/SafeAreaView.kt:9:37 Unresolved reference: FabricViewStateManager
e: file:///Users/ivanignatiev/GitHub/***/node_modules/react-native-safe-area-context/android/src/main/java/com/th3rdwave/safeareacontext/SafeAreaView.kt:19:66 Unresolved reference: HasFabricViewStateManager
e: file:///Users/ivanignatiev/GitHub/***/node_modules/react-native-safe-area-context/android/src/main/java/com/th3rdwave/safeareacontext/SafeAreaView.kt:24:41 Unresolved reference: FabricViewStateManager
e: file:///Users/ivanignatiev/GitHub/***/node_modules/react-native-safe-area-context/android/src/main/java/com/th3rdwave/safeareacontext/SafeAreaView.kt:26:3 'getFabricViewStateManager' overrides nothing
e: file:///Users/ivanignatiev/GitHub/***/node_modules/react-native-safe-area-context/android/src/main/java/com/th3rdwave/safeareacontext/SafeAreaView.kt:26:45 Unresolved reference: FabricViewStateManager
e: file:///Users/ivanignatiev/GitHub/***/node_modules/react-native-safe-area-context/android/src/main/java/com/th3rdwave/safeareacontext/SafeAreaViewManager.kt:67:28 Unresolved reference: fabricViewStateManager
e: file:///Users/ivanignatiev/GitHub/***/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt:391:46 Unresolved reference: attr

newArchitecture is disabled

For my previous issue, breaking change: facebook/react-native#38958 , react-native-safe-area-context module was depending on it

Open issue on react-native-safe-area-context side: th3rdwave/react-native-safe-area-context#439

Small update:

We added a compatibility layer inside React Native 0.73, that will make sure that namespace is configured for each libraries, even if you don't update the library. This will make sure 0.73 works also with older libraries that are not receiving may updates.

Still consider updating your library to keep up to date with the Android best practices.

Thanks to @gabrieldonadel for making this happen:

This will not work if the 3rd party librariy's name is before the 'app', such as 'aliyun' .....,
How can I fix it? @gabrieldonadel ,@cortinico

This will not work if the 3rd party librariy's name is before the 'app', such as 'aliyun' ....., How can I fix it? @gabrieldonadel ,@cortinico

Thanks for the report. This is tracked here:

Fix is forthcoming

I've been discussing with other library maintainers to understand if we want to recommended a snippet that would allow to selectively call namespace only on RN >= 0.71.

If you wish to retain the highest compatibility it's better to don't remove the package directive from the Manifest and add the namespace directive.

@cortinico Did you end up in a recommended approach regarding this? Is it the namespace addition or removal of the package directive that breaks compatibility with RN < 0.71?

Would be great if we can find a way to preserve backwards compatibility without recommending different versions of packages depending on their RN version.

Edit: Is this the way to go perhaps? https://github.com/react-native-netinfo/react-native-netinfo/pull/676/files

Edit: Is this the way to go perhaps? react-native-netinfo/react-native-netinfo#676 (files)

In theory yes. But that's depending on AGP internals. So if AGP decides to remove this flag, libraries will break again, so I'm really hesitant in recommending it for good.
I would say that as a temporary workaround to extend compatibility of libraries, that's probably the best solution.

We're working on a golden-template for create-react-native-libraries here:

Ideally we should include those if-then-else inside the template logic and not let you scatter those checks in the library build code, as it gets really hard to manage otherwise.

Makes sense. Great idea with the golden template, it will be appreciated.

I quickly tested the new 0.73.0 release on a library I am maintaining and it seemed to work fine, even without these namespace/package changes. Do you know how to trigger the potential build issue?

Repo: https://github.com/henninghall/react-native-date-picker/tree/master/examples/Rn073

I quickly tested the new 0.73.0 release on a library I am maintaining and it seemed to work fine, even without these namespace/package changes. Do you know how to trigger the potential build issue?

That's because we implemented a backward compat support for it (see here #671 (comment))

Closing as 0.73 has been released and we shipped all the necessary changes to make this transition as smooth as possible

Hi,
I upgraded my project from 0.68.7 to 0.73.2. I am getting the below error on Android.
I do not want to upgrade my libraries in package.json
The project is using an incompatible version (AGP 8.1.1) of the Android Gradle plugin. Latest supported version is AGP 7.2.1.