The Definitive Guide to Using Local AARs in a Flutter Plugin

· 798 words · 4 minute read

A step-by-step walkthrough of how to correctly configure Gradle to use a local .AAR file in a Flutter plugin for Android, based on our direct experience.

At Tinisoft, we often push the boundaries of what’s possible with Flutter, which sometimes means integrating powerful, custom native Android libraries. If you’ve ever had a local .AAR file—perhaps from a specialized SDK or your own in-house Android team—and tried to add it to a Flutter plugin, you’ve likely run into a wall of confusing Gradle errors.

The seemingly simple path of just adding the AAR as a dependency doesn’t work. This guide will walk you through the common pitfalls and provide the definitive, production-ready method to make it work, ensuring your builds are stable and predictable.

The Intuitive Approach (And Why It Fails) 🔗

Your Flutter plugin has an android folder, and inside, a build.gradle file. Your first instinct is to treat the AAR like any other dependency. You create a libs folder, place your custom-native-library.aar inside, and add the following line to your plugin’s build.gradle:

// In my_plugin/android/build.gradle

dependencies {
    // This seems right, but it's a trap!
    implementation files('libs/custom-native-library.aar')
}

You run flutter run in your example app, and the build immediately fails with an error that looks like this:

Direct local .aar file dependencies are not supported when building an AAR.
...The resulting AAR would be broken...

This error is the heart of the problem. Your Flutter plugin is itself packaged into an AAR file (my_plugin.aar). Gradle is telling you that you cannot package an AAR file inside another AAR file. It doesn’t know how to merge the code, resources, and manifests, which would lead to a broken library.

The Correct Architecture: Separating Compilation from Packaging 🔗

The solution lies in understanding the difference between what your plugin needs to compile and what your final app needs to package.

  1. The Plugin’s Role (compileOnly): Your plugin’s Kotlin/Java code only needs to know about the classes and methods inside the AAR to compile successfully. It does not need to bundle the entire AAR within itself. For this, we use the compileOnly configuration.

  2. The App’s Role (implementation): The final application (your example/ app or the app that eventually consumes your plugin) is responsible for packaging everything needed to run. It must bundle the Flutter engine, your plugin’s code, and your custom AAR into the final APK. For this, we use the standard implementation configuration.

With this separation of concerns, we can create a clean and robust build process.

Step-by-Step: The Production-Ready Solution 🔗

Let’s fix the build. Follow these two steps carefully.

Step 1: Configure Your Plugin’s build.gradle 🔗

First, we tell the plugin to only use the AAR for compiling.

  1. Place your custom-native-library.aar inside the my_plugin/android/libs/ directory.
  2. Open your plugin’s build.gradle file at my_plugin/android/build.gradle.
  3. Change the dependency from implementation to compileOnly.
// In my_plugin/android/build.gradle

dependencies {
    // Correct: Use compileOnly for the plugin
    compileOnly files('libs/custom-native-library.aar')

    // ... other dependencies like kotlin-stdlib
}

Now, your plugin will compile correctly, but if you try to build the app, it will crash at runtime because the AAR’s code is still missing from the final APK. Let’s fix that.

Step 2: Configure Your App’s build.gradle 🔗

Next, we make the main application responsible for packaging the AAR. The most robust way to do this is to have the app copy the AAR from the plugin’s directory into its own libs folder before the build begins. This avoids any tricky relative pathing issues with the Android build system.

  1. Open your example app’s build.gradle file, located at my_plugin/example/android/app/build.gradle.
  2. Add the following copy task at the top of the file, right after the plugins block. This task finds the AAR in your plugin and copies it locally.
// In my_plugin/example/android/app/build.gradle

apply plugin: 'com.android.application'
// ...

// Add this entire block
task copyLocalAar(type: Copy) {
    from '../../../android/libs' // Path from the app to the plugin's libs folder
    into 'libs'
    include 'custom-native-library.aar'
}
preBuild.dependsOn copyLocalAar
  1. Finally, add the implementation dependency in the same file, pointing to the file that the task just copied.
// In my_plugin/example/android/app/build.gradle

dependencies {
    // This line tells the app to package the AAR into the final APK
    implementation files('libs/custom-native-library.aar')

    // ... other dependencies
}

Now, run flutter clean and flutter run. Your application will build successfully.

Conclusion 🔗

Integrating local AARs in Flutter plugins requires a deeper understanding of how Gradle handles dependencies. By separating the plugin’s compile-time needs from the app’s packaging-time needs, we create a stable and maintainable build process.

The key takeaways are:

  • Use compileOnly in the plugin to reference the AAR without bundling it.
  • Use a preBuild copy task and an implementation dependency in the final app to package the AAR.

This method ensures your builds are reliable and frees you to focus on what matters: building amazing native-powered features for your Flutter apps.

Happy coding