name: 'Flutter Build Android' description: 'Configures Android keystore signing, builds release AAB/APK artifacts, and optionally deploys to Google Play Console and Shorebird.' inputs: working_directory: description: 'Path to the Flutter app package directory (e.g., packages/hfw_flutter or .)' required: false default: '.' build_aab: description: 'Whether to build an Android App Bundle (.aab)' required: false default: 'true' build_apk: description: 'Whether to build a release APK (.apk)' required: false default: 'false' build_args: description: 'Extra arguments passed to flutter build appbundle / apk (e.g., --dart-define=...)' required: false default: '' artifact_prefix: description: 'Optional prefix for copied versioned artifacts in output_dir (e.g., hfw-android -> hfw-android-v1.0.0.apk)' required: false default: '' output_dir: description: 'Directory to copy versioned artifacts into when artifact_prefix is set' required: false default: 'artifacts' keystore_base64: description: 'Base64-encoded Android release keystore file' required: false default: '' key_alias: description: 'Android signing key alias' required: false default: '' key_password: description: 'Android signing key password' required: false default: '' store_password: description: 'Android keystore password' required: false default: '' google_play_service_account_json: description: 'Google Play service account JSON content for Play Store upload' required: false default: '' google_play_service_account_file: description: 'Fallback path to Google Play service account JSON file (relative to repo root)' required: false default: '' google_play_track: description: 'Google Play track to deploy to (internal, alpha, beta, production)' required: false default: 'beta' google_play_status: description: 'Google Play release status (completed, draft, inProgress, halted)' required: false default: 'completed' google_play_script: description: 'Path to the Dart Google Play upload script (relative to repo root)' required: false default: 'scripts/upload_play_store.dart' shorebird_token: description: 'Shorebird token for optional Shorebird release' required: false default: '' shorebird_artifact: description: 'Shorebird artifact type (apk or aab)' required: false default: 'apk' outputs: version: description: 'Resolved package version from pubspec.yaml' value: ${{ steps.build.outputs.version }} aab_path: description: 'Path to the built AAB file' value: ${{ steps.build.outputs.aab_path }} apk_path: description: 'Path to the built APK file' value: ${{ steps.build.outputs.apk_path }} runs: using: 'composite' steps: - name: Configure Android Signing Keystore shell: bash env: WORK_DIR: ${{ inputs.working_directory }} KEYSTORE_BASE64: ${{ inputs.keystore_base64 }} KEY_ALIAS: ${{ inputs.key_alias }} KEY_PASSWORD: ${{ inputs.key_password }} STORE_PASSWORD: ${{ inputs.store_password }} run: | if [ -n "$KEYSTORE_BASE64" ]; then echo "Decoding Android Keystore..." mkdir -p "$WORK_DIR/android/app" echo "$KEYSTORE_BASE64" | base64 --decode > "$WORK_DIR/android/app/release.keystore" printf "storePassword=%s\nkeyPassword=%s\nkeyAlias=%s\nstoreFile=release.keystore\n" \ "$STORE_PASSWORD" "$KEY_PASSWORD" "$KEY_ALIAS" > "$WORK_DIR/android/key.properties" echo "Configured $WORK_DIR/android/key.properties" else echo "Notice: keystore_base64 not configured. Building unsigned / default debug signing." fi - name: Build Android Release Artifacts id: build shell: bash env: WORK_DIR: ${{ inputs.working_directory }} BUILD_AAB: ${{ inputs.build_aab }} BUILD_APK: ${{ inputs.build_apk }} BUILD_ARGS: ${{ inputs.build_args }} ARTIFACT_PREFIX: ${{ inputs.artifact_prefix }} OUTPUT_DIR: ${{ inputs.output_dir }} run: | ROOT_DIR="$(pwd)" VERSION=$(grep '^version: ' "$WORK_DIR/pubspec.yaml" | head -n 1 | cut -d ' ' -f 2 | tr -d '\r') echo "version=$VERSION" >> "$GITHUB_OUTPUT" cd "$WORK_DIR" if [ "$BUILD_AAB" = "true" ]; then echo "Building Android App Bundle (AAB)..." # shellcheck disable=SC2086 flutter build appbundle --release $BUILD_ARGS fi if [ "$BUILD_APK" = "true" ]; then echo "Building Android APK..." # shellcheck disable=SC2086 flutter build apk --release $BUILD_ARGS fi cd "$ROOT_DIR" AAB_REL="$WORK_DIR/build/app/outputs/bundle/release/app-release.aab" APK_REL="$WORK_DIR/build/app/outputs/flutter-apk/app-release.apk" AAB_REL="${AAB_REL#./}" APK_REL="${APK_REL#./}" if [ -n "$ARTIFACT_PREFIX" ]; then mkdir -p "$OUTPUT_DIR" if [ -f "$AAB_REL" ]; then cp "$AAB_REL" "$OUTPUT_DIR/${ARTIFACT_PREFIX}-v${VERSION}.aab" AAB_OUT="$OUTPUT_DIR/${ARTIFACT_PREFIX}-v${VERSION}.aab" fi if [ -f "$APK_REL" ]; then cp "$APK_REL" "$OUTPUT_DIR/${ARTIFACT_PREFIX}-v${VERSION}.apk" APK_OUT="$OUTPUT_DIR/${ARTIFACT_PREFIX}-v${VERSION}.apk" fi echo "Collected Android artifacts for version $VERSION in $OUTPUT_DIR" fi echo "aab_path=${AAB_OUT:-$AAB_REL}" >> "$GITHUB_OUTPUT" echo "apk_path=${APK_OUT:-$APK_REL}" >> "$GITHUB_OUTPUT" - name: Upload AAB to Google Play Console shell: bash env: WORK_DIR: ${{ inputs.working_directory }} PLAY_STORE_JSON: ${{ inputs.google_play_service_account_json }} PLAY_STORE_FILE: ${{ inputs.google_play_service_account_file }} PLAY_TRACK: ${{ inputs.google_play_track }} PLAY_STATUS: ${{ inputs.google_play_status }} PLAY_SCRIPT: ${{ inputs.google_play_script }} run: | AAB_FILE="$WORK_DIR/build/app/outputs/bundle/release/app-release.aab" AAB_FILE="${AAB_FILE#./}" if [ ! -f "$AAB_FILE" ]; then echo "No AAB file found at $AAB_FILE; skipping Google Play upload." exit 0 fi DEFAULT_SA_FILE="$WORK_DIR/android/app/google_play.json" DEFAULT_SA_FILE="${DEFAULT_SA_FILE#./}" SA_FILE="${PLAY_STORE_FILE:-$DEFAULT_SA_FILE}" if [ -n "$PLAY_STORE_JSON" ] && [ -f "$PLAY_SCRIPT" ]; then echo "Google Play service account secret detected. Uploading AAB to track '$PLAY_TRACK'..." echo "$PLAY_STORE_JSON" > /tmp/google_play_service_account.json dart "$PLAY_SCRIPT" \ --aab "$AAB_FILE" \ --service-account /tmp/google_play_service_account.json \ --track "$PLAY_TRACK" \ --status "$PLAY_STATUS" rm -f /tmp/google_play_service_account.json elif [ -f "$SA_FILE" ] && [ -f "$PLAY_SCRIPT" ]; then echo "Using repository service account file ($SA_FILE) for Google Play upload to track '$PLAY_TRACK'..." dart "$PLAY_SCRIPT" \ --aab "$AAB_FILE" \ --service-account "$SA_FILE" \ --track "$PLAY_TRACK" \ --status "$PLAY_STATUS" else echo "Skipping Google Play upload (credentials or upload script not configured)." fi - name: Release via Shorebird (Optional) if: inputs.shorebird_token != '' shell: bash env: WORK_DIR: ${{ inputs.working_directory }} SHOREBIRD_TOKEN: ${{ inputs.shorebird_token }} SHOREBIRD_ARTIFACT: ${{ inputs.shorebird_artifact }} run: | if [ -n "$SHOREBIRD_TOKEN" ]; then ROOT_DIR="$(pwd)" cd "$WORK_DIR" flutter pub global activate shorebird_cli shorebird release android --artifact="$SHOREBIRD_ARTIFACT" cd "$ROOT_DIR" fi