[{"data":1,"prerenderedAt":27},["ShallowReactive",2],{"articolo-pubblicare-unapp-flutter-su-play-store-e-app-store-build-firma-e-distribuzione":3,"comments-article-pubblicare-unapp-flutter-su-play-store-e-app-store-build-firma-e-distribuzione":26},{"id":4,"title":5,"slug":6,"excerpt":7,"body":8,"cover_image":9,"cover_remote_url":10,"cover_credit":11,"video_url":15,"status":16,"published_at":17,"meta_title":18,"meta_description":19,"category":20,"author":24},133,"Pubblicare un'app Flutter su Play Store e App Store: build, firma e distribuzione","pubblicare-unapp-flutter-su-play-store-e-app-store-build-firma-e-distribuzione","Guida completa al rilascio di un'app Flutter: versionamento, keystore Android, App Bundle, firma iOS, build IPA, TestFlight, rollout graduale su Google Play, privacy manifest e automazione con fastlane.","Scrivere l'app è solo metà del lavoro: il momento della pubblicazione è quello in cui emergono keystore dimenticati, profili di provisioning scaduti, requisiti di target SDK non rispettati e form sulla privacy da compilare. In questa guida vediamo l'intero percorso che porta da `flutter run` a un'app disponibile su Google Play e App Store, con i requisiti aggiornati al 2025.\n\n## 1. Versionamento: una sola fonte di verità\n\nFlutter centralizza la versione nel `pubspec.yaml`:\n\n```yaml\nname: my_app\ndescription: La mia app Flutter\nversion: 1.4.0+42\n```\n\nIl formato è `versionName+versionCode`:\n\n- **1.4.0** è la versione visibile all'utente (`CFBundleShortVersionString` su iOS, `versionName` su Android).\n- **42** è il numero di build (`CFBundleVersion`, `versionCode`): deve essere **strettamente crescente** su entrambi gli store, altrimenti l'upload viene rifiutato.\n\nIn CI conviene non toccare il pubspec e passare i valori da riga di comando:\n\n```bash\nflutter build appbundle --release \\\n  --build-name=1.4.0 \\\n  --build-number=$GITHUB_RUN_NUMBER\n```\n\nPer mostrare la versione dentro l'app (utilissimo nelle schermate di supporto) si usa `package_info_plus`:\n\n```dart\nimport 'package:package_info_plus\u002Fpackage_info_plus.dart';\n\nclass AppVersion {\n  static late final String label;\n\n  static Future\u003Cvoid> init() async {\n    final info = await PackageInfo.fromPlatform();\n    label = 'v${info.version} (${info.buildNumber})';\n  }\n}\n\n\u002F\u002F In un widget\nText(AppVersion.label, style: Theme.of(context).textTheme.bodySmall);\n```\n\n## 2. Android: creare e proteggere il keystore\n\nLa firma di upload è la chiave con cui Google Play riconosce che l'artefatto arriva davvero da te. Si genera una volta sola:\n\n```bash\nkeytool -genkey -v -keystore ~\u002Fupload-keystore.jks \\\n  -keyalg RSA -keysize 2048 -validity 10000 -alias upload\n```\n\nIl file **non va committato**. Si referenzia tramite `android\u002Fkey.properties` (da aggiungere al `.gitignore`):\n\n```properties\nstorePassword=********\nkeyPassword=********\nkeyAlias=upload\nstoreFile=\u002FUsers\u002Fmario\u002Fupload-keystore.jks\n```\n\nNei progetti Flutter recenti la configurazione Gradle usa il Kotlin DSL (`android\u002Fapp\u002Fbuild.gradle.kts`):\n\n```kotlin\nimport java.util.Properties\nimport java.io.FileInputStream\n\nval keystoreProperties = Properties()\nval keystorePropertiesFile = rootProject.file(\"key.properties\")\nif (keystorePropertiesFile.exists()) {\n    keystoreProperties.load(FileInputStream(keystorePropertiesFile))\n}\n\nandroid {\n    namespace = \"com.example.myapp\"\n    compileSdk = 35\n\n    defaultConfig {\n        applicationId = \"com.example.myapp\"\n        minSdk = flutter.minSdkVersion\n        targetSdk = 35\n        versionCode = flutter.versionCode\n        versionName = flutter.versionName\n    }\n\n    signingConfigs {\n        create(\"release\") {\n            keyAlias = keystoreProperties[\"keyAlias\"] as String?\n            keyPassword = keystoreProperties[\"keyPassword\"] as String?\n            storeFile = (keystoreProperties[\"storeFile\"] as String?)?.let { file(it) }\n            storePassword = keystoreProperties[\"storePassword\"] as String?\n        }\n    }\n\n    buildTypes {\n        release {\n            signingConfig = signingConfigs.getByName(\"release\")\n            isMinifyEnabled = true\n            isShrinkResources = true\n        }\n    }\n}\n```\n\n> **Attenzione al target SDK**: dal 31 agosto 2025 Google Play richiede `targetSdk = 35` (Android 15) per le nuove app e per gli aggiornamenti. Verifica anche la compatibilità edge-to-edge, obbligatoria su Android 15.\n\n### Play App Signing\n\nAlla prima pubblicazione Google genera (o importa) la **app signing key** definitiva e conserva solo quella: la tua `upload-keystore.jks` può essere resettata in caso di smarrimento aprendo un ticket. Resta comunque buona prassi archiviarla in un password manager aziendale, insieme alle password.\n\n## 3. Generare l'artefatto Android\n\nGoogle Play accetta solo **Android App Bundle** per le nuove app:\n\n```bash\nflutter build appbundle --release \\\n  --obfuscate --split-debug-info=build\u002Fsymbols\n```\n\nIl file finisce in `build\u002Fapp\u002Foutputs\u002Fbundle\u002Frelease\u002Fapp-release.aab`. Gli APK restano utili solo per distribuzioni dirette (sideload, store alternativi, test manuali):\n\n```bash\nflutter build apk --release --split-per-abi\n```\n\nSe usi `--obfuscate`, **conserva la cartella dei simboli** per ogni build: senza di essa gli stack trace di produzione sono illeggibili. Per Crashlytics o Sentry i simboli vanno caricati nello stesso step della build; per il de-obfuscation manuale si usa:\n\n```bash\nflutter symbolize -i stacktrace.txt -d build\u002Fsymbols\u002Fapp.android-arm64.symbols\n```\n\nRicorda anche di caricare su Play Console il file di mapping R8 (`build\u002Fapp\u002Foutputs\u002Fmapping\u002Frelease\u002Fmapping.txt`): l'AAB generato da Flutter lo include automaticamente se la minificazione è attiva.\n\n## 4. iOS: identificatori, certificati e capability\n\nSul fronte Apple servono tre pezzi che devono combaciare:\n\n1. **App ID (bundle identifier)** registrato sul portale Apple Developer, con le capability abilitate (push, Sign in with Apple, associated domains...).\n2. **Certificato di distribuzione** (Apple Distribution).\n3. **Provisioning profile** App Store collegato ai due precedenti.\n\nIn Xcode (`ios\u002FRunner.xcworkspace`) imposta il Team sotto *Signing & Capabilities* e lascia attivo *Automatically manage signing* se lavori in locale; in CI è preferibile la firma manuale con certificati importati in un keychain temporaneo (fastlane match risolve bene il problema).\n\nNel `Info.plist` non dimenticare:\n\n```xml\n\u003Ckey>ITSAppUsesNonExemptEncryption\u003C\u002Fkey>\n\u003Cfalse\u002F>\n```\n\nEvita la domanda sull'export compliance a ogni build (impostalo a `false` solo se usi esclusivamente la crittografia standard di sistema, es. HTTPS).\n\n> **Requisito 2025**: le app inviate all'App Store devono essere compilate con Xcode 16 e SDK iOS 18. Verifica inoltre che tutti i plugin con codice nativo includano il **privacy manifest** (`PrivacyInfo.xcprivacy`): se un SDK della lista Apple ne è sprovvisto, l'upload viene rifiutato con un warning bloccante via email.\n\n## 5. Build e upload dell'IPA\n\nDalla riga di comando:\n\n```bash\nflutter build ipa --release \\\n  --export-method app-store-connect \\\n  --obfuscate --split-debug-info=build\u002Fsymbols\n```\n\nSe hai esigenze particolari (bitcode, simboli, distribuzione ad-hoc) puoi passare un plist dedicato:\n\n```bash\nflutter build ipa --export-options-plist=ios\u002FExportOptions.plist\n```\n\n```xml\n\u003C!-- ios\u002FExportOptions.plist -->\n\u003Cplist version=\"1.0\">\n\u003Cdict>\n  \u003Ckey>method\u003C\u002Fkey>\n  \u003Cstring>app-store-connect\u003C\u002Fstring>\n  \u003Ckey>teamID\u003C\u002Fkey>\n  \u003Cstring>ABCDE12345\u003C\u002Fstring>\n  \u003Ckey>uploadSymbols\u003C\u002Fkey>\n  \u003Ctrue\u002F>\n\u003C\u002Fdict>\n\u003C\u002Fplist>\n```\n\nL'IPA risultante (`build\u002Fios\u002Fipa\u002F*.ipa`) si carica con l'app **Transporter**, con Xcode Organizer oppure, in automazione, con `fastlane pilot upload`.\n\n## 6. Canali di test e rollout\n\n### Google Play\n\nI track disponibili sono quattro: **internal testing** (fino a 100 tester, disponibile in pochi minuti), **closed**, **open** e **production**. Il flusso consigliato è internal → closed → produzione con **rollout graduale**: parti dal 5-10% degli utenti e monitora crash rate e ANR in Android Vitals prima di salire. Se qualcosa va storto puoi fermare il rilascio (ma non \"tornare indietro\": serve una nuova build con `versionCode` più alto).\n\nAttenzione al requisito introdotto per i nuovi account sviluppatore personali: 12 tester attivi per 14 giorni consecutivi in closed testing prima di poter pubblicare in produzione.\n\n### App Store Connect\n\nDopo l'upload la build appare in **TestFlight** entro qualche minuto (dopo l'elaborazione). I tester interni (fino a 100 membri del team) ricevono la build subito; i gruppi esterni (fino a 10.000 utenti) richiedono una breve revisione di Apple. Per la produzione puoi usare il **rilascio graduale su 7 giorni** e la pubblicazione manuale dopo l'approvazione.\n\nPrepara in anticipo i materiali che bloccano più spesso l'invio: screenshot nelle dimensioni richieste, note sulla privacy (App Privacy), account demo funzionante per il reviewer e una descrizione chiara dei permessi usati (le stringhe `NS...UsageDescription` devono spiegare il motivo reale, non essere generiche).\n\n## 7. Privacy e conformità\n\n- **Google Play Data Safety**: va dichiarato quali dati raccogli, se vengono condivisi e se sono cifrati in transito. Attenzione: anche SDK di terze parti (analytics, ads, crash reporting) contano.\n- **Apple App Privacy** + `PrivacyInfo.xcprivacy` con le *required reason API* usate.\n- **Account deletion**: entrambi gli store richiedono un percorso in-app o un link web per eliminare l'account se l'app ne consente la creazione.\n- **Consenso pubblicitario**: se usi ads nello Spazio Economico Europeo serve una CMP certificata (es. Google UMP).\n\n## 8. Automatizzare con fastlane\n\nUn `Fastfile` minimale per Android evita l'upload manuale:\n\n```ruby\n# android\u002Ffastlane\u002FFastfile\ndefault_platform(:android)\n\nplatform :android do\n  desc \"Deploy su internal testing\"\n  lane :internal do\n    sh(\"flutter build appbundle --release\")\n    upload_to_play_store(\n      track: \"internal\",\n      aab: \"..\u002Fbuild\u002Fapp\u002Foutputs\u002Fbundle\u002Frelease\u002Fapp-release.aab\",\n      skip_upload_metadata: true,\n      skip_upload_images: true\n    )\n  end\nend\n```\n\nPer iOS, `match` gestisce certificati condivisi in un repo cifrato e `pilot` carica su TestFlight. Collegando il tutto a GitHub Actions (con i segreti in `secrets`: keystore in base64, API key di Play, App Store Connect API key) ottieni un rilascio riproducibile con un tag git.\n\n## 9. Checklist pre-release\n\n- `flutter analyze` e test verdi, dipendenze aggiornate (`flutter pub outdated`).\n- Build in modalità release provata su dispositivo fisico, non solo su emulatore.\n- Icone e splash screen generati per tutte le densità; nome app corretto per ogni flavor.\n- Endpoint di produzione configurati (niente URL di staging nei flavor sbagliati).\n- Crash reporting attivo e simboli caricati.\n- `versionCode`\u002F`buildNumber` incrementati.\n- Offuscamento abilitato e cartella `symbols` archiviata insieme all'artefatto.\n- Permessi inutili rimossi dal `AndroidManifest.xml` e dall'`Info.plist` (i plugin ne aggiungono spesso più del necessario).\n- Policy sulla privacy raggiungibile da URL pubblico.\n\n## Conclusione\n\nLa pubblicazione è un processo ripetibile: una volta create le chiavi, configurati i flavor e automatizzati gli upload, rilasciare una nuova versione diventa questione di minuti. L'investimento iniziale più importante è la **gestione dei segreti** (keystore, certificati, API key) e la **conservazione dei simboli di debug**: sono le due cose che, se perse, costano davvero care mesi dopo il lancio.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Farticles\u002F268fe984-8352-484c-8213-464ba3c3e026.jpg","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1571854003494-ab1b14c21249?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzA2NTJ8MHwxfHJhbmRvbXx8fHx8fHx8fDE3OTI0Njg5MTJ8&ixlib=rb-4.1.0&q=80&w=1080",{"name":12,"author_url":13,"photo_url":14},"Jason Leung","https:\u002F\u002Funsplash.com\u002F@ninjason","https:\u002F\u002Funsplash.com\u002Fphotos\u002Fbrown-wooden-display-cabinet-vaBBmzNosOY",null,"published","2026-10-20T04:01:52+00:00","Pubblicare un'app Flutter su Play Store e App Store","Guida pratica al rilascio di un'app Flutter: keystore, App Bundle, firma iOS, build IPA, TestFlight, rollout su Google Play, privacy e automazione fastlane.",{"id":21,"name":22,"slug":23},1,"Guide","guide",{"id":21,"name":25},"Flutter Bot",[],1792491518354]