How to produce installable builds for Android, iOS and the web, how versioning and signing work, and a checklist before handing a build to anyone. For setting up the toolchain in the first place, see Installation.
Back to the documentation index.
flutter --version # Flutter 3.41.1 / Dart 3.11.0 used for this project
flutter pub get
flutter analyze # expect: No issues found!
flutter test # expect: 100 passed, 1 skipped
Never release a build that fails analysis or tests.
# Windows: keep Gradle's cache in your profile (avoids permission problems)
$env:GRADLE_USER_HOME = "$env:USERPROFILE\.gradle"
flutter build apk --release
Output: build\app\outputs\flutter-apk\app-release.apk (a universal APK, about 58 MB with the bundled samples).
Smaller per-architecture APKs:
flutter build apk --release --split-per-abi
# app-arm64-v8a-release.apk is the one for almost every modern phone
Install on a connected phone:
adb install -r build\app\outputs\flutter-apk\app-release.apk
Or copy the APK to the phone and open it (allow “Install unknown apps” for your file manager).
flutter build appbundle --release
# build\app\outputs\bundle\release\app-release.aab
Play requires a real release signing key (§3) and a final applicationId.
| Setting | Value | File |
|---|---|---|
namespace / applicationId |
com.adaptive.physicalcomm.adaptive_physical_communication (marked TODO) |
android/app/build.gradle.kts |
minSdk / targetSdk / compileSdk |
Flutter defaults (flutter.minSdkVersion etc.) |
Same |
| Java / Kotlin target | 17 | Same |
| Release signing | Debug key (so flutter run --release works) |
Same |
| App label | “Adaptive Comm” | AndroidManifest.xml |
| Icons | mipmap-* + adaptive mipmap-anydpi-v26 |
android/app/src/main/res |
The debug key is fine for demos. To publish, or to let users update without uninstalling, create a real key once and keep it safe: losing it means you can never update the app on Play.
Create a keystore (outside the repository):
keytool -genkey -v -keystore $env:USERPROFILE\apcs-release.jks -keyalg RSA -keysize 2048 -validity 10000 -alias apcs
Create android/key.properties (never commit it; add it to .gitignore):
storePassword=<password>
keyPassword=<password>
keyAlias=apcs
storeFile=C:\\Users\\<you>\\apcs-release.jks
In android/app/build.gradle.kts, load it and use it for release:
import java.util.Properties
import java.io.FileInputStream
val keystoreProperties = Properties().apply {
val f = rootProject.file("key.properties")
if (f.exists()) load(FileInputStream(f))
}
android {
signingConfigs {
create("release") {
keyAlias = keystoreProperties["keyAlias"] as String?
keyPassword = keystoreProperties["keyPassword"] as String?
storeFile = (keystoreProperties["storeFile"] as String?)?.let { file(it) }
storePassword = keystoreProperties["storePassword"] as String?
}
}
buildTypes {
release { signingConfig = signingConfigs.getByName("release") }
}
}
Choose a final applicationId (e.g. com.<yourname>.adaptivecomm) before the first public release. It can never change afterwards.
Phones that have the debug-signed build installed must uninstall it before installing a release-signed one (the signatures differ).
Requires a Mac with Xcode and an Apple developer account.
cd ios && pod install && cd ..
open ios/Runner.xcworkspace # set Team and Bundle Identifier under Signing & Capabilities
flutter build ipa --release # build/ios/ipa/*.ipa
| Item | Value |
|---|---|
| Display name | “Adaptive Physical Communication” (CFBundleDisplayName; shortened under the icon by iOS) |
| Usage strings | Camera, microphone, photo library (add and read), motion; see Permissions and Privacy |
| Icons | ios/Runner/Assets.xcassets/AppIcon.appiconset (15 sizes, no alpha) |
| Launch screen | Dark background + logo (LaunchScreen.storyboard, LaunchImage.imageset) |
For a quick device test without TestFlight: connect the iPhone and run flutter run --release.
flutter build web --release
# build\web → serve with any static server
localhost (browser rule). For a phone on the same network, serve over HTTPS (e.g. a reverse proxy with a certificate).Local test:
flutter run -d chrome
pubspec.yaml holds version: 1.0.0+1:
1.0.0 is the user-visible version (versionName / CFBundleShortVersionString).+1 is the build number (versionCode / CFBundleVersion), which must increase for every store upload.AppBrand.version in lib/ui/widgets/app_logo.dart (shown in the About dialog) should be kept in step with pubspec.yaml.
Protocol compatibility is separate from the app version: two phones must share the same frame generation (see the Changelog).
| Asset | Generator | Re-run when |
|---|---|---|
| App icons (Android, iOS, web), splash logos, in-app logo | python tool/make_app_icon.py |
Changing the logo or brand colours |
GitHub social preview card (docs/images/social-preview.png, 1280 × 640) |
python tool/make_social_preview.py |
Changing the logo, colours or tagline; then re-upload it in Settings → General → Social preview |
| Demo photos and videos | python tool/make_sample_media.py [photo_dir] |
Changing samples |
| Explainer videos only | python tool/make_explainer_videos.py [stem …] |
Editing a topic |
The splash background colour #0B1220 appears in tool/make_app_icon.py, android/app/src/main/res/values/colors.xml, ios/Runner/Base.lproj/LaunchScreen.storyboard, web/index.html and web/manifest.json. Change it everywhere together. See Media Pipeline for the sample generators.
flutter analyze clean; flutter test greenpubspec.yaml; AppBrand.version matcheskey.properties not committedapplicationId / bundle identifier## [x.y.z] - YYYY-MM-DD heading, add a link for it at the bottom, and state any compatibility breakgit tag -a vX.Y.Z -m "APCS X.Y.Z"), push the tag, and create a GitHub Release with the changelog entry and the APK attachedversion in CITATION.cff to matchDo these once, when the repository first becomes public:
SECURITY.md describes exists..github/ISSUE_TEMPLATE/ replace blank issues.bug, triage, device-test and enhancement.flutter, qr-code, acoustic-modem, fountain-code, reed-solomon, offline-communication.git log -p | Select-String -Pattern "storePassword|keyPassword|BEGIN PRIVATE KEY" should find nothing.Search engines, including their AI answers, rank a repository mainly on its name, description, topics, README and outside links. These steps keep those signals strong:
main branch, folder / (root). GitHub Pages turns the Markdown into the documentation website using _config.yml, with a sitemap and structured data. Set the site as the repository’s Website in About.docs/images/social-preview.png (1280 × 640 px). This is the card shown when the repository link is shared on LinkedIn, X, Slack or WhatsApp. GitHub has no API for it, so it must be uploaded by hand.sitemap.xml.| Problem | Fix |
|---|---|
| Gradle fails with “access denied” or cache lock errors on Windows | Set $env:GRADLE_USER_HOME = "$env:USERPROFILE\.gradle"; close Android Studio; flutter clean |
| “SDK location not found” | Run flutter doctor; set sdk.dir in android/local.properties or ANDROID_HOME |
| Install fails with “signatures do not match” | Uninstall the old build first (debug vs release key) |
INSTALL_FAILED_OLDER_SDK |
The phone is below minSdk; use a newer phone |
| iOS: “No signing certificate” | Set the Team in Xcode → Runner → Signing & Capabilities |
| Web camera shows nothing | Serve over HTTPS or use localhost; allow camera access in the browser |
| Icons didn’t change on the phone | The launcher caches icons: uninstall and reinstall, or restart the launcher |
More runtime problems: Troubleshooting.