You Built an App With AI. Here's How to Ship It to Android
You described an app to Lovable, Bolt, Cursor, or Claude, and it built one. It runs in the preview pane. It looks finished. Then you try to put it on your actual phone, and the first honest error of the project shows up before any code runs at all:
$ flutter build apk zsh: command not found: flutter
Nothing is wrong with your app. The gap is simpler and more annoying than that: the AI handed you a project — a folder of source code — and a phone only installs a signed binary. Something has to compile one into the other, and that something is a toolchain nobody mentioned while you were prompting. This post covers both ways across: doing it yourself, honestly described, and skipping it.
What you have vs. what a phone accepts
| What the AI tool gave you | What Android installs | |
|---|---|---|
| Format | A Flutter or web project (source files, pubspec.yaml, an android/ folder) | A compiled .apk (direct install) or .aab (Google Play) |
| Runs where | The tool's preview, or a dev machine with the SDK installed | Any Android device, no dev tools involved |
| Signature | None — source code isn't signed | Mandatory. Android refuses unsigned APKs outright |
The signature requirement is the part that surprises people, so here is keystores in one paragraph: a keystore is a password-protected file containing a private key, and every Android app must be signed with one before any device will install it. The key is your app's permanent identity — every future update must be signed with the same key, or phones and Google Play will reject the update as an impostor. Lose the keystore and you can never update the app again, only publish a new one. That is the entire concept. The ceremony around it (keytool, key.properties, signingConfigs) is just plumbing.
The honest local path (the 10GB wall)
There is no trick that avoids this if you build locally, so here it is straight. The Android Studio download is a couple of gigabytes, but after the SDK platforms, build tools, emulator images, and Gradle's caches settle in, expect 10GB+ of disk and an hour or two before your first successful build.
Install Android Studio
From developer.android.com. Run it once so it downloads the SDK, then accept the licenses from a terminal: flutter doctor --android-licenses.
Install the Flutter SDK
Download, unzip, add flutter/bin to your PATH, then run flutter doctor repeatedly until every line is green. This step is where most first-timers stall.
Create a keystore
One command, but keep the file and passwords forever (see the paragraph above):
$ keytool -genkey -v -keystore upload-keystore.jks \
-keyalg RSA -keysize 2048 -validity 10000 -alias uploadWire the keystore into Gradle
Create android/key.properties with the paths and passwords, and reference it from the signingConfigs block in android/app/build.gradle. The Flutter docs have the exact snippet.
Build
flutter build apk --release for a file you can install directly, or flutter build appbundle for Google Play.
For a hand-written project by an experienced Flutter developer, step 5 usually works. For an AI-generated project, step 5 is where a second, distinct category of problem begins — and it is not your fault either.
The five build failures we actually see in AI-generated projects
We run a build service, so failed AI-generated uploads are our daily weather. The same five failures account for most of them. Each one below shows the real error text and what our preflight does about it automatically.
1. The Gradle wrapper is missing pieces
Error: Could not find or load main class org.gradle.wrapper.GradleWrapperMain Caused by: java.lang.ClassNotFoundException: org.gradle.wrapper.GradleWrapperMain
gradle-wrapper.jaris a small binary file, and AI tools and chat exports routinely drop it — some templates even gitignore it. The scripts are there; the jar they launch isn't. Auto-repair:preflight detects the missing wrapper files and seeds a known-good wrapper matching the project's declared Gradle version.
2. A stray gradlew copied from a tutorial
FAILURE: Build failed with an exception. * What went wrong: A problem occurred evaluating root project 'android'. > Minimum supported Gradle version is 8.4. Current version is 6.7.
The AI reproduced a gradle-wrapper.properties from a 2020-era tutorial in its training data, pinning a Gradle that predates every current Android Gradle Plugin. Auto-repair: the wrapper distribution, AGP, and Kotlin versions are raised to the minimum floors that work together — raised, never lowered, so intentionally modern projects are untouched.
3. The manifest still uses the deleted v1 embedding
Build failed due to use of deleted Android v1 embedding.
Flutter's original Android integration was removed years ago, but pre-2020 tutorials referencing io.flutter.app.FlutterApplication live on in training data, and models still emit that manifest shape. Auto-repair: the manifest is migrated to the v2 embedding — the application entry is rewritten and the flutterEmbedding=2 metadata added.
4. A keystore is referenced but not included
Execution failed for task ':app:validateSigningRelease'. > Keystore file '/Users/alex/upload-keystore.jks' not found for signing config 'release'.
The AI helpfully wrote a release signing config pointing at a keystore on a machine that doesn't exist — sometimes literally someone else's home directory from a Stack Overflow answer. Auto-repair: when the referenced keystore is absent, the build falls back to a managed per-project key we generate and keep, so every future build of that project is signed with the same identity and updates install cleanly over old versions.
5. Ancient plugins pulled in by "any" constraints
A problem occurred configuring project ':device_info'. > Namespace not specified. Specify a namespace in the module's build file. ... > Could not GET 'https://jcenter.bintray.com/...'.
AI-written pubspecs love any version constraints, which can resolve to plugin releases from 2019 — before AGP required a namespace, and pointing at jcenter, a repository that has been shut down for years. Auto-repair: the missing namespace is injected into plugin modules that lack one, and dead jcenter() references are replaced with Maven Central.
Every repair above is something a Flutter developer would eventually do by hand after an hour of searching the error text. The pipeline just does it before the build starts, and logs what it changed. If the project is simply from an older era, the toolchain adapts instead — we wrote up how the version matrix picked the right 2023 Flutter for Google's own archived gallery app.
Or skip all of it
The alternative path is one step: zip the project folder the AI gave you — the one containing pubspec.yaml — and upload it. The pipeline runs the preflight repairs above, builds, and signs the result with the managed per-project key. You get back an APK for direct install and an AAB for Google Play. No Android Studio, no PATH, no keytool.
Two honest caveats, because this is where build services usually oversell. First: some projects still fail — genuinely broken Dart code, for example, is your bug, not a toolchain problem. When a build fails you get the actual diagnosis from the log, not a shrug, and the build credit is refunded automatically. Second: iOS. We produce unsigned preview IPA builds on real Apple hardware, which is useful for testing — but App Store distribution requires your own Apple Developer account and your own signing. We don't claim otherwise, and you should be suspicious of anyone who does. If you just know Flutter and want the deeper Android build walkthrough, that guide is here.
FAQ
Do I ever need to install Android Studio?
Not for building. If you keep iterating on the app through your AI tool and only need installable builds out the other end, the upload path covers it entirely. You'd want Android Studio if you plan to hand-edit native Android code or debug on-device with breakpoints — most people shipping an AI-generated app never do either.
What exactly do I upload?
A zip of the project root — the folder containing pubspec.yaml. Include everything the tool exported, including pubspec.lock if present. You can leave out build/ and .dart_tool/; they're regenerated anyway.
Can you put my app on the Apple App Store?
No, and we won't pretend to. iOS builds come back as unsigned preview IPAs; App Store submission requires your own Apple Developer membership ($99/year) and signing with your own certificate. Android is the full story here: signed APK and AAB, ready for direct install or Google Play under your own developer account.
What happens if the build fails?
You see the real failure reason extracted from the build log — the same error text a developer would read — and the credit for that build is refunded automatically. A failed build costs you nothing, which also keeps us honest: we only get paid when a binary comes out.
The folder the AI gave you is enough.
Zip it, upload it, and let the preflight deal with the wrapper, the manifest, and the signing. If it fails, you pay nothing and see exactly why.
Code2Native Engineering
Engineering team
Written by the Code2Native engineering team — the people who build and operate the cloud build pipeline.