Search code examples
androidreact-nativegradleaar

How to pack react-native application in AAR


I have a react-native application with android target.

I also have a separate android application and I need to integrate react-natie part there. But I need to do it without react-native environment dependencies so other developers could work without setting it up.

It seems that the best way to do it is to pack all the react-native application into AAR, then publish it in maven or just add in in lib folder of target application.

To build AAR instead of APK I've done the following in react-native android project:

  1. Changed plugin from com.android.application to com.android.library
  2. Removed applicationId
  3. Cleared manifest file from application classes, icons, etc

I've managed to build an AAR, but the problem is it misses all the dependencies like com.facebook.react.ReactActivity and other classes from this dependencies. There is no bundled js assets in the AAR.

When I'm building APK all these components are there.

Some more details

In react-native android module level gradle I've set project.ext.react.bundleInDebug: true

I'm building AAR with gradlew build

There were one problem building AAR: gradle failed wit an error:

FAILURE: Build failed with an exception.

 

* Where:

Script 'C:\proj\react-native\node_modules\react-native\react.gradle' line: 353

 
* What went wrong:

A problem occurred configuring project ':app'.

> Cannot invoke method doFirst() on null object

This means there is no packageDebug/packageRelease tasks that are required by node_modules\react-native\react.gradle' at line #353

There are such tasks when building with application plugin. Probably the problem is here.

To overcome this problem I've added stub-tasks.

Versions

Versions I'm using:

  • react-native: 0.63.5
  • com.android.tools.build:gradle:4.1.3 (gradle: 6.9)

Solution

  • I've solved my task. As there is so much interest to subject, I will post the solution.

    1. I've published all the dependencies along with my library.
    2. I've chagned my library .pom to include theese dependencies.
    3. I've added the react-native and android-jsc-intl dependenies into my repo manually.

    In the root build.gradle

    subprojects {
        apply plugin: "maven-publish"
    
        project.afterEvaluate {
            if (!plugins.hasPlugin("android")) {
                publishing {
                    publications {
                        release(MavenPublication) {
                            afterEvaluate {
                                // @ - cant be a part of artifactId
                                artifactId = project.name.replace('@', '')
    
                                if (plugins.hasPlugin("java")) {
                                    from components.java
                                } else if (plugins.hasPlugin("android-library")) {
                                    if (project.name == "MY_LIBRARY_MODULE") {
                                        groupId 'com.my_librayr'
                                        artifactId 'my_library'
                                        version '1.0.0' // Change version each publication. I use env variable and external script for track version number.
                                        artifact sourceJar
    
                                        def aarDir = "$buildDir/outputs/aar/"
                                        File[] files = (new File(aarDir)).listFiles()
                                        for (file in files) {
                                            if (file.path.endsWith(".aar")) artifact file.path
                                        }
    
                                        pom.withXml {
                                            def node = asNode()
                                            ((NodeList) node.get('packaging')).get(0).value = 'aar'
                                            def dependenciesNode = node.appendNode('dependencies')
    
                                            def includeDependency = { Dependency dep ->
                                                println("includeDependency : ${dep.name}")
                                                if (dep.group == null || dep.version == null || dep.name == null || dep.name == "unspecified") return
    
                                                // To save group names (to load from different places, or to use manually loaded packages), all other dependencies would go to android group
                                                def persistGroupName = [
                                                    'com.facebook.react',
                                                    'androidx.swiperefreshlayout',
                                                    'androidx.appcompat',
                                                    'com.google.firebase',
                                                ].any { dep.group.startsWith(it) }
                                                def persistGroupNameForArtifactNames = [
                                                    'kotlin-stdlib-jdk8',
                                                    'android-jsc-intl',
                                                    'react-native-appmetrica',
                                                    YET_ANOTHER_LIBRARY_MODYULE_OF_YOUR_PROJECT
                                                ].any { dep.name.startsWith(it) }
                                                def groupId
                                                if (persistGroupName || persistGroupNameForArtifactNames) {
                                                    groupId = dep.group
                                                } else {
                                                    groupId = 'android'
                                                }
    
                                                def depName = dep.name.replace('@', '')
                                                
                                                def dependencyNode = dependenciesNode.appendNode('dependency')
                                                dependencyNode.appendNode('artifactId', depName)
                                                dependencyNode.appendNode('groupId', groupId)
                                                dependencyNode.appendNode('version', dep.version)
                                            }
    
                                            configurations.api.getAllDependencies().each includeDependency
                                            configurations.implementation.getAllDependencies().each includeDependency
                                            configurations.compile.getAllDependencies().each includeDependency
                                        }
                                    } else {
                                        // This branch is needed to change groupId and version of your library. Use it if you have a module your library depends on
                                        if (project.name == YET_ANOTHER_LIBRARY_MODYULE_OF_YOUR_PROJECT) {
                                            groupId 'my_library'
                                            version '1.0.0'
                                        }
                                        def generalRelease = components.find { it.name == "generalRelease" }
                                        if (generalRelease != null) from generalRelease else from components.release
                                        tasks.withType(GenerateModuleMetadata) { enabled = false }
                                    }
                                }
                            }
    
                        }
    
                    }
    
                    // Setting repository to buplish to
                    repositories {
                        maven {
                            url "http://nexus.local/megarepo"
                            credentials {
                                username "login"
                                password "password"
                            }
                        }
                    }
    
                }
            }
        }
    }
    

    Adding missing tasks in modules build.gradle

    addDummyPackageTask(tasks, "release")
    addDummyPackageTask(tasks, "debug")
    
    def addDummyPackageTask(TaskContainer tasks, String typeName) {
        def name = "package${typeName.capitalize()}"
        if (tasks.findByName(name) == null) {
            tasks.register(name) {
                doLast {
                }
            }
        }
    }
    

    Wont pust more explanations. If you need some more - ask. I will try my best to answer.