# Bitbucket Pipelines - Flutter iOS & Android Deployment

Complete CI/CD setup for deploying Flutter apps to both App Store and Play Store using Bitbucket Pipelines.

---

## Table of Contents

1. [Prerequisites](#prerequisites)
2. [Repository Setup](#repository-setup)
3. [Bitbucket Variables Configuration](#bitbucket-variables-configuration)
4. [Pipeline Configuration](#pipeline-configuration)
5. [Deployment Workflows](#deployment-workflows)
6. [Troubleshooting](#troubleshooting)

---

## Prerequisites

### Required Accounts
- ✅ Apple Developer Account ($99/year)
- ✅ Google Play Developer Account ($25 one-time)
- ✅ Bitbucket account with Pipelines enabled

### Required Files (Already Set Up)
- ✅ Android keystore file
- ✅ iOS certificates and provisioning profiles (via Match or manual)
- ✅ Fastlane configured for both platforms
- ✅ Service accounts and API keys

---

## Repository Setup

### 1. Enable Bitbucket Pipelines

1. Go to your repository in Bitbucket
2. Settings → Pipelines → Settings
3. Enable Pipelines

### 2. Project Structure

```
your-flutter-app/
├── bitbucket-pipelines.yml        # Main pipeline config
├── android/
│   ├── fastlane/
│   │   ├── Fastfile
│   │   └── Appfile
│   ├── key.properties             # DON'T COMMIT (use variables)
│   └── app/
├── ios/
│   ├── fastlane/
│   │   ├── Fastfile
│   │   ├── Appfile
│   │   └── Matchfile
│   └── Runner.xcworkspace
└── pubspec.yaml
```

---

## Bitbucket Variables Configuration

Go to: **Repository Settings → Pipelines → Repository variables**

### Android Variables

| Variable Name | Value | Secured? |
|--------------|-------|----------|
| `ANDROID_KEYSTORE_BASE64` | Base64 encoded keystore file | ✅ Yes |
| `ANDROID_STORE_PASSWORD` | Keystore password | ✅ Yes |
| `ANDROID_KEY_PASSWORD` | Key password | ✅ Yes |
| `ANDROID_KEY_ALIAS` | Key alias (e.g., "upload") | ❌ No |
| `ANDROID_PLAYSTORE_JSON_BASE64` | Base64 encoded service account JSON | ✅ Yes |

### iOS Variables

| Variable Name | Value | Secured? |
|--------------|-------|----------|
| `IOS_APP_STORE_CONNECT_API_KEY_ID` | API Key ID | ❌ No |
| `IOS_APP_STORE_CONNECT_ISSUER_ID` | Issuer ID | ❌ No |
| `IOS_APP_STORE_CONNECT_API_KEY_BASE64` | Base64 encoded .p8 file | ✅ Yes |
| `MATCH_PASSWORD` | Match repository password | ✅ Yes |
| `MATCH_GIT_URL` | Match repository URL | ❌ No |
| `MATCH_GIT_BASIC_AUTH` | Base64(username:token) | ✅ Yes |

### General Variables

| Variable Name | Value | Secured? |
|--------------|-------|----------|
| `FLUTTER_VERSION` | 3.16.0 (or your version) | ❌ No |

---

## Encode Files to Base64

### Android Keystore

```bash
# Encode keystore
cat android/upload-keystore.jks | base64 | pbcopy

# Or save to file
cat android/upload-keystore.jks | base64 > keystore.base64.txt
```

### Android Service Account JSON

```bash
# Encode service account JSON
cat android/playstore-credentials.json | base64 | pbcopy
```

### iOS API Key

```bash
# Encode .p8 file
cat AuthKey_ABC123DEF4.p8 | base64 | pbcopy
```

### iOS Match Git Authentication

```bash
# Create base64 encoded authentication
echo -n "your-git-username:your-personal-access-token" | base64
```

---

## Pipeline Configuration

### Basic Pipeline - `bitbucket-pipelines.yml`

```yaml
image: ghcr.io/cirruslabs/flutter:stable

definitions:
  caches:
    flutter: ~/.pub-cache
    gradle: ~/.gradle
    cocoapods: ~/.cocoapods

  steps:
    - step: &build-android
        name: Build Android
        caches:
          - flutter
          - gradle
        script:
          - flutter pub get
          - flutter build appbundle --release
        artifacts:
          - build/app/outputs/bundle/release/app-release.aab

    - step: &build-ios
        name: Build iOS
        caches:
          - flutter
          - cocoapods
        script:
          - flutter pub get
          - flutter build ios --release --no-codesign
        artifacts:
          - build/ios/iphoneos/Runner.app

    - step: &deploy-android
        name: Deploy to Play Store
        image: ruby:3.0
        caches:
          - gradle
        script:
          # Install Fastlane
          - gem install fastlane -NV
          
          # Decode and setup keystore
          - echo $ANDROID_KEYSTORE_BASE64 | base64 -d > android/app/upload-keystore.jks
          
          # Create key.properties
          - |
            cat > android/key.properties << EOF
            storePassword=$ANDROID_STORE_PASSWORD
            keyPassword=$ANDROID_KEY_PASSWORD
            keyAlias=$ANDROID_KEY_ALIAS
            storeFile=upload-keystore.jks
            EOF
          
          # Decode service account JSON
          - echo $ANDROID_PLAYSTORE_JSON_BASE64 | base64 -d > android/playstore-credentials.json
          
          # Deploy
          - cd android
          - fastlane deploy

    - step: &deploy-ios
        name: Deploy to App Store
        image: ruby:3.0
        caches:
          - cocoapods
        script:
          # Install dependencies
          - gem install fastlane -NV
          - gem install cocoapods -NV
          
          # Setup API Key
          - mkdir -p ~/.private_keys
          - echo $IOS_APP_STORE_CONNECT_API_KEY_BASE64 | base64 -d > ~/.private_keys/AuthKey.p8
          - chmod 600 ~/.private_keys/AuthKey.p8
          
          # Deploy
          - cd ios
          - pod install
          - bundle install
          - fastlane beta
        artifacts:
          - ios/*.ipa

pipelines:
  default:
    - parallel:
      - step:
          name: Run Tests
          caches:
            - flutter
          script:
            - flutter pub get
            - flutter test
  
  branches:
    develop:
      - step: *build-android
      - step: *build-ios
  
  tags:
    'v*':
      - parallel:
        - step: *build-android
        - step: *build-ios
      - parallel:
        - step: *deploy-android
        - step: *deploy-ios

  custom:
    deploy-android-only:
      - step: *build-android
      - step: *deploy-android
    
    deploy-ios-only:
      - step: *build-ios
      - step: *deploy-ios
    
    deploy-both:
      - parallel:
        - step: *build-android
        - step: *build-ios
      - parallel:
        - step: *deploy-android
        - step: *deploy-ios
```

---

## Advanced Pipeline with Staging/Production

### Multi-Environment Pipeline

```yaml
image: ghcr.io/cirruslabs/flutter:stable

definitions:
  caches:
    flutter: ~/.pub-cache
    gradle: ~/.gradle
    cocoapods: ~/.cocoapods

  steps:
    # Android Steps
    - step: &build-android
        name: Build Android Bundle
        caches:
          - flutter
          - gradle
        script:
          - echo "Building Android for $BITBUCKET_BRANCH"
          - flutter pub get
          - flutter build appbundle --release
        artifacts:
          - build/app/outputs/bundle/release/app-release.aab

    - step: &deploy-android-internal
        name: Deploy to Play Store (Internal)
        image: ruby:3.0
        deployment: android-internal
        script:
          - gem install fastlane -NV
          - echo $ANDROID_KEYSTORE_BASE64 | base64 -d > android/app/upload-keystore.jks
          - |
            cat > android/key.properties << EOF
            storePassword=$ANDROID_STORE_PASSWORD
            keyPassword=$ANDROID_KEY_PASSWORD
            keyAlias=$ANDROID_KEY_ALIAS
            storeFile=upload-keystore.jks
            EOF
          - echo $ANDROID_PLAYSTORE_JSON_BASE64 | base64 -d > android/playstore-credentials.json
          - cd android
          - fastlane internal

    - step: &deploy-android-beta
        name: Deploy to Play Store (Beta)
        image: ruby:3.0
        deployment: android-beta
        trigger: manual
        script:
          - gem install fastlane -NV
          - echo $ANDROID_KEYSTORE_BASE64 | base64 -d > android/app/upload-keystore.jks
          - |
            cat > android/key.properties << EOF
            storePassword=$ANDROID_STORE_PASSWORD
            keyPassword=$ANDROID_KEY_PASSWORD
            keyAlias=$ANDROID_KEY_ALIAS
            storeFile=upload-keystore.jks
            EOF
          - echo $ANDROID_PLAYSTORE_JSON_BASE64 | base64 -d > android/playstore-credentials.json
          - cd android
          - fastlane promote_to_beta

    - step: &deploy-android-production
        name: Deploy to Play Store (Production)
        image: ruby:3.0
        deployment: android-production
        trigger: manual
        script:
          - gem install fastlane -NV
          - echo $ANDROID_KEYSTORE_BASE64 | base64 -d > android/app/upload-keystore.jks
          - |
            cat > android/key.properties << EOF
            storePassword=$ANDROID_STORE_PASSWORD
            keyPassword=$ANDROID_KEY_PASSWORD
            keyAlias=$ANDROID_KEY_ALIAS
            storeFile=upload-keystore.jks
            EOF
          - echo $ANDROID_PLAYSTORE_JSON_BASE64 | base64 -d > android/playstore-credentials.json
          - cd android
          - fastlane promote_to_production

    # iOS Steps
    - step: &build-ios
        name: Build iOS
        caches:
          - flutter
          - cocoapods
        script:
          - echo "Building iOS for $BITBUCKET_BRANCH"
          - flutter pub get
          - flutter build ios --release --no-codesign
        artifacts:
          - build/ios/iphoneos/Runner.app

    - step: &deploy-ios-testflight
        name: Deploy to TestFlight
        image: ruby:3.0
        deployment: ios-testflight
        script:
          - gem install fastlane -NV
          - gem install cocoapods -NV
          - mkdir -p ~/.private_keys
          - echo $IOS_APP_STORE_CONNECT_API_KEY_BASE64 | base64 -d > ~/.private_keys/AuthKey.p8
          - chmod 600 ~/.private_keys/AuthKey.p8
          - cd ios
          - pod install
          - bundle install
          - fastlane beta
        artifacts:
          - ios/*.ipa

    - step: &deploy-ios-appstore
        name: Deploy to App Store
        image: ruby:3.0
        deployment: ios-production
        trigger: manual
        script:
          - gem install fastlane -NV
          - gem install cocoapods -NV
          - mkdir -p ~/.private_keys
          - echo $IOS_APP_STORE_CONNECT_API_KEY_BASE64 | base64 -d > ~/.private_keys/AuthKey.p8
          - chmod 600 ~/.private_keys/AuthKey.p8
          - cd ios
          - pod install
          - bundle install
          - fastlane release
          - fastlane submit

pipelines:
  # Default pipeline for feature branches
  default:
    - parallel:
      - step:
          name: Analyze & Test
          caches:
            - flutter
          script:
            - flutter pub get
            - flutter analyze
            - flutter test

  # Development branch - deploy to internal testing
  branches:
    develop:
      - parallel:
        - step: *build-android
        - step: *build-ios
      - parallel:
        - step: *deploy-android-internal
        - step: *deploy-ios-testflight

    # Staging branch - deploy to beta
    staging:
      - parallel:
        - step: *build-android
        - step: *build-ios
      - parallel:
        - step: *deploy-android-beta
        - step: *deploy-ios-testflight

    # Main/Master branch - option to deploy to production
    main:
      - parallel:
        - step: *build-android
        - step: *build-ios
      - parallel:
        - step: *deploy-android-production
        - step: *deploy-ios-appstore

  # Tag-based deployments
  tags:
    # Beta releases (v1.0.0-beta.1)
    'v*-beta*':
      - parallel:
        - step: *build-android
        - step: *build-ios
      - parallel:
        - step: *deploy-android-beta
        - step: *deploy-ios-testflight

    # Production releases (v1.0.0)
    'v*':
      - parallel:
        - step: *build-android
        - step: *build-ios
      - parallel:
        - step: *deploy-android-production
        - step: *deploy-ios-appstore

  # Custom pipelines (manual trigger)
  custom:
    # Deploy only Android
    deploy-android-internal:
      - step: *build-android
      - step: *deploy-android-internal

    deploy-android-beta:
      - step: *build-android
      - step: *deploy-android-beta

    deploy-android-production:
      - step: *build-android
      - step: *deploy-android-production

    # Deploy only iOS
    deploy-ios-testflight:
      - step: *build-ios
      - step: *deploy-ios-testflight

    deploy-ios-appstore:
      - step: *build-ios
      - step: *deploy-ios-appstore

    # Deploy both platforms
    deploy-all-staging:
      - parallel:
        - step: *build-android
        - step: *build-ios
      - parallel:
        - step: *deploy-android-internal
        - step: *deploy-ios-testflight

    deploy-all-production:
      - parallel:
        - step: *build-android
        - step: *build-ios
      - parallel:
        - step: *deploy-android-production
        - step: *deploy-ios-appstore

    # Build only (no deployment)
    build-all:
      - parallel:
        - step: *build-android
        - step: *build-ios
```

---

## Pipeline with Notifications

### Slack/Email Notifications

```yaml
image: ghcr.io/cirruslabs/flutter:stable

definitions:
  steps:
    - step: &notify-slack-success
        name: Notify Success
        image: curlimages/curl:latest
        script:
          - |
            curl -X POST -H 'Content-type: application/json' \
            --data '{"text":"✅ Deployment successful for '"$BITBUCKET_BRANCH"'"}' \
            $SLACK_WEBHOOK_URL

    - step: &notify-slack-failure
        name: Notify Failure
        image: curlimages/curl:latest
        script:
          - |
            curl -X POST -H 'Content-type: application/json' \
            --data '{"text":"❌ Deployment failed for '"$BITBUCKET_BRANCH"'"}' \
            $SLACK_WEBHOOK_URL

pipelines:
  tags:
    'v*':
      - parallel:
        - step: *build-android
        - step: *build-ios
      - parallel:
        - step: *deploy-android
        - step: *deploy-ios
      - step: *notify-slack-success

  # Add to repository variables:
  # SLACK_WEBHOOK_URL = https://hooks.slack.com/services/YOUR/WEBHOOK/URL
```

---

## Fastlane Configuration Updates

### Android Fastfile with API Key

Update `android/fastlane/Fastfile`:

```ruby
default_platform(:android)

platform :android do
  
  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',
      skip_upload_metadata: true,
      skip_upload_images: true,
      skip_upload_screenshots: true
    )
  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
  
  desc "Deploy directly to production"
  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: true,
      skip_upload_images: true,
      skip_upload_screenshots: true
    )
  end
  
end
```

### iOS Fastfile with API Key

Update `ios/fastlane/Fastfile`:

```ruby
default_platform(:ios)

platform :ios do
  
  before_all do
    # Setup API Key from environment or file
    if ENV['IOS_APP_STORE_CONNECT_API_KEY_ID']
      app_store_connect_api_key(
        key_id: ENV['IOS_APP_STORE_CONNECT_API_KEY_ID'],
        issuer_id: ENV['IOS_APP_STORE_CONNECT_ISSUER_ID'],
        key_filepath: "#{Dir.home}/.private_keys/AuthKey.p8",
        duration: 1200,
        in_house: false
      )
    end
  end
  
  desc "Deploy to TestFlight"
  lane :beta do
    # Setup Match if using it
    if ENV['MATCH_GIT_URL']
      setup_match
    end
    
    increment_build_number(xcodeproj: "Runner.xcodeproj")
    
    build_app(
      workspace: "Runner.xcworkspace",
      scheme: "Runner",
      export_method: "app-store",
      output_directory: ".",
      output_name: "Runner.ipa"
    )
    
    upload_to_testflight(
      skip_waiting_for_build_processing: true,
      skip_submission: true,
      distribute_external: false
    )
  end
  
  desc "Deploy to App Store"
  lane :release do
    if ENV['MATCH_GIT_URL']
      setup_match
    end
    
    increment_build_number(xcodeproj: "Runner.xcodeproj")
    
    build_app(
      workspace: "Runner.xcworkspace",
      scheme: "Runner",
      export_method: "app-store"
    )
    
    upload_to_app_store(
      force: true,
      skip_metadata: true,
      skip_screenshots: true,
      submit_for_review: false
    )
  end
  
  desc "Submit for App Store review"
  lane :submit do
    upload_to_app_store(
      submit_for_review: true,
      automatic_release: false,
      force: true,
      skip_binary_upload: true,
      skip_metadata: true,
      skip_screenshots: true
    )
  end
  
  # Helper to setup Match
  private_lane :setup_match do
    match(
      type: "appstore",
      readonly: true,
      git_url: ENV['MATCH_GIT_URL'],
      git_basic_authorization: ENV['MATCH_GIT_BASIC_AUTH']
    )
  end
  
end
```

---

## Deployment Workflows

### Workflow 1: Feature Development

```bash
# Developer pushes to feature branch
git checkout -b feature/new-feature
git push origin feature/new-feature

# Bitbucket runs: tests only
# ✅ No deployment
```

### Workflow 2: Development Testing

```bash
# Merge to develop branch
git checkout develop
git merge feature/new-feature
git push origin develop

# Bitbucket runs:
# 1. Build Android & iOS
# 2. Deploy to Play Store (Internal)
# 3. Deploy to TestFlight
```

### Workflow 3: Staging/Beta Release

```bash
# Create staging branch or tag
git checkout -b staging
git push origin staging

# OR tag for beta
git tag v1.0.0-beta.1
git push origin v1.0.0-beta.1

# Bitbucket runs:
# 1. Build Android & iOS
# 2. Deploy to Play Store (Beta) - Manual approval
# 3. Deploy to TestFlight
```

### Workflow 4: Production Release

```bash
# Tag for production
git checkout main
git tag v1.0.0
git push origin v1.0.0

# Bitbucket runs:
# 1. Build Android & iOS
# 2. Deploy to Play Store (Production) - Manual approval
# 3. Deploy to App Store - Manual approval
```

### Workflow 5: Manual Pipeline

1. Go to Bitbucket → Pipelines
2. Click "Run pipeline"
3. Select custom pipeline:
   - `deploy-android-internal`
   - `deploy-ios-testflight`
   - `deploy-all-production`
   - etc.
4. Click "Run"

---

## Environment Setup in Bitbucket

### Create Deployment Environments

1. Go to Repository Settings → Deployments
2. Create environments:
   - `android-internal`
   - `android-beta`
   - `android-production`
   - `ios-testflight`
   - `ios-production`

3. For each production environment:
   - Enable "Lock for all"
   - Add deployment approvers
   - Set restrictions

---

## Monitoring and Logs

### View Pipeline Logs

1. Go to Pipelines in your repository
2. Click on a pipeline run
3. Expand each step to see logs
4. Download artifacts (APK/IPA files)

### Common Log Sections

```
✅ Flutter build successful
✅ Fastlane upload successful
✅ Build available in Play Console/TestFlight
❌ Fastlane authentication failed
❌ Code signing error
```

---

## Troubleshooting

### Issue 1: Android Build Fails

**Error:** "Keystore not found"

**Solution:**
```bash
# Verify base64 encoding
cat android/upload-keystore.jks | base64

# Ensure no line breaks in Bitbucket variable
# Use "Secured" checkbox for ANDROID_KEYSTORE_BASE64
```

### Issue 2: iOS Build Fails

**Error:** "No provisioning profile found"

**Solution:**
```yaml
# Add Match setup to pipeline
- git clone $MATCH_GIT_URL ~/match_repo
- cd ios
- fastlane match appstore --readonly
```

### Issue 3: Fastlane Upload Fails

**Error:** "Authentication failed"

**Solution:**
```bash
# For Android - verify service account has access
# Play Console → Setup → API access

# For iOS - verify API key is valid
# App Store Connect → Users and Access → Keys
```

### Issue 4: Pipeline Times Out

**Solution:**
```yaml
# Increase timeout in step
- step:
    name: Deploy iOS
    max-time: 60  # minutes
    script:
      - cd ios
      - fastlane beta
```

### Issue 5: Parallel Builds Fail

**Solution:**
```yaml
# Don't run parallel if resources limited
# Run sequentially instead
- step: *build-android
- step: *deploy-android
- step: *build-ios
- step: *deploy-ios
```

---

## Best Practices

### 1. Version Management

Update version before creating tag:

```yaml
# In pubspec.yaml
version: 1.0.0+1  # Increment before tag

# Or automate in pipeline
- flutter pub get
- flutter pub run version_increment
```

### 2. Secure Secrets

- ✅ Use Bitbucket secured variables
- ✅ Never commit credentials
- ✅ Rotate keys periodically
- ❌ Don't log secret values

### 3. Testing Strategy

```yaml
- step:
    name: Run Tests
    script:
      - flutter pub get
      - flutter analyze
      - flutter test
      - flutter test --coverage
```

### 4. Artifact Management

```yaml
- step:
    name: Build Android
    artifacts:
      - build/app/outputs/**/*.aab
      - build/app/outputs/**/*.apk
```

### 5. Caching

```yaml
definitions:
  caches:
    flutter: ~/.pub-cache
    gradle: ~/.gradle/caches
    cocoapods: ~/.cocoapods

- step:
    caches:
      - flutter
      - gradle
```

---

## Pipeline Costs

Bitbucket Pipelines pricing (as of 2024):
- Free tier: 50 build minutes/month
- Standard: $10/month for 2500 minutes
- Premium: $25/month for unlimited

**Tips to reduce build time:**
- Use caching effectively
- Run parallel builds
- Skip unnecessary steps
- Use smaller Docker images

---

## Quick Commands Reference

```bash
# Test pipeline locally (requires Docker)
docker pull ghcr.io/cirruslabs/flutter:stable
docker run -it -v $(pwd):/app ghcr.io/cirruslabs/flutter:stable bash

# Validate pipeline file
# Use Bitbucket's online validator in repository settings

# Encode files
cat file.jks | base64 | pbcopy
cat file.json | base64 | pbcopy
cat file.p8 | base64 | pbcopy

# Create Git tag
git tag v1.0.0
git push origin v1.0.0

# Delete remote tag
git push origin --delete v1.0.0
```

---

## Complete Workflow Example

### 1. Setup (One Time)

```bash
# 1. Configure Fastlane
cd android && fastlane init
cd ../ios && fastlane init

# 2. Encode secrets
cat android/upload-keystore.jks | base64 > keystore.txt
cat android/playstore-credentials.json | base64 > playstore.txt
cat ios/AuthKey.p8 | base64 > authkey.txt

# 3. Add to Bitbucket variables
# (copy from .txt files)

# 4. Commit pipeline config
git add bitbucket-pipelines.yml
git commit -m "Add CI/CD pipeline"
git push origin main
```

### 2. Development Cycle

```bash
# Feature development
git checkout -b feature/awesome-feature
# ... make changes ...
git push origin feature/awesome-feature
# Pipeline runs tests only

# Merge to develop
git checkout develop
git merge feature/awesome-feature
git push origin develop
# Pipeline deploys to internal/TestFlight

# Create release
git checkout main
git merge develop
git tag v1.0.0
git push origin main --tags
# Pipeline deploys to production (manual approval)
```

---

## Summary

You now have a complete CI/CD pipeline that:

✅ Builds Flutter apps for both iOS and Android
✅ Runs tests automatically
✅ Deploys to internal testing (develop branch)
✅ Deploys to beta testing (staging branch)
✅ Deploys to production (tags with manual approval)
✅ Supports custom manual deployments
✅ Runs builds in parallel for speed
✅ Includes notifications
✅ Manages secrets securely

**Next Steps:**
1. Set up Bitbucket variables
2. Commit `bitbucket-pipelines.yml`
3. Push to develop branch to test
4. Create a tag to deploy to production

Good luck with your deployments! 🚀
