Flutter-app publiceren in Google Play met GitHub Actions

  • flutter
  • android
  • google play
  • github actions
  • handleiding

Je Flutter-app is klaar en je wilt hem in de Google Play Store krijgen, liefst zonder bij elke update met de hand een bundle te bouwen en te uploaden. In deze handleiding zet je de app in de Play Console en bouw je een pipeline in GitHub Actions die bij elke release een gesigneerde app bundle (AAB) bouwt en uploadt naar de testtrack Intern testen. Naar productie promoot je daarna zelf in de Play Console.

Ik schreef deze stappen op tijdens de publicatie van een echte app (oktober 2026), inclusief de valkuilen waar ik zelf in trapte. Vervang de voorbeeldwaarden (Mijn App, com.voorbeeld.mijn_app, src/mijn_app, …) door die van je eigen project.

Read this article in English: How to publish a Flutter app to Google Play with GitHub Actions.

Ook een iOS-versie nodig? Lees dan Flutter-app naar TestFlight zonder Mac (stap voor stap).

Legenda: 💶 = kost geld · ⏳ = wachttijd · ⚠️ = valkuil die we echt tegenkwamen

Inhoud

  1. Overzicht en benodigdheden
  2. Beslissingen vooraf
  3. Google Play-ontwikkelaarsaccount
  4. Flutter-project voorbereiden
  5. Upload-keystore en signing
  6. App aanmaken en eerste upload (met de hand)
  7. App-content en store-listing invullen
  8. Service account voor uploads vanuit CI
  9. GitHub-secrets
  10. CI op elke pull request
  11. Release-workflow: bouwen en uploaden naar Play
  12. Optioneel: versies automatisch met release-please
  13. Testers en naar productie
  14. Problemen en oplossingen
  15. Korte checklist

0. Overzicht en benodigdheden

Overzicht van het publicatieproces

Wat Waarom Kost
Google-account Eigenaar van het ontwikkelaarsaccount gratis
Google Play Console-account Apps publiceren 💶 $25 eenmalig
GitHub-repo met het Flutter-project Broncode + GitHub Actions gratis (publieke repo of binnen de gratis minuten)
Android-toestel Testen via de testtrack —
JDK 17+ en Android SDK Lokaal bouwen (zit in Android Studio) gratis
Publieke privacybeleid-URL Verplicht in de Play Console (zeker met advertenties) —

1. Beslissingen vooraf

Beslissing Voorbeeld Opmerking
Application ID com.voorbeeld.mijn_app ⚠️ Na de eerste upload nooit meer te wijzigen. Underscores mogen op Android (op iOS niet: kies eventueel een vorm zonder _ als je beide platformen wilt gelijktrekken).
App-naam (Play Store) Mijn App Max. 30 tekens.
Accounttype Persoonlijk Organisatie vraagt een D-U-N-S-nummer.
Productietoegang — ⚠️ Nieuwe persoonlijke accounts moeten eerst een gesloten test doen met minstens 12 testers gedurende 14 dagen voor ze naar productie mogen. Zie stap 12.3.
Releasetrack voor CI internal Snelst (geen review), max. 100 testers.

2. Google Play-ontwikkelaarsaccount

  1. Ga naar https://play.google.com/console/signup en meld aan met het Google-account dat eigenaar wordt.
  2. Kies persoonlijk of organisatie en betaal 💶 $25.
  3. ⏳ Identiteitsverificatie (ID-kaart, adres, telefoonnummer). Kan enkele dagen duren.
  4. Zet tweestapsverificatie aan op het Google-account.

3. Flutter-project voorbereiden

3.1 Application ID en namespace

In android/app/build.gradle.kts:

android {
    namespace = "com.voorbeeld.mijn_app"
    compileSdk = 36
    defaultConfig {
        applicationId = "com.voorbeeld.mijn_app"
        minSdk = flutter.minSdkVersion
        targetSdk = 36                    // minstens wat Google Play op dat moment eist
        versionCode = flutter.versionCode // = buildnummer uit pubspec.yaml
        versionName = flutter.versionName // = versie uit pubspec.yaml
    }
}
  • Verplaats ook MainActivity.kt naar de map die bij de nieuwe package hoort (android/app/src/main/kotlin/com/voorbeeld/mijn_app/) en pas de package-regel aan.
  • ⚠️ Target API level: Google verhoogt jaarlijks de minimale targetSdk voor nieuwe apps en updates. Controleer de actuele eis op Target API level requirements.

3.2 Versie en buildnummer

In pubspec.yaml:

version: 1.0.0+1   # versionName 1.0.0, versionCode 1

⚠️ Google Play weigert elke upload met een versionCode die al gebruikt is. Verhoog het getal na de + bij elke upload.

3.3 Appnaam en iconen

  • Naam onder het icoon: android:label="Mijn App" in android/app/src/main/AndroidManifest.xml.

  • Iconen: flutter_launcher_icons met een 1024×1024 PNG:

    # flutter_launcher_icons.yaml
    flutter_launcher_icons:
      android: true
      ios: false
      image_path: "assets/icon/app_icon.png"
      adaptive_icon_background: "#FFFFFF"
      adaptive_icon_foreground: "assets/icon/app_icon_foreground.png"
    flutter pub add --dev flutter_launcher_icons
    dart run flutter_launcher_icons
  • Store-icoon voor de listing: 512×512 PNG (apart te uploaden in stap 6).

3.4 Advertenties (alleen bij AdMob)

  1. AdMob → Apps → App toevoegen → Android. Staat de app nog niet in Google Play, kies dan Nee bij "staat de app in een ondersteunde app store?".

  2. Noteer het app-ID (ca-app-pub-…~…) en maak een banner-advertentieblok (ca-app-pub-…/…).

  3. In AndroidManifest.xml, binnen <application>:

    <meta-data
        android:name="com.google.android.gms.ads.APPLICATION_ID"
        android:value="ca-app-pub-XXXXXXXXXXXXXXXX~XXXXXXXXXX"/>
  4. Gebruik in debug- en profile-builds altijd Google's test-banner (ca-app-pub-3940256099942544/6300978111), echte ID's alleen als kReleaseMode true is. ⚠️ Eigen impressies of kliks met echte ID's kunnen je AdMob-account laten schorsen.

  5. GDPR: Google eist een gecertificeerd toestemmingsplatform in de EER/VK/CH. Gebruik de UMP SDK (zit in google_mobile_ads), start Mobile Ads pas als canRequestAds() true is, en maak in AdMob → Privacy & messaging een GDPR-bericht aan. Zonder dat bericht toont UMP niets.

  6. Zet een app-ads.txt op de website die je als ontwikkelaarswebsite opgeeft in Play.

3.5 In de app zelf

  • Link naar het privacybeleid (bv. in het scherm "Over").
  • Bij ads met UMP: een optie "Privacy Settings" in de instellingen (ConsentForm.showPrivacyOptionsForm), alleen tonen als getPrivacyOptionsRequirementStatus() required is.

3.6 Controleren

dart format --set-exit-if-changed lib test
flutter analyze
flutter test
flutter build apk --debug

4. Upload-keystore en signing

Google Play gebruikt Play App Signing: Google bewaart de echte app signing key; jij signeert uploads met een eigen upload-key. Raak je de upload-key kwijt, dan kan Google hem resetten (⏳ via support). Raak je hem niet kwijt: dat is veel makkelijker.

4.1 Keystore aanmaken

Op Windows met de JDK van Android Studio (pad kan verschillen):

& "C:\Program Files\Android\Android Studio\jbr\bin\keytool.exe" -genkey -v `
  -keystore "$env:USERPROFILE\upload-keystore.jks" `
  -storetype JKS -keyalg RSA -keysize 2048 -validity 10000 -alias upload

Op macOS/Linux:

keytool -genkey -v -keystore ~/upload-keystore.jks -storetype JKS \
  -keyalg RSA -keysize 2048 -validity 10000 -alias upload
  • Kies een sterk wachtwoord voor de keystore en voor de key.
  • ⚠️ Back-up: het .jks-bestand + beide wachtwoorden + de alias in je wachtwoordbeheerder.
  • ⚠️ Bewaar de keystore buiten de repo en commit hem nooit.

4.2 android/key.properties (alleen lokaal)

storePassword=<keystore-wachtwoord>
keyPassword=<key-wachtwoord>
keyAlias=upload
storeFile=C:\\Users\\<jij>\\upload-keystore.jks

Voeg toe aan android/.gitignore (staat er bij nieuwe Flutter-projecten meestal al):

key.properties
**/*.jks
**/*.keystore

4.3 Signing in android/app/build.gradle.kts

Leest de waarden uit key.properties of uit omgevingsvariabelen (voor CI). Zonder key valt de build terug op de debug-key, zodat CI op pull requests ook zonder secrets werkt. Google Play weigert zo'n build, dus je kunt hem nooit per ongeluk publiceren.

import java.io.FileInputStream
import java.util.Properties

val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("key.properties")
if (keystorePropertiesFile.exists()) {
    keystoreProperties.load(FileInputStream(keystorePropertiesFile))
}

// Lokaal uit key.properties; in CI uit omgevingsvariabelen.
fun signingValue(propertyKey: String, envKey: String): String? =
    keystoreProperties.getProperty(propertyKey) ?: System.getenv(envKey)

val releaseStoreFile = signingValue("storeFile", "ANDROID_KEYSTORE_PATH")
val hasReleaseSigning = releaseStoreFile != null

android {
    // … namespace, defaultConfig, …

    signingConfigs {
        if (hasReleaseSigning) {
            create("release") {
                keyAlias = signingValue("keyAlias", "ANDROID_KEY_ALIAS")
                keyPassword = signingValue("keyPassword", "ANDROID_KEY_PASSWORD")
                storeFile = file(releaseStoreFile!!)
                storePassword = signingValue("storePassword", "ANDROID_KEYSTORE_PASSWORD")
            }
        }
    }
    buildTypes {
        release {
            signingConfig = if (hasReleaseSigning) {
                signingConfigs.getByName("release")
            } else {
                logger.warn("No upload key found: release build is signed with the debug key.")
                signingConfigs.getByName("debug")
            }
        }
    }
}

4.4 Lokaal een gesigneerde bundle bouwen

flutter build appbundle --release

Resultaat: build/app/outputs/bundle/release/app-release.aab.

5. App aanmaken en eerste upload (met de hand)

⚠️ De Google Play-API kan geen nieuwe app aanmaken en de eerste bundle niet uploaden. Doe dit één keer met de hand; daarna neemt CI het over.

  1. Play Console → App maken.
    • App-naam, standaardtaal, App (geen game), Gratis.
    • Bevestig de verklaringen → App maken.
  2. Testen → Intern testen → Nieuwe release maken.
  3. Play App Signing: accepteer Door Google gegenereerde sleutel gebruiken (standaard).
  4. Upload app-release.aab uit stap 4.4.
  5. Releasenaam en releasenotities invullen → Volgende → Opslaan en publiceren.
  6. Testers (tabblad bij Intern testen): maak een e-maillijst met minstens jezelf.

Controleer daarna bij Testen en releasen → App-integriteit dat Play App Signing actief is.

6. App-content en store-listing invullen

Het dashboard van de app toont welke taken nog open staan. Vul ze allemaal in; zonder deze gegevens kun je niet naar productie (en voor sommige testtracks ook niet).

Beleid → App-content:

Onderdeel Wat invullen
Privacybeleid De publieke URL
App-toegang "Alle functionaliteit is beschikbaar zonder speciale toegang" (tenzij je een login hebt)
Advertenties "Ja, mijn app bevat advertenties" als je AdMob gebruikt
Contentclassificatie Vragenlijst (IARC) eerlijk invullen
Doelgroep Leeftijdsgroepen; ⚠️ kies je kinderen, dan gelden strenge regels voor ads
Gegevensveiligheid Met AdMob: apparaat- of andere ID's, app-interacties, diagnostiek; gedeeld met Google voor advertenties; versleuteld onderweg
Advertentie-ID "Ja", gebruikt voor advertenties (met AdMob)
Overheidsapps, financiële functies, gezondheid, … Meestal "Nee"

Groeien → Winkelvermelding (store-listing):

Onderdeel Eisen
App-naam Max. 30 tekens
Korte beschrijving Max. 80 tekens
Volledige beschrijving Max. 4000 tekens
App-icoon 512×512 PNG
Feature graphic 1024×500 PNG/JPEG
Screenshots telefoon Min. 2, bv. 1080×1920
Categorie, contactgegevens E-mail verplicht, website optioneel

7. Service account voor uploads vanuit CI

7.1 In Google Cloud

  1. Ga naar de Google Cloud Console → maak een project (bv. play-publisher) of kies een bestaand.
  2. APIs & Services → Library → zoek Google Play Android Developer API → Enable.
  3. IAM & Admin → Service Accounts → Create service account: naam bv. github-play-upload → Create and continue → geen rol nodig → Done.
  4. Open het service account → Keys → Add key → Create new key → JSON → het bestand wordt gedownload. ⚠️ Dit is een geheim: meteen in je wachtwoordbeheerder, nooit in git.
  5. Kopieer het e-mailadres van het service account (…@….iam.gserviceaccount.com).

7.2 In de Play Console

  1. Gebruikers en rechten → Nieuwe gebruikers uitnodigen → plak het e-mailadres van het service account.
  2. App-rechten → App toevoegen → kies je app, met minstens:
    • Releases naar testtracks
    • Releases beheren voor testtracks (de exacte naam verschilt per taal en versie van de Console)
  3. Gebruiker uitnodigen.
  4. ⏳ Rechten kunnen tot 24–36 uur nodig hebben voor de API ze herkent.

Eén service account kan voor al je apps dienen: voeg per nieuwe app alleen de app-rechten toe.

8. GitHub-secrets

GitHub → repo → Settings → Secrets and variables → Actions → New repository secret:

Secret Inhoud
ANDROID_KEYSTORE_BASE64 De keystore, base64-gecodeerd (zie hieronder)
ANDROID_KEYSTORE_PASSWORD Wachtwoord van de keystore
ANDROID_KEY_ALIAS Alias, bv. upload
ANDROID_KEY_PASSWORD Wachtwoord van de key
PLAY_SERVICE_ACCOUNT_JSON Volledige inhoud van het JSON-bestand uit stap 7.1

Keystore naar base64 en meteen op het klembord:

# Windows (PowerShell)
[Convert]::ToBase64String([IO.File]::ReadAllBytes("$env:USERPROFILE\upload-keystore.jks")) | Set-Clipboard
# macOS
base64 -i ~/upload-keystore.jks | pbcopy
# Linux
base64 -w 0 ~/upload-keystore.jks

9. CI op elke pull request

.github/workflows/ci.yml: controleert elke PR, zonder secrets (de release-build valt terug op de debug-key, zie 4.3).

name: CI

on:
  pull_request:
    branches: [master]
  push:
    branches: [master]

concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true

permissions:
  contents: read

env:
  FLUTTER_VERSION: 3.47.3          # vastleggen: lokaal en CI dezelfde versie

defaults:
  run:
    working-directory: src/mijn_app   # weglaten als het project in de root staat

jobs:
  checks:
    name: Format, analyze and test
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v7
      - uses: subosito/flutter-action@v2
        with:
          flutter-version: ${{ env.FLUTTER_VERSION }}
          channel: stable
          cache: true
      - run: flutter pub get
      - run: dart format --output=none --set-exit-if-changed lib test
      - run: flutter analyze
      - run: flutter test

  build-android:
    name: Build Android (debug)
    runs-on: ubuntu-latest
    needs: checks
    timeout-minutes: 30
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-java@v6
        with:
          distribution: temurin
          java-version: 17
          cache: gradle
          cache-dependency-path: |
            src/mijn_app/android/*.gradle.kts
            src/mijn_app/android/gradle/wrapper/gradle-wrapper.properties
      - uses: subosito/flutter-action@v2
        with:
          flutter-version: ${{ env.FLUTTER_VERSION }}
          channel: stable
          cache: true
      - run: flutter pub get
      - run: flutter build apk --debug

Branch protection (aanrader): GitHub → Settings → Branches → Add rule voor master → Require a pull request + Require status checks: "Format, analyze and test" en "Build Android (debug)". Die checks kun je pas kiezen nadat ze één keer gedraaid hebben.

10. Release-workflow: bouwen en uploaden naar Play

.github/workflows/release.yml: start bij het pushen van een tag v* (bv. v1.0.1).

name: Release

on:
  push:
    tags: ["v*"]

permissions:
  contents: read

jobs:
  android:
    name: Android → Play internal testing
    runs-on: ubuntu-latest
    timeout-minutes: 45
    defaults:
      run:
        working-directory: src/mijn_app
    steps:
      - uses: actions/checkout@v7

      - uses: actions/setup-java@v6
        with:
          distribution: temurin
          java-version: 17
          cache: gradle
          cache-dependency-path: |
            src/mijn_app/android/*.gradle.kts
            src/mijn_app/android/gradle/wrapper/gradle-wrapper.properties

      - uses: subosito/flutter-action@v2
        with:
          flutter-version: 3.47.3
          channel: stable
          cache: true

      - run: flutter pub get

      - name: Restore upload keystore
        env:
          ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
        run: |
          if [ -z "$ANDROID_KEYSTORE_BASE64" ]; then
            echo "::error::Secret ANDROID_KEYSTORE_BASE64 is missing."
            exit 1
          fi
          echo "$ANDROID_KEYSTORE_BASE64" | base64 --decode > "$RUNNER_TEMP/upload-keystore.jks"
          echo "ANDROID_KEYSTORE_PATH=$RUNNER_TEMP/upload-keystore.jks" >> "$GITHUB_ENV"

      - name: Build signed app bundle
        env:
          ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
          ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
          ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
        run: flutter build appbundle --release

      - name: Upload to Google Play (internal testing)
        uses: r0adkll/upload-google-play@v1
        with:
          serviceAccountJsonPlainText: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }}
          packageName: com.voorbeeld.mijn_app
          releaseFiles: src/mijn_app/build/app/outputs/bundle/release/app-release.aab
          mappingFile: src/mijn_app/build/app/outputs/mapping/release/mapping.txt
          track: internal
          status: completed
          releaseName: ${{ github.ref_name }}
  • mappingFile geeft Play de R8-mapping, zodat crashrapporten leesbaar zijn. Bestaat het bestand niet (R8 uit), laat de regel weg.
  • Releasenotities ("Wat is er nieuw"): voeg whatsNewDirectory: <map> toe met een bestand whatsnew-en-US (max. 500 tekens) per taal.

Een release maken (zonder release-please):

# 1. versie in pubspec.yaml verhogen, bv. 1.0.1+2, committen en mergen
# 2. tag maken en pushen
git tag v1.0.1
git push origin v1.0.1

Volg de run in GitHub → Actions → Release. Na ±10 minuten staat de build in de Play Console onder Intern testen.

11. Optioneel: versies automatisch met release-please

release-please leest je commitberichten (Conventional Commits: feat:, fix:, …) en houdt een release-PR bij met de nieuwe versie in pubspec.yaml (buildnummer +1) en een CHANGELOG.md. Merge je die PR, dan maakt het een tag en een GitHub Release.

11.1 Configuratie

release-please-config.json in de root:

{
  "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
  "include-component-in-tag": false,
  "packages": {
    "src/mijn_app": {
      "release-type": "dart",
      "package-name": "mijn_app",
      "changelog-path": "CHANGELOG.md"
    }
  }
}

.release-please-manifest.json met de huidige versie:

{ "src/mijn_app": "1.0.0" }

Bestaat het project al met releases, voeg dan "bootstrap-sha": "<commit van de laatste release>" toe, anders neemt release-please de hele geschiedenis mee in de eerste changelog.

11.2 Workflow

⚠️ Tags die release-please met de standaard GITHUB_TOKEN maakt, starten geen andere workflows. De tag-trigger uit stap 10 werkt dan niet. Oplossing: bouw in dezelfde workflow, als job die alleen draait als er een release gemaakt is.

name: Release

on:
  push:
    branches: [master]

permissions:
  contents: write
  pull-requests: write

jobs:
  release-please:
    runs-on: ubuntu-latest
    outputs:
      release_created: ${{ steps.release.outputs['src/mijn_app--release_created'] }}
      tag_name: ${{ steps.release.outputs['src/mijn_app--tag_name'] }}
      version: ${{ steps.release.outputs['src/mijn_app--version'] }}
    steps:
      - uses: googleapis/release-please-action@v5
        id: release
        with:
          config-file: release-please-config.json
          manifest-file: .release-please-manifest.json

  android:
    needs: release-please
    if: needs.release-please.outputs.release_created == 'true'
    # … dezelfde stappen als in stap 10, met:
    #     releaseName: ${{ needs.release-please.outputs.version }}

Staat het project in een submap, dan heten de outputs <pad>--release_created enz. Staat het in de root, dan gewoon release_created, tag_name en version.

11.3 GitHub-instellingen

  • Settings → Actions → General → Allow GitHub Actions to create and approve pull requests aan.
  • Settings → General → Pull Requests: alleen squash merging, Default to PR title aan. De PR-titel wordt dan de commit op master, dus die moet Conventional Commits volgen. Controleer dat met action-semantic-pull-request.
  • ⚠️ Op een release-PR draaien geen CI-checks (zelfde GITHUB_TOKEN-beperking). Met branch protection blijven verplichte checks dan op Expected — Waiting for status staan. Kies:
    • als admin mergen met "Merge without waiting for requirements to be met (bypass branch protections)"; laat daarvoor Do not allow bypassing the above settings uit, of
    • een fine-grained personal access token of GitHub App als token: voor release-please (checks draaien dan wel, maar de token moet je vernieuwen).

12. Testers en naar productie

12.1 Intern testen (max. 100 testers, geen review)

  1. Testen → Intern testen → Testers → e-maillijst met Google-accounts van de testers.
  2. Kopieer de link voor deelname en stuur die naar de testers.
  3. Tester opent de link op het toestel → Tester worden → installeert via de Play Store. (De eerste keer kan het enkele minuten duren voor de app zichtbaar is.)

12.2 Gesloten en open testen

  • Gesloten testen: grotere groepen (e-maillijsten of Google Groups), met review van Google.
  • Open testen: iedereen kan meedoen via de Play Store-pagina.
  • Een release naar een andere track: open de release in Intern testen → Release promoten.

12.3 Nieuwe persoonlijke accounts: gesloten test verplicht

⚠️ Persoonlijke ontwikkelaarsaccounts die na november 2023 zijn aangemaakt, krijgen pas productietoegang na een gesloten test met minstens 12 testers die 14 dagen achter elkaar meedoen. Daarna vraag je productietoegang aan via het dashboard (met een korte vragenlijst). Details: Testvereisten voor nieuwe persoonlijke accounts.

12.4 Naar productie

  1. Testen → Intern testen → release → Release promoten → Productie.
  2. Kies landen (Productie → Landen/regio's) en eventueel een gefaseerde uitrol (bv. 20 %).
  3. Ter review verzenden → ⏳ review door Google (uren tot enkele dagen).
  4. Controleer na de release Android vitals en het pre-launch-rapport op crashes.

13. Problemen en oplossingen

Melding / symptoom Oorzaak Oplossing
Upload via API: Package not found App bestaat nog niet of had nog geen enkele upload Stap 5: app aanmaken en eerste bundle met de hand uploaden
The caller does not have permission Service account mist app-rechten, of rechten nog niet actief Stap 7.2 controleren; ⏳ tot 36 uur wachten
Version code X has already been used Buildnummer niet verhoogd pubspec.yaml: getal na + verhogen
You uploaded an APK or Android App Bundle that was signed in debug mode Geen upload-key gevonden tijdens de build Secrets (stap 8) of key.properties controleren
signed with the wrong key Andere keystore dan bij de eerste upload Juiste keystore gebruiken; kwijt → upload-key resetten via de Play Console (App-integriteit)
Release-workflow start niet na een release-please-tag Tags via GITHUB_TOKEN triggeren geen workflows Bouwen in dezelfde workflow (stap 11.2)
Release-PR blijft geblokkeerd door branch protection Geen checks op PR's van GITHUB_TOKEN Als admin bypassen of een PAT/GitHub App (stap 11.3)
Workflow faalt: secret missing Secret niet aangemaakt vóór de eerste release Secret aanmaken → Actions → run → Re-run failed jobs
Play: targetSdkVersion te laag Nieuwe jaarlijkse eis van Google targetSdk verhogen (stap 3.1)
Play: ondersteuning voor 16 KB-paginagrootte ontbreekt Oude NDK/AGP of native libraries Recente Flutter, AGP en NDK; controleer in App-bundleverkenner
Windows: flutter build vraagt symlink-ondersteuning Ontwikkelaarsmodus uit start ms-settings:developers → Ontwikkelaarsmodus aan
Kotlin-compilatie faalt met different roots Project en Pub-cache op een andere schijf (bv. D: en C:) kotlin.incremental=false in android/gradle.properties
Gradle: Unsupported class file major version Te nieuwe of te oude JDK voor de Gradle-versie Gradle/AGP bijwerken of de JDK van Android Studio gebruiken
Geen advertenties in de EU Geen GDPR-bericht in AdMob of geen UMP-consent Stap 3.4 punt 5

14. Korte checklist

Eenmalig per account (bij een volgende app overslaan):

  • [ ] Google Play Console-account 💶, identiteit geverifieerd ⏳
  • [ ] Google Cloud-project met Google Play Android Developer API
  • [ ] Service account + JSON-sleutel bewaard
  • [ ] (AdMob) account en app-ads.txt op je website

Per app:

  • [ ] Application ID, naam, privacybeleid-URL gekozen
  • [ ] build.gradle.kts: namespace, applicationId, targetSdk, signing via key.properties of env
  • [ ] Iconen, appnaam, privacylink in de app
  • [ ] (AdMob) app-ID in het manifest, test-ID's in debug, UMP-consent, GDPR-bericht
  • [ ] Upload-keystore aangemaakt + back-up
  • [ ] App aangemaakt in de Play Console + eerste AAB met de hand naar Intern testen
  • [ ] App-content en store-listing ingevuld
  • [ ] Service account uitgenodigd met app-rechten
  • [ ] GitHub-secrets (5 stuks)
  • [ ] ci.yml + branch protection
  • [ ] release.yml (tag-trigger of release-please)
  • [ ] Eerste automatische release staat in Intern testen
  • [ ] Testers uitgenodigd, getest, gepromoot naar productie

Vragen of liep je ergens anders vast? Laat het me gerust weten, dan vul ik deze handleiding aan.