# Flutter Play Store Deployment via CLI - Complete Guide

## Prerequisites

- Flutter SDK installed
- Google Play Developer account ($25 one-time fee)
- Ruby installed (for Fastlane)
- Your Flutter app ready

---

## Step 1: Prepare Your Flutter App

### 1.1 Update App Version

Edit `pubspec.yaml`:

```yaml
version: 1.0.0+1  # version_name+version_code
```

- First number (1.0.0) = version name (user-visible)
- Second number (+1) = version code (must increment each release)

### 1.2 Update App Information

Edit `android/app/src/main/AndroidManifest.xml`:

```xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.yourcompany.yourapp">
    
    <application
        android:label="Your App Name"
        android:icon="@mipmap/ic_launcher">
        ...
    </application>
</manifest>
```

---

## Step 2: Create Signing Key

### 2.1 Generate Keystore

```bash
keytool -genkey -v -keystore ~/upload-keystore.jks \
  -keyalg RSA -keysize 2048 -validity 10000 \
  -alias upload
```

You'll be asked:
- Password (remember this!)
- Name, organization, etc.

**Important:** Save this keystore file securely! You'll need it for all future updates.

### 2.2 Create key.properties File

Create `android/key.properties`:

```properties
storePassword=your_keystore_password
keyPassword=your_key_password
keyAlias=upload
storeFile=/Users/yourusername/upload-keystore.jks
```

**CI note (Bitbucket Pipelines):**
Use a relative path and place the keystore at `android/upload-keystore.jks`:
```properties
storeFile=upload-keystore.jks
```

**Add to .gitignore:**
```bash
echo "android/key.properties" >> .gitignore
```

### 2.3 Configure Gradle for Signing

Edit `android/app/build.gradle`:

```gradle
// Add before 'android {' block
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
    keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}

android {
    ...
    
    // Add this inside android block
    signingConfigs {
        release {
            keyAlias keystoreProperties['keyAlias']
            keyPassword keystoreProperties['keyPassword']
            storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
            storePassword keystoreProperties['storePassword']
        }
    }
    
    buildTypes {
        release {
            signingConfig signingConfigs.release
            // Optional: enable minification
            minifyEnabled true
            shrinkResources true
        }
    }
}
```

---

## Step 3: Build App Bundle

### 3.1 Build Release AAB

```bash
# Clean build
flutter clean

# Build app bundle
flutter build appbundle --release
```

Your AAB will be at: `build/app/outputs/bundle/release/app-release.aab`

### 3.2 Test the Build (Optional)

```bash
# Build APK for testing
flutter build apk --release

# Install on connected device
flutter install
```

---

## Step 4: Set Up Google Play Console

### 4.1 Create App in Play Console

1. Go to https://play.google.com/console
2. Click "Create app"
3. Fill in app details
4. Complete all setup tasks

### 4.2 Create Service Account for API Access

1. Go to Google Cloud Console: https://console.cloud.google.com/
2. Create new project or select existing
3. Enable "Google Play Android Developer API"
4. Create Service Account:
   - Go to IAM & Admin → Service Accounts
   - Click "Create Service Account"
   - Name: `playstore-deployer`
   - Create and download JSON key

5. Grant Play Console Access:
   - Go to Play Console → Setup → API access
   - Link Google Cloud project
   - Grant access to service account (Admin or Release Manager)

**Save the JSON key as:** `android/playstore-credentials.json`

**Add to .gitignore:**
```bash
echo "android/playstore-credentials.json" >> .gitignore
```

---

## Step 5: Install and Configure Fastlane

### 5.1 Install Fastlane

```bash
# Install via RubyGems
sudo gem install fastlane -NV

# Or via Homebrew (Mac)
brew install fastlane
```

### 5.2 Initialize Fastlane

```bash
cd android
fastlane init
```

When prompted:
- Select option 3: "Manual setup"
- Package name: `com.yourcompany.yourapp`

### 5.3 Configure Fastlane

Edit `android/fastlane/Appfile`:

```ruby
json_key_file("playstore-credentials.json")
package_name("com.yourcompany.yourapp")
```

Edit `android/fastlane/Fastfile`:

```ruby
default_platform(:android)

platform :android do
  
  desc "Deploy a new version to the Google Play Store"
  lane :deploy do
    # Build the app bundle
    sh("cd ../.. && flutter clean && flutter build appbundle --release")
    
    # Upload to Play Store
    upload_to_play_store(
      track: 'internal',  # or 'alpha', 'beta', 'production'
      aab: '../build/app/outputs/bundle/release/app-release.aab',
      skip_upload_metadata: true,
      skip_upload_images: true,
      skip_upload_screenshots: true
    )
  end
  
  desc "Deploy to internal testing"
  lane :internal do
    sh("cd ../.. && flutter clean && flutter build appbundle --release")
    upload_to_play_store(
      track: 'internal',
      aab: '../build/app/outputs/bundle/release/app-release.aab'
    )
  end
  
  desc "Promote internal to beta"
  lane :promote_to_beta do
    upload_to_play_store(
      track: 'internal',
      track_promote_to: 'beta',
      skip_upload_aab: true
    )
  end
  
  desc "Promote beta to production"
  lane :promote_to_production do
    upload_to_play_store(
      track: 'beta',
      track_promote_to: 'production',
      skip_upload_aab: true
    )
  end
  
end
```

---

## Step 6: Deploy via CLI

### 6.1 First Deployment (Manual)

For the **first release**, you must upload manually via Play Console:

1. Go to Play Console → Your App → Production/Testing
2. Upload the AAB from `build/app/outputs/bundle/release/app-release.aab`
3. Fill in release notes, screenshots, etc.
4. Submit for review

### 6.2 Subsequent Deployments (Fastlane)

```bash
# Navigate to android directory
cd android

# Deploy to internal testing
fastlane internal

# Deploy to alpha track
fastlane deploy --env alpha

# Deploy to beta track
fastlane deploy --env beta

# Deploy to production
fastlane deploy --env production
```

### 6.3 Promotion Between Tracks

```bash
# Promote from internal to beta
fastlane promote_to_beta

# Promote from beta to production
fastlane promote_to_production
```

---

## Step 7: Advanced Fastlane Configuration

### 7.1 Environment Variables (Optional)

Create `android/fastlane/.env`:

```bash
PACKAGE_NAME=com.yourcompany.yourapp
JSON_KEY_FILE=playstore-credentials.json
```

### 7.2 Upload Metadata Automatically

```bash
# Initialize metadata
cd android
fastlane supply init
```

This downloads current Play Store listing to `android/fastlane/metadata/android/`

Edit metadata files:
- `en-US/full_description.txt` - Full description
- `en-US/short_description.txt` - Short description
- `en-US/title.txt` - App title

Update Fastfile to include metadata:

```ruby
lane :deploy do
  sh("cd ../.. && flutter clean && flutter build appbundle --release")
  
  upload_to_play_store(
    track: 'production',
    aab: '../build/app/outputs/bundle/release/app-release.aab',
    skip_upload_metadata: false,  # Upload metadata
    skip_upload_images: false,     # Upload images
    skip_upload_screenshots: false # Upload screenshots
  )
end
```

---

## Step 8: CI/CD Integration (Optional)

This repository uses Bitbucket Pipelines for Android-only internal deployments. See `bitbucket_pipelines_flutter_cicd.md` for the live configuration.

### 8.1 GitHub Actions Example

Create `.github/workflows/deploy.yml`:

```yaml
name: Deploy to Play Store

on:
  push:
    tags:
      - 'v*'

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - uses: actions/setup-java@v3
        with:
          distribution: 'zulu'
          java-version: '11'
      
      - uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.16.0'
      
      - name: Decode keystore
        run: echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > android/app/upload-keystore.jks
      
      - name: Create key.properties
        run: |
          echo "storePassword=${{ secrets.STORE_PASSWORD }}" > android/key.properties
          echo "keyPassword=${{ secrets.KEY_PASSWORD }}" >> android/key.properties
          echo "keyAlias=upload" >> android/key.properties
          echo "storeFile=upload-keystore.jks" >> android/key.properties
      
      - name: Create service account JSON
        run: echo "${{ secrets.PLAYSTORE_SERVICE_ACCOUNT }}" > android/playstore-credentials.json
      
      - name: Install dependencies
        run: flutter pub get
      
      - name: Deploy to Play Store
        run: |
          cd android
          bundle install
          fastlane deploy
```

---

## Troubleshooting

### Common Issues

**1. "App not signed" error:**
```bash
# Verify signing configuration
./gradlew signingReport
```

**2. "Version code must be greater" error:**
- Increment version code in `pubspec.yaml` (the number after +)

**3. Fastlane upload fails:**
```bash
# Check API access
fastlane run validate_play_store_json_key json_key: playstore-credentials.json
```

**4. Build fails:**
```bash
# Clear cache
flutter clean
rm -rf build/
flutter pub get
flutter build appbundle --release
```

**5. "Tag number over 30 is not supported" or "Failed to read key from store":**
- The pipeline's Java can't read keystores created with a newer keytool. Fix by re-exporting the keystore using **Java 8 or 11** (or the same Java as the CI image), then re-encode for Bitbucket:
```bash
# Option A: Re-import into a new JKS using Java 8/11 keytool (keeps same key)
keytool -importkeystore -srckeystore your-current.jks -destkeystore upload-keystore.jks -deststoretype JKS

# Option B: If you can, create a new keystore with Java 8/11 keytool and migrate your app signing key

# Encode for Bitbucket (single line, no newlines)
cat upload-keystore.jks | base64 | tr -d '\n' | pbcopy
```
- Update `ANDROID_KEYSTORE_BASE64` in Bitbucket with the new value (Secured). The pipeline now strips newlines when decoding.

---

## Quick Reference Commands

```bash
# Build release AAB
flutter build appbundle --release

# Deploy to internal testing
cd android && fastlane internal

# Deploy to production
cd android && fastlane deploy

# Check Fastlane version
fastlane --version

# Update Fastlane
sudo gem update fastlane
```

---

## Security Checklist

- [ ] Keystore file backed up securely
- [ ] `key.properties` in .gitignore
- [ ] Service account JSON in .gitignore
- [ ] Keystore password stored securely
- [ ] Service account has minimal permissions
- [ ] Enable Play App Signing in Play Console

---

## Additional Resources

- Flutter Deployment Docs: https://docs.flutter.dev/deployment/android
- Fastlane Docs: https://docs.fastlane.tools/
- Play Console: https://play.google.com/console
- Play Developer API: https://developers.google.com/android-publisher

---

## Notes

- Always test on internal track before promoting to production
- Keep your keystore safe - you can't update your app without it
- Version codes must always increase
- First release must be done manually via Play Console
- Fastlane can handle subsequent releases automatically
