| 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. | |
| - The Flutter tool fetches any plugin dependencies of the Flutter app from pub | |
| and downloads it to the pub cache. This step follows normal `pub get` behavior | |
| and version solving. | |
| - The Flutter tool writes project metadata to some local temporary files. This | |
| includes things like the location of the local Flutter SDK, and paths to all | |
| the newly downloaded plugins from pub on disk. | |
| - The Flutter tool invokes Gradle to build the APK. | |
| - Gradle reads the app's `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). | |
| - Gradle compiles the Flutter app and all of its subprojects (Flutter plugins) | |
| into a root APK. | |
| 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. | |
| ## Plugins are compiled as subprojects (usually) | |
| 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"](https://docs.gradle.org/current/userguide/multi_project_builds.html#sec:subproject_configuration), | |
| 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: | |
| ```console | |
| $ ./gradlew projects | |
| > Task :projects | |
| ------------------------------------------------------------ | |
| Root project | |
| ------------------------------------------------------------ | |
| Root project 'android' | |
| +--- Project ':app' | |
| +--- Project ':bar' | |
| +--- Project ':baz'' | |
| \--- Project ':foo' | |
| ``` | |
| ### The how | |
| - [The Flutter tool's Dart | |
| code](https://github.com/flutter/flutter/blob/82410a5ae7159b9f39e85b713decf532840ac70c/packages/flutter_tools/lib/src/build_system/targets/assets.dart#L115-L119) | |
| writes a `.flutter-plugins` file containing a list of all the plugins used by | |
| the app and their location on disk. | |
| - [`flutter.gradle`](https://github.com/flutter/flutter/blob/508c35940b4989c400e7b5a2851d03e4bd0b2ca2/packages/flutter_tools/gradle/flutter.gradle#L243-L264) | |
| reads from this file and actually adds the plugins as Gradle subproject | |
| dependencies of the app. | |
| - This also depends on the Flutter app having a block of code in its template | |
| that actually references and builds these subprojects once they're defined, | |
| see ["hello world" as an example of the app's Gradle | |
| script](https://github.com/flutter/flutter/blob/508c35940b4989c400e7b5a2851d03e4bd0b2ca2/examples/hello_world/android/settings.gradle#L15-L19). | |
| ### Special Considerations | |
| 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: | |
| - Plugins have their own `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. | |
| - If a plugin has a problem in its Android code, the entire user's app will fail | |
| to compile with a stack trace that may or may not really point to the plugin's | |
| code on disk. | |
| - If the plugin's code conflicts with the app's Android source code (for | |
| example, the plugin has a conflicting transitive dependency), Gradle will | |
| generally fail to compile with difficult to translate stack traces. | |
| - These subprojects are added in an `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. | |
| ## Sometimes plugins are compiled as AARs | |
| 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: | |
| - The Flutter tool fetches any plugin dependencies of the Flutter app from pub | |
| and downloads it to the pub cache. This step follows normal `pub get` behavior | |
| and version solving. | |
| - The Flutter tool writes project metadata to some local temporary files. This | |
| includes things like the location of the local Flutter SDK, and paths to all | |
| the newly downloaded plugins from pub on disk. | |
| - The Flutter tool invokes Gradle to build the APK. | |
| - Gradle reads the app's `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. | |
| - Gradle compiles the Flutter app and all of its binary dependencies into a root | |
| APK. | |
| ### Why this isn't the standard | |
| There were a couple blocking issues that kept us from following this flow in all | |
| cases: | |
| - The implementation of this depends on `TaskInternal.execute()`, a Gradle API | |
| that's been removed with no replacement in Gradle 5. | |
| - This style of build is extremely slow compared to the previous build flow. | |
| - The plugins currently in the Flutter ecosystem were written under the previous | |
| build system and in many cases depend on the transitive dependency bleed to | |
| work. For example, many plugins built this way will fail to compile | |
| individually because they're missing an explicit dependency on | |
| `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. | |
| ### When this is still used | |
| 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. |