# How to Publish a Flutter App on Google Play Store and Apple App Store: Step-by-Step Guide


Building the app is the easy part. Getting it into the hands of users — navigating developer consoles, signing configurations, store policies, and review processes — is where first-time publishers tend to get stuck. This guide covers the complete publishing process for both Google Play and the Apple App Store, from a clean Flutter project to a live listing.

---

## Part 1: Google Play Store

### Prepare Your App

**App identity**

Update your app name in `android/app/src/main/AndroidManifest.xml`:

```xml
<application android:label="YourAppName">
```

Add a proper launcher icon using `flutter_launcher_icons`:

```yaml
# pubspec.yaml
dev_dependencies:
  flutter_launcher_icons: ^0.13.0

flutter_icons:
  android: true
  ios: true
  image_path: "assets/icon.png"
```

```bash
flutter pub run flutter_launcher_icons
```

Set your version number in `pubspec.yaml`:

```yaml
version: 1.0.0+1
```

The number before `+` is the user-visible version. The number after is the internal build number — increment this with every upload to the Play Store.

**Signing**

Google Play requires a signed AAB. Generate a keystore:

```bash
keytool -genkey -v -keystore ~/my-release-key.jks \
  -keyalg RSA -keysize 2048 -validity 10000 -alias my-key
```

Create a `key.properties` file in the `android/` directory (and add it to `.gitignore` — never commit this):

```
storePassword=yourpassword
keyPassword=yourpassword
keyAlias=my-key
storeFile=../my-release-key.jks
```

Reference it in `android/app/build.gradle`:

```groovy
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(file("key.properties")))

android {
    signingConfigs {
        release {
            storeFile file(keystoreProperties['storeFile'])
            storePassword keystoreProperties['storePassword']
            keyAlias keystoreProperties['keyAlias']
            keyPassword keystoreProperties['keyPassword']
        }
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release
        }
    }
}
```

**Build the AAB:**

```bash
flutter build appbundle
```

The output is at `build/app/outputs/bundle/release/app-release.aab`. This is what you upload — not an APK.

---

### Google Play Console

Create a developer account at play.google.com/console. The one-time registration fee is $25.

Create a new app and fill in the basics — title, default language, free or paid, app or game.

**Store listing** — you'll need:
- App title (50 characters max)
- Short description (80 characters max)
- Full description (up to 4000 characters)
- App icon at 512x512 pixels
- Feature graphic at 1024x500 pixels
- At least 2 screenshots per device type
- A privacy policy URL

**Data safety** — declare what data your app collects and why. Be accurate. Google cross-checks this against your app's behavior.

**Content rating** — complete the questionnaire. It takes about 5 minutes.

**App access** — if your app requires login to use, provide test credentials for reviewers. Apps without this often get rejected during review.

**Upload and release:**

Go to Production > Create new release, upload your `.aab` file, add release notes, and submit for review.

Google typically reviews new apps in 1–7 days. Updates are usually faster, sometimes a few hours.

---

### Common Rejection Reasons (Android)

- App requires login but no test account was provided
- Trademarked terms in the app name
- Data safety form doesn't match actual app behavior
- Broken functionality or crashes in the review build
- Missing privacy policy for apps that handle personal data

---

## Part 2: Apple App Store

### Apple Developer Account

Register at developer.apple.com. The annual fee is $99 USD. Two-factor authentication is required.

Sign into Xcode with your Apple ID and accept the developer agreement before attempting any builds.

---

### Prepare Your iOS App

Update the version in `pubspec.yaml` — Xcode will pull from this.

Update your app name in `ios/Runner/Info.plist`:

```xml
<key>CFBundleDisplayName</key>
<string>YourAppName</string>
```

Add permission descriptions for any sensitive APIs your app uses:

```xml
<key>NSCameraUsageDescription</key>
<string>This app uses the camera to scan barcodes.</string>

<key>NSLocationWhenInUseUsageDescription</key>
<string>Your location is used to find nearby results.</string>
```

Apple will reject apps that use sensitive APIs without a usage description string.

For splash screens and icons:

```bash
flutter pub add flutter_native_splash
flutter pub run flutter_native_splash:create
```

---

### Build and Upload via Xcode

Open `ios/Runner.xcworkspace` in Xcode (not the `.xcodeproj` file).

Set your Bundle Identifier, Version, Build number, and Development Team under the Signing & Capabilities tab.

Then go to **Product > Archive**. This compiles a release build and adds it to the Xcode Organizer. From the Organizer, click **Distribute App > App Store Connect** and follow the prompts. Xcode handles the upload.

---

### App Store Connect

At appstoreconnect.apple.com, create a new app with your Bundle ID and fill in:

- App name and subtitle
- Description and promotional text
- Keywords (100 characters, comma-separated)
- Screenshots — iPhone 6.5" and 5.5" are required; iPad if your app supports it
- App icon at 1024x1024 pixels (no rounded corners — the store adds those)
- Privacy policy URL
- Support URL
- App privacy labels (what data you collect and how it's used)

Select the build you uploaded via Xcode, complete all required fields, and submit for review.

Apple typically reviews within 1–3 days. Expect detailed feedback if anything is rejected — Apple's rejection messages are usually specific.

---

### Common Rejection Reasons (iOS)

- Placeholder content or lorem ipsum in screenshots or description
- Broken functionality on smaller screen sizes (especially iPhone SE)
- Missing or inaccurate privacy labels
- Requesting permissions not actually used by the app
- UI that doesn't follow platform conventions

---

## Publishing Updates

**Android:** Increment both version name and build number, rebuild the AAB, create a new release in Play Console, upload, and submit.

**iOS:** Increment the build number in Xcode (the version visible to users can stay the same for minor updates), archive, upload via Xcode, and select the new build in App Store Connect.

---

## Automation

Once you've done the manual process a few times, automation becomes worth the setup cost.

**fastlane** handles both platforms — it can build, sign, and upload to both stores from the command line. It has a Flutter-compatible setup.

**Codemagic** is a CI/CD platform built specifically for Flutter. It handles signing, building, and publishing, triggered by pushes to your repository.

**GitHub Actions** can run `flutter build appbundle` and upload to the Play Store using the `r0adkll/upload-google-play` action.

---

## Before You Submit — A Quick Checklist

- `debugShowCheckedModeBanner` is set to `false`
- App icon and splash screen are production-quality
- All permission usage descriptions are accurate and non-generic
- The app has been tested on real devices, not just simulators
- Your signing credentials are backed up securely somewhere other than your development machine
- Privacy policy is live at a publicly accessible URL
- Release notes are written for humans, not just "bug fixes"

---

## Summary

Android publishing is faster and cheaper to start ($25 one-time). Apple's process is more strict and more expensive ($99/year), but the review feedback is usually actionable. Both stores reward apps that are complete, well-documented, and honest about what they do. Get the manual process right first, then automate it.

