Delivery is part of the product

An application is not finished when it compiles on a developer machine. A professional release must be reproducible, identifiable, signed, verified, and monitored.

Versioning and artifacts

In pubspec.yaml:

version: 1.2.0+42

The value before + communicates the version to users; the build number identifies a store delivery. Generate artifacts from the exact revision that passed validation:

flutter build appbundle --release
flutter build ipa --release
flutter build web --release
Artifact flutter build <target> --release

build produces an artifact for the requested target; --release selects optimizations and removes development tooling incompatible with distribution. The command neither publishes nor proves production behavior.

appbundle creates the Android store distribution format; ipa depends on Apple toolchain and signing on macOS; web produces static files for hosting. The pipeline host must support the target.

Record revision, toolchain version, environment, path, size, and artifact hash. The distributed file must be the exact output of the validated revision, not a later manual reconstruction.

Debug, profile, and release answer different questions. Debug favors iteration and asserts; profile enables performance measurement with appropriate instrumentation; release represents optimization and distribution. Diagnose functionality early, measure performance in profile, and smoke-test the release artifact.

Signing requirements differ by platform. Keys and certificates never belong in the repository or logs.

Environments and flavors

Development, staging, and production need their own endpoints, services, and identifiers.

final class AppEnvironment {
  const AppEnvironment({required this.name, required this.apiUrl});
  final String name;
  final Uri apiUrl;
}

Public configuration can enter through --dart-define. Real secrets must stay in the backend or protected pipeline storage. A secret included in a client binary should be considered public.

A minimum pipeline

name: flutter-ci
on:
  pull_request:
  push:
    branches: [main]

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: subosito/flutter-action@v2
        with:
          channel: stable
          cache: true
      - run: flutter pub get
      - run: dart format --output=none --set-exit-if-changed .
      - run: flutter analyze
      - run: flutter test --coverage
      - run: flutter build web --release

For production, pin actions to trusted references, protect environments, and require approval before distribution.

Read the pipeline as a chain of evidence: pub get reconstructs dependencies; format and analyze validate static consistency; test verifies covered behavior; build proves the target produces an artifact. Each step should fail fast and preserve enough logs without exposing secrets.

Controlled release

Prepare store copy, icons, screenshots, privacy policy, age rating, and support contact. Use internal and closed testing before public rollout.

Fastlane can automate metadata and submission, but traceability still matters: version, commit, environment, approver, and artifact.

Release checklist

  • The release revision passed CI.
  • Version and release notes are correct.
  • Signing and target environment are confirmed.
  • A real device and upgrade from the previous version were tested.
  • Permissions and privacy disclosures were reviewed.
  • Crash reporting and metrics identify the new version.
  • An owner, incident channel, and containment plan exist.

Mobile applications cannot roll back instantly for every user. Prefer staged rollout, safe feature flags, and backend compatibility with older clients.

Post-deploy

Observe crash-free users, startup failure, API errors, latency, and critical conversions by version. Define thresholds before release. Pause rollout when a threshold is exceeded.

Guided practice: build an auditable delivery

Continue with Sales Management after the Module 9 gate passes. You will produce and preserve a staging artifact; this practice does not publish to a store or production.

Checkpoint 1 — identify the release before building

Set a lab version such as 1.0.0+1 in pubspec.yaml, then record:

flutter --version
flutter doctor -v
git rev-parse HEAD

Write these values to release-evidence.md. Do not proceed with unexplained checkout changes or a failing local gate.

Checkpoint 2 — make environment configuration explicit

Create lib/core/config/app_environment.dart:

enum AppEnvironment { development, staging, production }

final class AppConfig {
  AppConfig._({required this.environment, required this.apiUrl});
  final AppEnvironment environment;
  final Uri apiUrl;

  factory AppConfig.fromDefines() {
    const name = String.fromEnvironment('APP_ENV');
    const rawApiUrl = String.fromEnvironment('API_URL');
    final environment = AppEnvironment.values.firstWhere(
      (value) => value.name == name,
      orElse: () => throw StateError('Invalid APP_ENV: $name'),
    );
    final apiUrl = Uri.tryParse(rawApiUrl);
    if (apiUrl == null || !apiUrl.hasScheme) {
      throw StateError('Invalid API_URL');
    }
    return AppConfig._(environment: environment, apiUrl: apiUrl);
  }
}

Run staging with:

flutter run --dart-define=APP_ENV=staging --dart-define=API_URL=https://staging.example.invalid

Public endpoints and identifiers can enter the binary. Passwords, tokens, and administrative keys cannot; keep them in the backend or in protected pipeline storage used only by the required step.

Checkpoint 3 — build what the host supports

flutter clean
flutter pub get
dart format --output=none --set-exit-if-changed .
flutter analyze
flutter test
flutter build web --release --dart-define=APP_ENV=staging --dart-define=API_URL=https://staging.example.invalid

On Windows or Linux with Android tooling, also run flutter build appbundle --release .... flutter build ipa requires macOS, Xcode, and Apple signing, so keep it as a separate compatible-runner job.

Serve build/web over local HTTP and smoke-test startup, navigation, loading, and API failure. Opening index.html directly does not model real hosting.

Checkpoint 4 — record artifact integrity

In PowerShell:

Compress-Archive -Path build/web/* -DestinationPath build/sales-management-web.zip
Get-FileHash build/sales-management-web.zip -Algorithm SHA256
Get-Item build/sales-management-web.zip | Select-Object Name, Length, LastWriteTimeUtc

Add version, build number, commit, environment, Flutter/Dart version, target, filename, size, and SHA-256 to release-evidence.md. The hash proves that the approved file is the delivered file.

Checkpoint 5 — reproduce the gate in GitHub Actions

Create .github/workflows/flutter-ci.yml:

name: flutter-ci

on:
  pull_request:
  push:
    branches: [main]

permissions:
  contents: read

jobs:
  validate:
    runs-on: ubuntu-latest
    timeout-minutes: 20
    steps:
      - uses: actions/checkout@v4
      - uses: subosito/flutter-action@v2
        with:
          channel: stable
          cache: true
      - run: flutter pub get
      - run: dart format --output=none --set-exit-if-changed .
      - run: flutter analyze
      - run: flutter test --coverage
      - run: >-
          flutter build web --release
          --dart-define=APP_ENV=staging
          --dart-define=API_URL=https://staging.example.invalid
      - uses: actions/upload-artifact@v4
        with:
          name: sales-management-web
          path: build/web
          if-no-files-found: error

For a real project, review and pin actions to trusted references under organizational policy. The pull-request job validates; a future distribution job should use a protected environment, minimum permissions, and human approval.

Checkpoint 6 — rehearse the decision without publishing

Create docs/release-checklist.md with owner, approved revision, smoke tests, metrics, error threshold, and containment action. Simulate two outcomes: within limits means proceed; elevated API failure means pause rollout, preserve evidence, and notify the owner.

For mobile, rollback usually means stopping rollout, fixing, and shipping another version. The backend must therefore remain compatible with older clients.

SymptomLikely causeFix
CI uses another Flutter versionfloating channel was not recordedpin and record the approved version
staging calls productionmissing define or unsafe fallbackfail early on invalid environment
artifact does not match commitlater manual rebuildpromote the already validated artifact
secret appears in the bundleprivate value passed through dart-defineremove it from the client and rotate it

Completion gate and independent practice

Finish when the local gate passes, workflow permissions are minimal, the staging artifact has a SHA-256, and evidence links it to the commit. No real publication is required.

Independently design an Android job on a compatible runner that produces a signed AAB from protected secrets. Document which steps may run on pull requests and which require approval, without adding real credentials.

Terms to review: semantic version, build number, artifact, signing, flavor, environment, secret, protected environment, staged rollout, and rollback strategy. See the course reference.