Vibe code to app stores
Android & iOS on macOS
14 min read
A step-by-step guide for shipping a vibe-coded Flutter app to the Google Play Store and/or the Apple App Store from a Mac — covering Flutter SDK setup, Xcode and Android Studio configuration, signing, and the full submission process for each store.
If you're using Lunadeck, the build and signing steps are handled for you automatically. This guide is for building and publishing locally, or for understanding what Lunadeck does under the hood.
What you'll need before starting:
- A Mac running macOS Sonoma 14 or later (Sequoia 15 required for Xcode 26)
- At least 20 GB of free disk space (Xcode ~12 GB, Android Studio ~8 GB, plus SDKs and simulators)
- At least 8 GB of RAM (16 GB recommended)
- A credit/debit card ($25 one-time for Google Play; $99/year for Apple Developer Program)
- Your exported Flutter project (ZIP from your AI coding tool, or a git clone)
- About 2–4 hours for initial setup, then 1–4 weeks for publishing
Part 1: Install the Required Tools
You need: Flutter SDK, Xcode (iOS builds), Android Studio (Android builds), Java (JDK 17), and CocoaPods (iOS dependency management). Homebrew handles most of this.
1.1 Install Homebrew
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"Follow the printed instructions to add Homebrew to your PATH. On Apple Silicon:
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv)"Verify:
brew --version
brew install curl wget unzip git1.2 Install the Flutter SDK
Option A: Using Homebrew (easiest)
brew install --cask flutterOption B: Manual install
Download the latest stable Flutter SDK .zip for macOS from https://docs.flutter.dev/get-started/install/macos/mobile-ios, extract it, and add to your PATH:
mkdir -p ~/development
cd ~/development
unzip ~/Downloads/flutter_macos_*.zip
echo 'export PATH="$PATH:$HOME/development/flutter/bin"' >> ~/.zshrc
source ~/.zshrcVerify and run flutter doctor:
flutter --version
flutter doctorflutter doctor lists everything Flutter needs. Resolve issues as you encounter them in the steps below — it will guide you through each one.
1.3 Install Xcode (iOS builds only)
Step 1: Install from the Mac App Store — search for "Xcode" and install it (~12 GB). Or download from https://developer.apple.com/xcode/.
Step 2: Install command line tools:
xcode-select --install
sudo xcodebuild -license acceptVerify:
xcode-select -p
# Should output: /Applications/Xcode.app/Contents/DeveloperStep 3: Open Xcode once to let it install simulators and additional components.
1.4 Install CocoaPods (iOS dependency management)
brew install cocoapods
pod --version1.5 Install Java (JDK 17) — Android only
brew install openjdk@17
sudo ln -sfn $(brew --prefix openjdk@17)/libexec/openjdk.jdk \
/Library/Java/JavaVirtualMachines/openjdk-17.jdk
echo 'export JAVA_HOME=$(/usr/libexec/java_home -v 17)' >> ~/.zshrc
source ~/.zshrc
java -version1.6 Install Android Studio — Android only
Step 1: Download the macOS .dmg from https://developer.android.com/studio, drag to Applications, and launch it. Run through the setup wizard (choose Standard, let it download the SDK and emulator).
Step 2: Install SDK components — go to Tools > SDK Manager:
- SDK Platforms: check Android 14 (API 34) or newer
- SDK Tools: check Android SDK Build-Tools, Android SDK Command-line Tools, Android Emulator, Android SDK Platform-Tools
Step 3: Set environment variables — add to ~/.zshrc:
export ANDROID_HOME="$HOME/Library/Android/sdk"
export ANDROID_SDK_ROOT="$HOME/Library/Android/sdk"
export PATH="$PATH:$ANDROID_HOME/platform-tools"
export PATH="$PATH:$ANDROID_HOME/cmdline-tools/latest/bin"Reload and verify:
source ~/.zshrc
adb --versionApple Silicon note: When creating Android virtual devices, pick ARM-based system images (not x86).
Step 4: Accept Android licenses:
flutter doctor --android-licensesType y at each prompt.
1.7 Final check
flutter doctorAll items for your target platforms should show a green checkmark. The "Chrome" and "VS Code" entries are optional and can be ignored for mobile publishing.
Part 2: Export and Prepare Your Project
2.1 Get your project files
If your AI tool provides a ZIP export:
mkdir ~/my-app
cd ~/my-app
unzip ~/Downloads/your-project.zip -d .If you're working from a git repository:
git clone https://github.com/yourname/your-app.git ~/my-app
cd ~/my-app2.2 Get dependencies
cd ~/my-app
flutter pub getFor iOS, also install CocoaPods dependencies:
cd ios
pod install
cd ..2.3 Check your app configuration
Open pubspec.yaml and confirm the version and app name:
name: my_app
version: 1.0.0+1 # versionName+versionCodeCheck the applicationId in android/app/build.gradle:
defaultConfig {
applicationId "com.yourname.myapp"
...
}Check the bundle ID in ios/Runner/Info.plist or in Xcode (App target > General > Bundle Identifier):
com.yourname.myapp
Both IDs are permanent. Change any generic
com.example.*values now, before your first store upload.
Part 3: Test Your App
3.1 Test on Android
In Android Studio > Device Manager, create a virtual device (Pixel 7, API 34+, ARM image on Apple Silicon). Start it, then:
flutter run -d androidOr run flutter devices first to see available targets.
3.2 Test on iOS
Simulator:
flutter run -d iosFlutter auto-selects the running iOS Simulator. Or specify one:
flutter run -d "iPhone 16"Physical device: Connect your iPhone. In Xcode, select the Runner target > Signing & Capabilities > select your Apple team. A free Apple ID works for testing (builds expire every 7 days; the paid Developer Program is required for App Store distribution).
Then:
flutter run -d YOUR_DEVICE_ID3.3 Common issues
"No devices found" — run flutter devices. For the emulator, make sure it's fully booted. For iOS Simulator, make sure it's open: open -a Simulator.
iOS build fails with "No signing certificate" — select an Apple team in Xcode > Signing & Capabilities.
"pod: command not found" — brew install cocoapods
pod install fails — try pod repo update first. If a plugin is incompatible with your CocoaPods version, check the plugin's documentation for workarounds.
Gradle errors (Android) — run flutter clean and rebuild.
Part 4: Prepare for Release
4.1 Set your app icon
Add flutter_launcher_icons to pubspec.yaml:
dev_dependencies:
flutter_launcher_icons: ^0.14.0
flutter_launcher_icons:
android: true
ios: true
image_path: "assets/icon.png"
adaptive_icon_background: "#FFFFFF"
adaptive_icon_foreground: "assets/icon-foreground.png"Place your icon (at least 1024×1024 px, no transparency, no rounded corners) at assets/icon.png, then:
flutter pub get
dart run flutter_launcher_iconsiOS icon note: Apple applies rounded corners automatically. Your source image must be square with no transparency and no pre-applied rounding.
4.2 Set your splash screen
dev_dependencies:
flutter_native_splash: ^2.4.0
flutter_native_splash:
color: "#FFFFFF"
image: assets/splash.png
android: true
ios: trueflutter pub get
dart run flutter_native_splash:create4.3 Update version numbers
In pubspec.yaml:
version: 1.0.0+1
# ^^^^^ versionName / CFBundleShortVersionString
# ^ versionCode / CFBundleVersion — must increment with every store uploadFlutter writes these to both the Android and iOS native projects at build time.
4.4 Configure permissions
Android — in android/app/src/main/AndroidManifest.xml:
<!-- Internet access — required for most apps -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- Add only what your app actually uses: -->
<!-- <uses-permission android:name="android.permission.CAMERA" /> -->
<!-- <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> -->iOS — in ios/Runner/Info.plist, add usage description strings for each permission. Apple rejects apps with missing or vague descriptions:
<!-- Add only for permissions your app actually uses: -->
<key>NSCameraUsageDescription</key>
<string>This app uses the camera to take photos for your profile.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app uses your location to show nearby results.</string>
<key>NSMicrophoneUsageDescription</key>
<string>This app uses the microphone for voice messages.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app accesses your photo library to let you choose images.</string>Part 5: Sign and Build the Android Release Bundle
5.1 Generate a signing key
keytool -genkey -v -keystore ~/my-app-release.keystore \
-alias my-app-key \
-keyalg RSA \
-keysize 2048 \
-validity 10000Back up this keystore file and both passwords. Losing it means you can never update your app on Google Play.
5.2 Configure Flutter signing
Create android/key.properties (do NOT commit this to git — add it to .gitignore):
storeFile=/Users/YOUR_USERNAME/my-app-release.keystore
storePassword=your_keystore_password
keyAlias=my-app-key
keyPassword=your_key_passwordEdit android/app/build.gradle — add above the android { block:
def keystorePropertiesFile = rootProject.file("key.properties")
def keystoreProperties = new Properties()
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}Inside the android { block:
android {
// ... existing config ...
signingConfigs {
release {
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
}5.3 Build the Android release bundle
flutter build appbundle --releaseOutput: build/app/outputs/bundle/release/app-release.aab
Part 6: Sign and Build the iOS Release Archive
iOS code signing requires an Apple Developer Program membership ($99/year).
6.1 Enroll in the Apple Developer Program
- Go to https://developer.apple.com/programs/
- Click "Enroll" and sign in with your Apple ID
- Choose Individual or Organization
- Pay the $99/year fee
- Wait for identity verification (a few hours to a few days)
6.2 Configure signing in Xcode
open ios/Runner.xcworkspace # Use .xcworkspace, not .xcodeproj- Select the "Runner" target in the project navigator
- Go to Signing & Capabilities
- Check "Automatically manage signing" and select your Apple Developer team
6.3 Build the iOS release
Option A: Flutter CLI (easiest)
flutter build ipaFlutter builds the archive and exports an .ipa file to build/ios/ipa/. It uses the signing configuration from Xcode.
If
flutter build ipafails with a signing error, open Xcode and confirm your team and provisioning profile are set correctly first.
Option B: Xcode GUI
- In Xcode, set the device target to "Any iOS Device (arm64)" — not a simulator
- Go to Product > Archive
- When the Organizer opens, select your archive and click Distribute App
- Choose App Store Connect > Upload and follow the prompts
Option C: Command line (advanced)
xcodebuild -workspace ios/Runner.xcworkspace \
-scheme Runner \
-configuration Release \
-archivePath ~/my-app.xcarchive \
-destination 'generic/platform=iOS' \
archiveCreate ExportOptions.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key>
<string>app-store</string>
<key>uploadSymbols</key>
<true/>
</dict>
</plist>xcodebuild -exportArchive \
-archivePath ~/my-app.xcarchive \
-exportPath ~/my-app-ipa/ \
-exportOptionsPlist ExportOptions.plist6.4 Upload to App Store Connect
- From Xcode Organizer: Distribute App > App Store Connect > Upload (after Product > Archive)
- Transporter app: free on the Mac App Store — drag and drop your
.ipa - Flutter CLI:
flutter build ipafollowed by opening the Organizer from Xcode's Window menu
Part 7: Developer Accounts and Store Listings
7.1 Google Play Developer Account
- Go to https://play.google.com/console and sign in
- Pay the one-time $25 USD fee and complete identity verification
- Wait 24–48 hours for activation
Personal accounts created after November 2023 require device verification — install the Play Console app on an Android device and follow the steps.
7.2 App Store Connect
- Go to https://appstoreconnect.apple.com and sign in
- Click + > New App
- Fill in: Platform (iOS), Name, Primary language, Bundle ID (must match your Xcode project's bundle ID), and an internal SKU
Required fields:
- Screenshots — at minimum for 6.7" (iPhone 15 Pro Max) and 6.1" (iPhone 15 Pro); capture from the Simulator
- App icon — 1024×1024 px, no transparency, no rounded corners
- Privacy policy URL — mandatory; a Notion or GitHub Pages page works
- Age rating — fill out the content questionnaire
Part 8: Google Play — The Closed Testing Requirement
New personal developer accounts cannot publish directly to production. Google requires:
- At least 12 testers opted in to a closed test track
- Those testers remain opted in for 14 consecutive days
- Then you can apply for production access
Set up closed testing
- In Play Console, click Create app and complete all required sections
- Go to Testing > Closed testing, create a tester list with 12+ Gmail addresses
- Click Create new release, accept Play App Signing, upload your
.aab, click Start rollout - Share the opt-in link — testers must install the app on a real Android device
After 14 days, apply for production access from the app Dashboard.
Part 9: App Store — Review Process
Flutter apps are fully native — they don't use a WebView — so you won't encounter the Guideline 4.2 rejection that Capacitor WebView apps commonly face. Apple reviews Flutter apps like any other native app.
What Apple looks for
- App works correctly and doesn't crash during review
- All permission usage description strings are present in
Info.plist - Privacy policy URL is provided if the app collects user data
- Store listing accurately describes what the app does
- Demo credentials are provided if the app requires login
TestFlight
Before submitting to production, test with TestFlight:
- Upload a build via
flutter build ipaor Xcode Organizer - In App Store Connect, go to the TestFlight tab
- Internal testers (up to 100 team members): no review required, available almost immediately
- External testers (up to 10,000): first build of each version requires a brief beta review (~24 hours)
TestFlight builds expire after 90 days.
Submitting for production
- In App Store Connect, select your build
- Fill out App Review Information — provide demo credentials if your app has login
- Click Submit for Review
Most reviews complete within 24 hours.
Part 10: Publish to Production
Google Play
Once you have production access:
- Production > Create new release
- Upload your
.aab, add release notes, select distribution countries - Start rollout to production
App Store
Once Apple approves, choose Immediate, Manual, or Scheduled release.
Part 11: Updating Your App
# 1. Make your code changes
# 2. Increment the build number in pubspec.yaml
# e.g., 1.0.0+1 → 1.0.0+2
# (both Android versionCode and iOS CFBundleVersion are derived from this)
# 3. If iOS dependencies changed:
cd ios && pod install && cd ..
# Android
flutter build appbundle --release
# Upload new .aab to Google Play Console
# iOS
flutter build ipa
# Upload to App Store Connect via Transporter or Xcode OrganizerQuick Reference
# === SETUP ===
flutter pub get
cd ios && pod install && cd ..
flutter doctor --android-licenses
# === TESTING ===
flutter devices
flutter run # auto-selects a device
flutter run -d android
flutter run -d ios
# === ANDROID RELEASE ===
flutter build appbundle --release
# Output: build/app/outputs/bundle/release/app-release.aab
# === iOS RELEASE (CLI) ===
flutter build ipa
# Output: build/ios/ipa/YourApp.ipa
# === iOS RELEASE (Xcode GUI) ===
open ios/Runner.xcworkspace
# Product > Archive > Distribute App > App Store Connect > Upload
# === CLEAN BUILD ===
flutter clean && flutter pub getTroubleshooting
"flutter: command not found" — Flutter is not in your PATH. Follow Part 1.2 and run source ~/.zshrc.
"Android licenses not accepted" — flutter doctor --android-licenses → type y at each prompt.
"JAVA_HOME is not set" (Android) — add to ~/.zshrc:
export JAVA_HOME=$(/usr/libexec/java_home -v 17)Gradle fails with "Could not determine java version" — multiple Java versions installed. Confirm JAVA_HOME points to JDK 17 with /usr/libexec/java_home -V.
Xcode build fails with "No signing certificate" — select your Apple Developer team in Xcode > Signing & Capabilities. Paid membership required for distribution.
pod install fails — try pod repo update first. If a plugin is incompatible, check its documentation for workarounds.
flutter build ipa fails with signing error — open Xcode, confirm the signing team is set, and try again. Or use Xcode's Product > Archive for more detailed error output.
iOS Simulator is slow — close other heavy apps. Try Device > Erase All Content and Settings in the Simulator menu.
Dart/Flutter version mismatch — run flutter upgrade to update to latest stable.
"The request was denied by service delegate" — provisioning profile or certificate issue. In Xcode, toggle "Automatically manage signing" off and back on, or go to Settings > Accounts > download profiles.
Realistic Expectations
The Google Play timeline is long. The 14-day testing requirement plus review means 3–4 weeks from start to live. The App Store is faster but rejections add cycles.
Flutter gets much easier App Store approval than WebView wrappers. Because Flutter renders native-feeling UI compiled to ARM code, Apple doesn't apply the Guideline 4.2 WebView rejection. The App Store bar is still real — your app must work, not crash, and comply with all guidelines — but the WebView problem isn't one you face.
The costs are different. Google Play: $25 one-time. Apple: $99 per year, every year. A lapsed membership removes your apps from the App Store within 24 hours.
AI-generated Flutter code frequently has plugin issues. Flutter plugins often require minimum SDK versions, specific iOS permissions, or pod install after adding them. Budget time for this.
iOS and Android will surface different bugs. Test on both platforms before submitting to either store.