Create an aar with all flutter libraries and dependencies inside
Solution 1
Add Android native library into your project in Android Studio: File -> New -> New module -> Android Library.
After that add this plugin https://github.com/kezong/fat-aar-android into the project and replace 'implementation' by 'embed' keyword. Then your project structure will look like:
In flutter_library directory run command
flutter build aar -v
. Note: flutter_library contains Flutter related files, e.g lib/, .android, .ios, pubspec.yaml, etcIn root project directory run
./gradlew assemble
aar will be located in library/build/outputs/aar
See my example: https://github.com/askarsyzdykov/native_flutter_lib
Solution 2
The aar file doesn't contain the transitive dependencies and doesn't have a pom file which describes the dependencies used by the library.
It means that, if you are importing a aar file using a flatDir
repo you have to specify the dependencies also in your project.
I know that it is not the solution you are looking for but you should use a maven repository to solve this issue. In this case, gradle downloads the dependencies using the pom file which will contains the dependencies list.
Manuel Peixoto
Updated on December 12, 2022Comments
-
Manuel Peixoto over 1 year
I need to create an aar with all the libraries of my flutter project inside, I created a flutter module and now I've to create an sdk in android to be embedded in a client app for that it would be nice to have a single aar file. I tried Mobbeel fat AAR Gradle plugin but with no avail. I know I can create a maven repository but that is not the solution I'm looking for right now.
my project build.gradle
buildscript { repositories { maven { url "https://plugins.gradle.org/m2/" } google() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.2.1' classpath "com.mobbeel.plugin:fat-aar:2.0.1" } } allprojects { repositories { google() jcenter() } }
and the app build.graddle
def flutterPluginVersion = 'managed' apply plugin: 'com.android.library' apply plugin: "com.mobbeel.plugin" android { compileSdkVersion 28 compileOptions { sourceCompatibility 1.8 targetCompatibility 1.8 } defaultConfig { minSdkVersion 21 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } buildDir = new File(rootProject.projectDir, "../build/host") dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) api (project(':flutter')) testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' implementation 'androidx.annotation:annotation:1.1.0' implementation 'androidx.lifecycle:lifecycle-common:2.0.0' } aarPlugin { includeAllInnerDependencies false packagesToInclude = ["mobbeel"] }
EDIT: I found a solution, but I'm not an android developer so a made some changes to the mobbeel plugin and add it to the build.gradle. After that I was able to add all the libraries to my aar by doing api project(":vibrate")
String archiveAarName project.afterEvaluate { project.android.libraryVariants.all { variant -> variant.outputs.all { archiveAarName = outputFileName } print "afterEvaluate\n" def copyTask = createBundleDependenciesTask(variant) String rsDirPath = "${copyTask.temporaryDir.path}/rs/" String rsCompiledDirPath = "${copyTask.temporaryDir.path}/rs-compiled/" String sourceAarPath = "${copyTask.temporaryDir.path}/${variant.name}/" String taskNameCompileRs = "SDKcompileRs${variant.name.capitalize()}" String taskNameRsJa = "CreateRsJar${variant.name.capitalize()}" String taskNameCreateZip = "createZip${variant.name.capitalize()}" def compileRsTask = R2ClassTask(variant, rsDirPath, rsCompiledDirPath, taskNameCompileRs) def rsJarTask = bundleRJarTask(variant, rsCompiledDirPath, sourceAarPath, taskNameRsJa) def aarTask = bundleFinalAAR(variant, sourceAarPath, "finalname", taskNameCreateZip) def assembleTask = project.tasks.findByPath("assemble${variant.name.capitalize()}") assembleBundleDependenciesTask(variant).finalizedBy(assembleTask) assembleTask.finalizedBy(copyTask) copyTask.finalizedBy(compileRsTask) compileRsTask.finalizedBy(rsJarTask) rsJarTask.finalizedBy(aarTask) } } Task assembleBundleDependenciesTask(def variant) { println "assembleBundleDependenciesTask -> ${variant.name}" return project.getTasks().create("hello_${variant}", { project.configurations.api.getDependencies().each { dependency -> if (dependency instanceof ProjectDependency) { Project dependencyProject = project.parent.findProject(dependency.name) String dependencyPath = "${dependencyProject.buildDir}" println "dependencyPath -> ${dependencyPath}" String variantName = "${variant.name}" def assembleTask = project.tasks.findByPath(":${dependency.name}:assemble${variant.name.capitalize()}") assembleTask.finalizedBy(copyTo( "${dependencyPath}/outputs/aar", variantName, dependency.name)) } println '' } }) } Task copyTo(String fromz, String variant, String dependency) { println "copyTo fromz -> $fromz " return project.task(type: Copy, "copyFile$dependency$variant") { from fromz into project.projectDir.path + "/build/outputs/aar/" include('*.aar') rename { String fileName -> fileName = "${dependency}-${variant}.aar" } } } Task createBundleDependenciesTask(def variant) { println "createBundleDependenciesTask -> ${variant.name}" String taskName = "copy${variant.name.capitalize()}Dependencies" return project.getTasks().create(taskName, CopyDependenciesTask.class, { it.includeInnerDependencies = true it.dependencies = project.configurations.api.getDependencies() it.variantName = variant.name it.gradleVersion = "3.2.1" it.buildAARDir = project.projectDir.path + "/build/outputs/aar/" }) } Task R2ClassTask(def variant, String sourceDir, String destinationDir, String taskName) { print "R2ClassTask sourceDir -> $sourceDir to destDir -> $destinationDir" project.mkdir(destinationDir) def classpath classpath = project.files(project.projectDir.path + "/build/intermediates/javac/${variant.name}/compile${variant.name.capitalize()}JavaWithJavac/classes") return project.getTasks().create(taskName, JavaCompile.class, { it.source = sourceDir it.sourceCompatibility = project.android.compileOptions.sourceCompatibility it.targetCompatibility = project.android.compileOptions.targetCompatibility it.classpath = classpath it.destinationDir project.file(destinationDir) }) } Task bundleRJarTask(def variant, String fromDir, String aarPath, String taskName) { print "bundleRJarTask\n" return project.getTasks().create(taskName, Jar.class, { it.from fromDir it.archiveName = "r-classes.jar" it.destinationDir project.file("${aarPath}/libs") }) } Task bundleFinalAAR(def variant, String fromPath, name, String taskName) { print "bundleFinalAAR -> from ${fromPath} to > " + project.file(project.projectDir.path + "/build/outputs/aar/") + "\n" return project.getTasks().create(taskName, Zip.class, { it.from fromPath it.include "**" it.archiveName = "${name}-${variant.name}.aar" it.destinationDir(project.file(project.projectDir.path + "/build/outputs/aar/")) }) } import groovy.xml.XmlUtil class CopyDependenciesTask extends DefaultTask { Boolean includeInnerDependencies DependencySet dependencies String variantName String gradleVersion String[] packagesToInclude = [""] String buildAARDir @TaskAction def executeTask() { if (temporaryDir.exists()) { temporaryDir.deleteDir() } temporaryDir.mkdir() copyProjectBundles() analyzeDependencies() } def copyProjectBundles() { println "copyProjectBundles" if (gradleVersion.contains("3.2")) { // Version 3.4.x println "packaged-classes -> ${project.projectDir.parentFile.parent}/finalProjname/app/build/intermediates/packaged-classes/" project.copy { from "${project.projectDir.parentFile.parent}/finalProjname/app/build/intermediates/packaged-classes/" include "${variantName}/**" into temporaryDir.path } project.copy { from("${project.projectDir.parentFile.parent}/finalProjname/app/build/intermediates/res/symbol-table-with-package/${variantName}") { include "package-aware-r.txt" rename '(.*)', 'R.txt' } from("${project.projectDir.parentFile.parent}/finalProjname/app/build/intermediates/aapt_friendly_merged_manifests/" + "${variantName}/process${variantName.capitalize()}Manifest/aapt/") { include "AndroidManifest.xml" } into "${temporaryDir.path}/${variantName}" } println " check this -> ${temporaryDir.path}/${variantName}/R.txt" processRsAwareFile(new File("${temporaryDir.path}/${variantName}/R.txt")) project.copy { from "${project.projectDir.parentFile.parent}/finalProjname/app/build/intermediates/packaged_res/${variantName}" include "**" into "${temporaryDir.path}/${variantName}/res" } project.copy { from "${project.projectDir.parentFile.parent}/finalProjname/app/build/intermediates/library_assets/${variantName}/packageDebugAssets/out/" include "**" into "${temporaryDir.path}/${variantName}/assets" } } else { // Version 3.0.x project.copy { from "${project.projectDir.parentFile.parent}/finalProjname/app/build/intermediates/bundles/" from "${project.projectDir.parentFile.parent}/finalProjname/app/build/intermediates/manifests/full/" include "${variantName}/**" exclude "**/output.json" into temporaryDir.path } } } def analyzeDependencies() { print "analyzeDependencies\n" dependencies.each { dependency -> def dependencyPath def archiveName print "dependency -> " + dependency if (dependency instanceof ProjectDependency) { print " instanceof -> ProjectDependency\n" String group = dependency.group Project dependencyProject dependencyProject = project.parent.findProject(dependency.name) println "dependencyProject -> ${dependencyProject}" if (dependencyProject.plugins.hasPlugin('java-library')) { println "Internal java dependency detected -> " + dependency.name archiveName = dependencyProject.jar.archiveName dependencyPath = "${dependencyProject.buildDir}/libs/" } else { println "Internal android dependency detected -> " + dependency.name dependencyProject.android.libraryVariants.all { if (it.name == variantName) { it.outputs.all { archiveName = outputFileName } } } dependencyPath = buildAARDir } processDependency(dependency, archiveName, dependencyPath) } else if (dependency instanceof ExternalModuleDependency) { println "External dependency detected -> " + dependency.group + ":" + dependency.name + ":" + dependency.version dependencyPath = project.gradle.getGradleUserHomeDir().path + "/caches/modules-2/files-2.1/" dependencyPath += dependency.group + "/" + dependency.name + "/" + dependency.version + "/" processDependency(dependency, archiveName, dependencyPath) } else { println "Not recognize type of dependency for " + dependency println() } } } /** * In this case dependency is outside from workspace, download from maven repository if file is * a jar directly move to lib/ folder and analyze pom file for detect another transitive dependency * @param dependency * @return */ def processDependency(Dependency dependency, String archiveName, String dependencyPath) { println "processDependency -> ${archiveName} in ${dependencyPath}" project.fileTree(dependencyPath).getFiles().each { file -> println "processDependency file.name -> ${file.name} " if (file.name.endsWith(".pom")) { println "POM: " + file.name processPomFile(file.path) } else { if (archiveName == null || file.name == archiveName) { println "Artifact: " + file.name if (file.name.endsWith(".aar")) { processZipFile(file, dependency) } else if (file.name.endsWith(".jar")) { if (!file.name.contains("sources")) { copyArtifactFrom(file.path) } else { println " |--> Exclude for source jar" } } } } } println() } def processZipFile(File aarFile, Dependency dependency) { println "processZipFile" String tempDirPath = "${temporaryDir.path}/${dependency.name}_zip" println "tempDirPath -> ${tempDirPath}" project.copy { from project.zipTree(aarFile.path) include "**/*" into tempDirPath } File tempFolder = new File(tempDirPath) println "temporaryDir -> ${temporaryDir.path}/${variantName}/" project.copy { from "${tempFolder.path}" include "classes.jar" into "${temporaryDir.path}/${variantName}/libs" def jarName = getJarNameFromDependency(dependency) rename "classes.jar", jarName } project.copy { from "${tempFolder.path}/libs" include "**/*.jar" into "${temporaryDir.path}/${variantName}/libs" } project.copy { from "${tempFolder.path}/jni" include "**/*.so" into "${temporaryDir.path}/${variantName}/jni" } project.copy { from "${tempFolder.path}/assets" include "**/*" into "${temporaryDir.path}/${variantName}/assets" } project.copy { from "${tempFolder.path}/res" include "**/*" exclude "values/**" into "${temporaryDir.path}/${variantName}/res" } processValuesResource(tempFolder.path) processRsFile(tempFolder) println "tempFolder.deleteDir()" tempFolder.deleteDir() } def getJarNameFromDependency(Dependency dependency) { def jarName = "" if (null != dependency.group) { jarName += dependency.group.toLowerCase() + "-" } jarName += dependency.name.toLowerCase() if(null != dependency.version && !dependency.version.equalsIgnoreCase('unspecified')) { jarName += "-" + dependency.version } jarName += ".jar" return jarName } def processRsAwareFile(File resAwareFile) { println "processRsAwareFile" RandomAccessFile raf = new RandomAccessFile(resAwareFile, "rw") long writePosition = raf.getFilePointer() raf.readLine() // Move pointer to second line of file long readPosition = raf.getFilePointer() byte[] buffer = new byte[1024] int bytesInBuffer while (-1 != (bytesInBuffer = raf.read(buffer))) { raf.seek(writePosition) raf.write(buffer, 0, bytesInBuffer) readPosition += bytesInBuffer writePosition += bytesInBuffer raf.seek(readPosition) } raf.setLength(writePosition) raf.seek(0) if (gradleVersion.contains("3.2")) { String filePath = "${project.projectDir.parentFile.parent}/finalProjname/app/build/intermediates/symbols/${variantName}/R.txt" Scanner resourcesOriginal = new Scanner(new File(filePath)) raf.seek(0) // Move pointer to first line String line int offset = 0 while (resourcesOriginal.hasNextLine()) { boolean match = false line = resourcesOriginal.nextLine() println line line += "\n" byte[] data = line.getBytes() raf.seek(offset) raf.write(data, 0, data.length) offset += data.length raf.seek(offset + 1) } } raf.close() } def processRsFile(File tempFolder) { println "processRsFile" def mainManifestFile = project.android.sourceSets.main.manifest.srcFile def libPackageName = "" if (mainManifestFile.exists()) { println "processRsFile -> mainManifestFile.exists()" libPackageName = new XmlParser().parse(mainManifestFile).@package } def manifestFile = new File("$tempFolder/AndroidManifest.xml") if (manifestFile.exists()) { println "processRsFile -> manifestFile.exists()" def aarManifest = new XmlParser().parse(manifestFile) def aarPackageName = aarManifest.@package String packagePath = aarPackageName.replace('.', '/') // Generate the R.java file and map to current project's R.java // This will recreate the class file def rTxt = new File("$tempFolder/R.txt") def rMap = new ConfigObject() if (rTxt.exists()) { println "processRsFile -> rTxt.exists()" rTxt.eachLine { line -> //noinspection GroovyUnusedAssignment def (type, subclass, name, value) = line.tokenize(' ') rMap[subclass].putAt(name, type) } } def sb = "package $aarPackageName;" << '\n' << '\n' sb << 'public final class R {' << '\n' rMap.each { subclass, values -> sb << " public static final class $subclass {" << '\n' values.each { name, type -> sb << " public static $type $name = com.company.native_sdk.R.${subclass}.${name};" << '\n' } sb << " }" << '\n' } sb << '}' << '\n' new File("${temporaryDir.path}/rs/$packagePath").mkdirs() FileOutputStream outputStream = new FileOutputStream("${temporaryDir.path}/rs/$packagePath/R.java") println "R file path -> ${temporaryDir.path}/rs/$packagePath/R.java" outputStream.write(sb.toString().getBytes()) outputStream.close() } } def processValuesResource(String tempFolder) { println "processValuesResource" File valuesSourceFile = new File("${tempFolder}/res/values/values.xml") File valuesDestFile = new File("${temporaryDir.path}/${variantName}/res/values/values.xml") if (valuesSourceFile.exists()) { println "processValuesResource -> valuesSourceFile.exists" if (!valuesDestFile.exists()) { println "processValuesResource -> !valuesDestFile.exists" project.copy { from "${tempFolder}/res" include "values/*" into "${temporaryDir.path}/${variantName}/res" } } else { println "processValuesResource -> valuesDestFile.exists" def valuesSource = new XmlSlurper().parse(valuesSourceFile) def valuesDest = new XmlSlurper().parse(valuesDestFile) valuesSource.children().each { valuesDest.appendNode(it) } FileOutputStream fileOutputStream = new FileOutputStream(valuesDestFile, false) byte[] myBytes = XmlUtil.serialize(valuesDest).getBytes("UTF-8") fileOutputStream.write(myBytes) fileOutputStream.close() } } else { println "processValuesResource -> !valuesSourceFile.exists" } } def copyArtifactFrom(String path) { project.copy { includeEmptyDirs false from path include "**/*.jar" into "${temporaryDir.path}/${variantName}/libs" rename '(.*)', '$1'.toLowerCase() } } def processPomFile(String pomPath) { def pom = new XmlSlurper().parse(new File(pomPath)) pom.dependencies.children().each { def subJarLocation = project.gradle.getGradleUserHomeDir().path + "/caches/modules-2/files-2.1/" if (!it.scope.text().equals("test") && !it.scope.text().equals("provided")) { String version = it.version.text() if (version.startsWith("\${") && version.endsWith("}")) { pom.properties.children().each { if (version.contains(it.name())) { version = it.text() } } } println " |--> Inner dependency: " + it.groupId.text() + ":" + it.artifactId.text() + ":" + version if (includeInnerDependencies || it.groupId.text() in packagesToInclude) { subJarLocation += it.groupId.text() + "/" + it.artifactId.text() + "/" + version + "/" project.fileTree(subJarLocation).getFiles().each { file -> if (file.name.endsWith(".pom")) { println " /--> " + file.name processPomFile(file.path) } else { if (!file.name.contains("sources") && !file.name.contains("javadoc")) { copyArtifactFrom(file.path) } } } } else { println " (Exclude inner dependency)" } } } } }
-
Nirav Tukadiya almost 5 yearsHi, Manuel. I also want to export the flutter module as an aar file. Did you get any solution?
-
Manuel Peixoto almost 5 years@NiravTukadiya take a look at my new edit. Hope that it helps you.
-
-
Manuel Peixoto almost 5 yearsSo if I have a maven solution do I've to have all flutter plugins in my maven repository as well?