101 lines
4.8 KiB
Markdown
101 lines
4.8 KiB
Markdown
|
|
# flutter-android-template
|
||
|
|
|
||
|
|
Starting point for Flutter Android apps on **halogen**. Generated by `flutter create` and
|
||
|
|
then annotated — the value here is not the code, it is the notes on the parts that go
|
||
|
|
wrong six months later.
|
||
|
|
|
||
|
|
Generated **2026-08-23** against **Flutter 3.47.1**, verified `--debug` and `--release`.
|
||
|
|
|
||
|
|
## Use it
|
||
|
|
|
||
|
|
Gitea template repo — *Use this template* on the web UI, or:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
tea repos create --owner android-stuff --name my-app
|
||
|
|
# then, locally:
|
||
|
|
flutter create --org com.novoyuuparosk --project-name my_app --platforms android .
|
||
|
|
```
|
||
|
|
|
||
|
|
After cloning, before anything else:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
flutter-android-preflight . # on halogen; see below
|
||
|
|
flutter pub get
|
||
|
|
flutter build apk --debug
|
||
|
|
```
|
||
|
|
|
||
|
|
`android/local.properties` is gitignored and regenerated per machine. Do not commit it.
|
||
|
|
|
||
|
|
## Why this repo exists
|
||
|
|
|
||
|
|
Two apps in this org became unbuildable without a line of their code changing. They had
|
||
|
|
been pinned to Gradle 8.12 / AGP 8.9.1 / Kotlin 2.1.0 — fine when written, and by
|
||
|
|
2026-08-23 all three were **below the hard floors Flutter refuses to build under**.
|
||
|
|
|
||
|
|
Flutter carries those floors as source constants inside the SDK, and moves them every
|
||
|
|
release:
|
||
|
|
|
||
|
|
| | |
|
||
|
|
|---|---|
|
||
|
|
| `packages/flutter_tools/gradle/src/main/kotlin/DependencyVersionChecker.kt` | error + warn floors for Gradle / AGP / Kotlin / Java / minSdk |
|
||
|
|
| `packages/flutter_tools/lib/src/android/gradle_utils.dart` | template defaults, compileSdk, NDK |
|
||
|
|
|
||
|
|
**Read those two files instead of recalling version numbers.** Any remembered number is
|
||
|
|
wrong on a long enough timeline, including one you read in a blog post last week.
|
||
|
|
|
||
|
|
## The rule that keeps this from happening again
|
||
|
|
|
||
|
|
> **Track Flutter's template defaults, not its floors.**
|
||
|
|
|
||
|
|
Flutter promotes its `warn` floor to the `error` floor in the very next release — its own
|
||
|
|
source says so. A project sitting just above the *hard* floor has one release of runway
|
||
|
|
before it breaks. A project on the template defaults has the full cycle.
|
||
|
|
|
||
|
|
That is why this template is on Gradle 9.3.1 / AGP 9.1.0 / Kotlin 2.4.0 / Java 17 rather
|
||
|
|
than the lower combo that merely builds today.
|
||
|
|
|
||
|
|
`/usr/local/bin/flutter-android-preflight` on halogen automates the comparison: it parses
|
||
|
|
the floors out of the *installed* SDK, checks a project against them, and `--fix` rewrites
|
||
|
|
the project. Run it **after every `flutter upgrade`**, not when a build breaks. `/opt/flutter`
|
||
|
|
tracks the `stable` branch, so it moves on its own.
|
||
|
|
|
||
|
|
## Traps, none of which are in a changelog
|
||
|
|
|
||
|
|
- ⚠ **Never edit `~/.pub-cache`.** It is shared between users, unversioned, invisible to
|
||
|
|
`git status`, and wiped by the next `pub get`. An edit there poisons every build on the
|
||
|
|
machine and leaves no trace in any repo. If a plugin needs different build settings,
|
||
|
|
override them from `android/build.gradle.kts` — there is a worked example, commented
|
||
|
|
out, in that file. Damage is repaired with `dart pub cache repair --all`.
|
||
|
|
- ⚠ **`kotlinOptions {}` is a script COMPILATION ERROR under Kotlin ≥ 2.3**, not a
|
||
|
|
deprecation warning. Use `kotlin { compilerOptions { jvmTarget = ... } }`, as
|
||
|
|
`android/app/build.gradle.kts` here already does.
|
||
|
|
- ⚠ **AGP 8.x cannot run on Gradle ≥ 9.6.** Gradle 9.6.0 removed `InternalProblems`, which
|
||
|
|
every AGP 8.x builds against; Gradle 9.7 fails at configuration time. While on AGP 8.x,
|
||
|
|
Gradle is capped at 9.5. Moot on AGP 9, but it is what makes "just use the newest
|
||
|
|
Gradle" the wrong instinct.
|
||
|
|
- ⚠ **Built-in Kotlin is a dead end on Flutter 3.47.1.** Flutter warns that applying the
|
||
|
|
Kotlin Gradle Plugin "will cause build failures in future versions" and points at the
|
||
|
|
migration guide. Do not follow it yet: with `android.builtInKotlin=true`, AGP 9.1.0
|
||
|
|
supplies **Kotlin 2.2.10**, which is *below Flutter's own 2.2.20 floor* — Flutter rejects
|
||
|
|
the Kotlin that Flutter told you to adopt. Keep `builtInKotlin=false` and apply KGP
|
||
|
|
explicitly until AGP ships a Kotlin at or above the SDK's floor.
|
||
|
|
- **`android.enableJetifier`** is obsolete and removed outright in AGP 9. It is absent here
|
||
|
|
deliberately; do not let an old snippet reintroduce it.
|
||
|
|
- **Abandoned plugins are the residual risk.** The toolchain is now easy to keep current;
|
||
|
|
a pub package last published in 2022 is not. Check `flutter pub outdated` before
|
||
|
|
adopting a dependency that looks quiet.
|
||
|
|
|
||
|
|
## Shared toolchain on halogen
|
||
|
|
|
||
|
|
`/opt/flutter` and `/opt/android-sdk` are group `flutterdev` (`mikkeli`, `hermes`) with
|
||
|
|
default ACLs granting the group write. Gradle compiles the Flutter Gradle plugin **in
|
||
|
|
place** inside the SDK, so without those ACLs whichever user builds first locks the other
|
||
|
|
out — and the resulting error mentions nothing about permissions:
|
||
|
|
|
||
|
|
```
|
||
|
|
IllegalStateException: The settings are not yet available for build.
|
||
|
|
```
|
||
|
|
|
||
|
|
If that appears, check ownership under
|
||
|
|
`/opt/flutter/packages/flutter_tools/gradle/{build,.gradle}` before believing anything else.
|