Flutter builds APKs through a long set of steps that ultimately defer to Gradle. The logic lives in many places: the Flutter framework, the Flutter app template, and a mix of both Dart and Gradle scripts. This doc is a high level description of the overall flow, which can be hard to follow given how many different places at once it lives and how asynchronous Gradle builds are.
pub get
behavior and version solving.build.gradle
, which applies the Flutter framework’s flutter.gradle
plugin.flutter.gradle
adds the Flutter engine and all of its support library dependencies as a jar
dependency to the Flutter app.flutter.gradle
adds all of the Flutter plugins of an app as “subproject” Gradle dependencies (usually).Those steps are deliberately listed as bullet points and not a numbered list. Gradle invokes a lot of these steps at seemingly random and parallel times. Time is relative.
Flutter plugins are typically included in Flutter apps as source code and compiled by Gradle, the same as the rest of the code in the Flutter app.
This is done by using the Gradle concept of “subprojects”, a way of structuring build tasks so that they're marked as being dependent on each other. All Flutter apps are built with one subproject for the actual Android code of the app and other subprojects for the Android code of any plugins.
For example, let‘s assume that there’s a Flutter app that uses two plugins: foo
and bar
. Let‘s also assume foo
depends on baz
, so baz
is included as part of the build at runtime. Running ./gradlew projects
in the app’s android
directory (after first running flutter build apk
to give the Flutter tool's Dart code an opportunity to set up the build) would reveal the following project structure:
$ ./gradlew projects > Task :projects ------------------------------------------------------------ Root project ------------------------------------------------------------ Root project 'android' +--- Project ':app' +--- Project ':bar' +--- Project ':baz'' \--- Project ':foo'
.flutter-plugins
file containing a list of all the plugins used by the app and their location on disk.flutter.gradle
reads from this file and actually adds the plugins as Gradle subproject dependencies of the app.Including plugin dependencies as source code and then compiling them is not something Gradle generally expects apps to be doing. In the past we've hit several issues caused by Gradle tooling around dependency management being built for typical Android patterns (including dependencies as .aars
or .jars
) and breaking down for the Flutter use case.
For example:
build.gradle
scripts that may conflict with the app and other plugins in unexpected ways. How Gradle handles these conflicts is situational and sometimes undefined behavior.afterEvaluate
hook in flutter.gradle
. Generally adding any logic in afterEvaluate
blocks is discouraged because it runs after so much of Gradle's standard pipeline.Because of the problems with compiling the plugins as subprojects, the Flutter team explored switching the plugins to instead be compiled first as standalone AARs and then included as binary dependencies as is more typical for Android dependencies and expected by Gradle. This effort ended up being blocked, but is still occasionally automatically used as a fallback attempt when one of the typical problems with building as subprojects is detected as failing a build today.
The basic AAR flow goes like this:
pub get
behavior and version solving.build.gradle
, which applies the Flutter framework’s flutter.gradle
plugin.flutter.gradle
adds the Flutter engine and all of its support library dependencies as a jar
dependency to the Flutter app.flutter.gradle
compiles all of the plugins individually into aars
, and adds all of the Flutter plugins as aar
dependencies to the Flutter app.There were a couple blocking issues that kept us from following this flow in all cases:
TaskInternal.execute()
, a Gradle API that's been removed with no replacement in Gradle 5.androidx.annotation
. There are also other plugins that deliberately expose their own transitive jar
dependencies in a way where they’re automatically included in the app at build time by the subproject system, but apps including them as AARs will fail with “Class not found errors” unless the transitive plugin dependencies from the plugin are manually added into the apps' Gradle dependencies as well.We still fall back on this as a silent retry if we detect that the failure is caused by one of the likely error cases from the subproject structure: specifically, if we detect that the build has failed because of a likely AndroidX incompatibility issue. Jetifier is one of the Gradle tools that doesn't function normally with the subproject structure, so building the plugins as AARs can sometimes fix errors in that class.