diff --git a/android/.gitignore b/android/.gitignore
new file mode 100644
index 0000000..be3943c
--- /dev/null
+++ b/android/.gitignore
@@ -0,0 +1,14 @@
+gradle-wrapper.jar
+/.gradle
+/captures/
+/gradlew
+/gradlew.bat
+/local.properties
+GeneratedPluginRegistrant.java
+.cxx/
+
+# Remember to never publicly share your keystore.
+# See https://flutter.dev/to/reference-keystore
+key.properties
+**/*.keystore
+**/*.jks
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
new file mode 100644
index 0000000..b100a8d
--- /dev/null
+++ b/android/app/build.gradle.kts
@@ -0,0 +1,46 @@
+plugins {
+ id("com.android.application")
+ // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
+ id("dev.flutter.flutter-gradle-plugin")
+}
+
+android {
+ namespace = "com.example.flutter_template"
+ compileSdk = flutter.compileSdkVersion
+ ndkVersion = flutter.ndkVersion
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+
+ defaultConfig {
+ // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+ applicationId = "com.example.flutter_template"
+ // You can update the following values to match your application needs.
+ // For more information, see: https://flutter.dev/to/review-gradle-config.
+ // minSdk = 23 required by flutter_secure_storage 9.x (see docs/setup_reference.md).
+ minSdk = 23
+ targetSdk = flutter.targetSdkVersion
+ versionCode = flutter.versionCode
+ versionName = flutter.versionName
+ }
+
+ buildTypes {
+ release {
+ // TODO: Add your own signing config for the release build.
+ // Signing with the debug keys for now, so `flutter run --release` works.
+ signingConfig = signingConfigs.getByName("debug")
+ }
+ }
+}
+
+kotlin {
+ compilerOptions {
+ jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17
+ }
+}
+
+flutter {
+ source = "../.."
+}
diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000..399f698
--- /dev/null
+++ b/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e048f86
--- /dev/null
+++ b/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java b/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java
index d62ab0c..0092f92 100644
--- a/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java
+++ b/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java
@@ -25,6 +25,11 @@ public static void registerWith(@NonNull FlutterEngine flutterEngine) {
} catch (Exception e) {
Log.e(TAG, "Error registering plugin flutter_secure_storage, com.it_nomads.fluttersecurestorage.FlutterSecureStoragePlugin", e);
}
+ try {
+ flutterEngine.getPlugins().add(new com.github.dart_lang.jni.JniPlugin());
+ } catch (Exception e) {
+ Log.e(TAG, "Error registering plugin jni, com.github.dart_lang.jni.JniPlugin", e);
+ }
try {
flutterEngine.getPlugins().add(new dev.fluttercommunity.plus.packageinfo.PackageInfoPlugin());
} catch (Exception e) {
diff --git a/android/app/src/main/kotlin/com/example/flutter_template/MainActivity.kt b/android/app/src/main/kotlin/com/example/flutter_template/MainActivity.kt
new file mode 100644
index 0000000..660cdb0
--- /dev/null
+++ b/android/app/src/main/kotlin/com/example/flutter_template/MainActivity.kt
@@ -0,0 +1,5 @@
+package com.example.flutter_template
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity : FlutterActivity()
diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml
new file mode 100644
index 0000000..f74085f
--- /dev/null
+++ b/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml
new file mode 100644
index 0000000..304732f
--- /dev/null
+++ b/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..db77bb4
Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..17987b7
Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..09d4391
Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..d5f1c8d
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4d6372e
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml
new file mode 100644
index 0000000..06952be
--- /dev/null
+++ b/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..cb1ef88
--- /dev/null
+++ b/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml
new file mode 100644
index 0000000..399f698
--- /dev/null
+++ b/android/app/src/profile/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/android/build.gradle.kts b/android/build.gradle.kts
new file mode 100644
index 0000000..dbee657
--- /dev/null
+++ b/android/build.gradle.kts
@@ -0,0 +1,24 @@
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+val newBuildDir: Directory =
+ rootProject.layout.buildDirectory
+ .dir("../../build")
+ .get()
+rootProject.layout.buildDirectory.value(newBuildDir)
+
+subprojects {
+ val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
+ project.layout.buildDirectory.value(newSubprojectBuildDir)
+}
+subprojects {
+ project.evaluationDependsOn(":app")
+}
+
+tasks.register("clean") {
+ delete(rootProject.layout.buildDirectory)
+}
diff --git a/android/gradle.properties b/android/gradle.properties
new file mode 100644
index 0000000..e96108c
--- /dev/null
+++ b/android/gradle.properties
@@ -0,0 +1,6 @@
+org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
+android.useAndroidX=true
+# This newDsl flag was added by the Flutter template
+android.newDsl=false
+# This builtInKotlin flag was added by the Flutter template
+android.builtInKotlin=false
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..2d428bf
--- /dev/null
+++ b/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-all.zip
diff --git a/android/key.properties.example b/android/key.properties.example
index fdc043f..bf58cba 100644
--- a/android/key.properties.example
+++ b/android/key.properties.example
@@ -2,6 +2,12 @@
# Copy this file to key.properties and fill in your values
# NEVER commit key.properties to git!
+# NOTE: The generated android/app/build.gradle.kts currently signs the release
+# build with the debug keystore (Flutter's default). It does NOT yet read this
+# file. Real release signing via key.properties is tracked in the follow-up
+# issue #18 "Release signing: key.properties + keystore for make build-android".
+# Keep this example as the reference format for when that is wired in.
+
storePassword=YOUR_KEYSTORE_PASSWORD
keyPassword=YOUR_KEY_PASSWORD
keyAlias=release
diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts
new file mode 100644
index 0000000..c21f0c5
--- /dev/null
+++ b/android/settings.gradle.kts
@@ -0,0 +1,26 @@
+pluginManagement {
+ val flutterSdkPath =
+ run {
+ val properties = java.util.Properties()
+ file("local.properties").inputStream().use { properties.load(it) }
+ val flutterSdkPath = properties.getProperty("flutter.sdk")
+ require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
+ flutterSdkPath
+ }
+
+ includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
+
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
+plugins {
+ id("dev.flutter.flutter-plugin-loader") version "1.0.0"
+ id("com.android.application") version "9.0.1" apply false
+ id("org.jetbrains.kotlin.android") version "2.3.20" apply false
+}
+
+include(":app")
diff --git a/docs/plans/android_scaffolding.md b/docs/plans/android_scaffolding.md
index 6603ae9..d5a41eb 100644
--- a/docs/plans/android_scaffolding.md
+++ b/docs/plans/android_scaffolding.md
@@ -154,7 +154,7 @@ No race conditions identified — all operations are synchronous and single-thre
## No-Gos (Out of Scope)
-- [SEPARATE-SLUG] Release signing setup (creating a real `key.properties` + keystore and verifying `make build-android` / `flutter build apk --release`) — out of scope because it requires a real keystore and secrets that must not be committed. The acceptance criteria only require the debug build. **The release build (`make build-android`) is NOT verified by this fix**; it is deferred to a concrete follow-up issue, "Release signing: key.properties + keystore for make build-android" (owner: yudame), linked to #13 (resolves CONCERN 2).
+- [SEPARATE-SLUG] Release signing setup (creating a real `key.properties` + keystore and verifying `make build-android` / `flutter build apk --release`) — out of scope because it requires a real keystore and secrets that must not be committed. The acceptance criteria only require the debug build. **The release build (`make build-android`) is NOT verified by this fix**; it is deferred to a concrete follow-up issue, "Release signing: key.properties + keystore for make build-android" (owner: yudame, #18), linked to #13 (resolves CONCERN 2).
- [EXTERNAL] Installing/verifying the Android SDK, JDK, or licenses on a developer machine — a human/environment action, not a repo change.
## Update System
@@ -184,7 +184,7 @@ No documentation site changes required.
- [ ] `minSdk = 23` is set in `android/app/build.gradle.kts` (for `flutter_secure_storage`).
- [ ] The existing 2 tracked files (`GeneratedPluginRegistrant.java`, `key.properties.example`) are preserved.
- [ ] `git status` shows only new `android/` files added and `key.properties.example` unchanged. `GeneratedPluginRegistrant.java` may appear as `M` (content-identical or content-drifted regeneration is acceptable) — only a deletion of `key.properties.example` or an unexpected new top-level file fails the gate (resolves CONCERN 1).
-- [ ] Release build (`make build-android` / `flutter build apk --release`) is NOT verified by this fix; it is explicitly deferred to the release-signing follow-up issue (resolves CONCERN 2). The debug build is the gate.
+- [ ] Release build (`make build-android` / `flutter build apk --release`) is NOT verified by this fix; it is explicitly deferred to the release-signing follow-up issue #18 (resolves CONCERN 2). The debug build is the gate.
## Team Orchestration
@@ -287,14 +287,14 @@ When this plan is executed, the lead agent orchestrates work using Task tools. T
## Open Questions
1. ~~**Toolchain pins source**~~ **RESOLVED (decision a)** — The issue's Solution Sketch cites pins (AGP 9.0.1, Gradle 9.1.0, JDK 17, Android SDK 36) that are not documented anywhere in the repo. Running `flutter create` and committing the generated Gradle files inherently hard-pins whatever the author's Flutter 3.44.8 emits, so the question was decided implicitly by Task 1. To make the committed toolchain explicit rather than emergent, Task 3 records the concrete generated AGP / Gradle wrapper / compileSdk / minSdk versions in the completion note (resolves CONCERN 3). No hard-pin override of the generated toolchain.
-2. ~~**Release signing follow-up**~~ **RESOLVED (deferred)** — Deferred as a concrete follow-up issue, "Release signing: key.properties + keystore for make build-android" (owner: yudame), linked to #13. This fix verifies only the debug build; `make build-android` / `flutter build apk --release` is explicitly out of scope and NOT verified here (resolves CONCERN 2).
+2. ~~**Release signing follow-up**~~ **RESOLVED (deferred)** — Deferred as a concrete follow-up issue, "Release signing: key.properties + keystore for make build-android" (owner: yudame, #18), linked to #13. This fix verifies only the debug build; `make build-android` / `flutter build apk --release` is explicitly out of scope and NOT verified here (resolves CONCERN 2).
## Implementation Notes (Revision Pass)
Embedded resolutions for the do-plan-critique findings (commit 6e444db, verdict READY TO BUILD with concerns). Each concern's resolution is applied in the plan text and linked where relevant:
- **CONCERN 1 (Risk & Robustness)**: Success criterion 6 and the Verification "Existing files preserved" row now explicitly permit `GeneratedPluginRegistrant.java` appearing as `M` (content-identical or content-drifted regeneration is acceptable), with only a `D ` deletion of `key.properties.example` or an unexpected new top-level file failing the gate. Task 1 carries the same note. The gate is no longer self-contradictory.
-- **CONCERN 2 (Scope & Value)**: The plan is now explicit that `make build-android` / `flutter build apk --release` is NOT verified by this fix — the debug build is the gate. Deferred to a concrete follow-up issue, "Release signing: key.properties + keystore for make build-android" (owner: yudame, linked to #13), noted in No-Gos, Success Criteria, and resolved Open Question 2.
+- **CONCERN 2 (Scope & Value)**: The plan is now explicit that `make build-android` / `flutter build apk --release` is NOT verified by this fix — the debug build is the gate. Deferred to a concrete follow-up issue, "Release signing: key.properties + keystore for make build-android" (owner: yudame, #18), noted in No-Gos, Success Criteria, and resolved Open Question 2.
- **CONCERN 3 (Toolchain)**: Open Question 1 is resolved as decision (a) — accept the toolchain `flutter create` emits. Task 3 now captures and records the concrete generated AGP / Gradle wrapper / compileSdk / minSdk versions in the completion note, making the committed toolchain explicit rather than emergent.
- **NIT (Scope)**: Verification and Success Criteria now state the applicationId is `com.example.flutter_template` — the "matches pubspec" criterion is a substring match (contains `flutter_template`), not literal equality, and the `com.example` prefix is accepted for a template.
@@ -305,6 +305,6 @@ Embedded resolutions for the do-plan-critique findings (commit 6e444db, verdict
Round 2 re-critique confirms the four round-1 resolutions are embedded in the plan text and close their concerns; no new findings. Verdict: **READY TO BUILD (no concerns)**.
- **CONCERN 1 (GeneratedPluginRegistrant.java `M` tolerance)** — CLOSED. Success Criteria (line 186), Task 1 (line 229), and Verification (line 273) all explicitly permit `GeneratedPluginRegistrant.java` to appear as `M`; only a `D ` deletion of `key.properties.example` or an unexpected new top-level file fails the gate. The gate is no longer self-contradictory.
-- **CONCERN 2 (release-build out-of-scope + follow-up)** — CLOSED. No-Gos (line 157), Success Criteria (line 187), and resolved Open Question 2 (line 290) all state the release build (`make build-android` / `flutter build apk --release`) is NOT verified by this fix and is deferred to the concrete follow-up issue "Release signing: key.properties + keystore for make build-android" (owner: yudame, linked to #13).
+- **CONCERN 2 (release-build out-of-scope + follow-up)** — CLOSED. No-Gos (line 157), Success Criteria (line 187), and resolved Open Question 2 (line 290) all state the release build (`make build-android` / `flutter build apk --release`) is NOT verified by this fix and is deferred to the concrete follow-up issue "Release signing: key.properties + keystore for make build-android" (owner: yudame, #18).
- **CONCERN 3 (toolchain decision (a))** — CLOSED. Open Question 1 (line 289) resolves to decision (a); Task 3 (line 251) captures the concrete generated AGP / Gradle wrapper / compileSdk / minSdk versions in the completion note, making the committed toolchain explicit rather than emergent.
- **NIT (applicationId substring match)** — CLOSED. Success Criteria (line 183), Verification (line 272), and Task 3 (line 250) state the applicationId is `com.example.flutter_template`, accepted as a substring match (contains `flutter_template`), not literal equality.
diff --git a/pubspec.lock b/pubspec.lock
index eed9390..f6031ab 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -485,6 +485,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.5"
+ jni:
+ dependency: transitive
+ description:
+ name: jni
+ sha256: d2c361082d554d4593c3012e26f6b188f902acd291330f13d6427641a92b3da1
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.14.2"
js:
dependency: transitive
description:
@@ -761,18 +769,18 @@ packages:
dependency: transitive
description:
name: sentry
- sha256: "599701ca0693a74da361bc780b0752e1abc98226cf5095f6b069648116c896bb"
+ sha256: c2ecd8abe82e63cdcb6947f71320612ced56e10ba94db7529d85cc02be47cb3b
url: "https://pub.dev"
source: hosted
- version: "8.14.2"
+ version: "9.27.0"
sentry_flutter:
dependency: "direct main"
description:
name: sentry_flutter
- sha256: "5ba2cf40646a77d113b37a07bd69f61bb3ec8a73cbabe5537b05a7c89d2656f8"
+ sha256: ec89cc6ba939ca19155ea83900d9740a36544f50b3b6baf265518e3348fb0f50
url: "https://pub.dev"
source: hosted
- version: "8.14.2"
+ version: "9.27.0"
shared_preferences:
dependency: "direct main"
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index f685e41..a1983b1 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -46,7 +46,7 @@ dependencies:
json_annotation: ^4.9.0
# Monitoring (Production)
- sentry_flutter: ^8.11.0
+ sentry_flutter: ^9.27.0
dev_dependencies:
flutter_test: