[{"data":1,"prerenderedAt":27},["ShallowReactive",2],{"articolo-creare-e-pubblicare-un-package-flutter-su-pubdev-guida-completa":3,"comments-article-creare-e-pubblicare-un-package-flutter-su-pubdev-guida-completa":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},86,"Creare e pubblicare un package Flutter su pub.dev: guida completa","creare-e-pubblicare-un-package-flutter-su-pubdev-guida-completa","Dalla struttura del progetto al punteggio di pub.dev: come progettare l'API di un package Dart\u002FFlutter, documentarlo, testarlo, versionarlo secondo il semantic versioning e pubblicarlo in automatico con GitHub Actions.","## Perché pubblicare un package\n\nOgni progetto Flutter accumula prima o poi codice riutilizzabile: un client HTTP configurato, un set di widget di design system, un wrapper attorno a una API di piattaforma. Estrarre quel codice in un package porta tre vantaggi concreti:\n\n- **Riuso reale** tra app diverse (o tra i moduli di un monorepo);\n- **Confini architetturali chiari**: un package non può accedere ai dettagli interni della tua app, quindi ti obbliga a disegnare un'API;\n- **Testabilità**: la superficie pubblica è piccola e ben definita.\n\nPubblicarlo su [pub.dev](https:\u002F\u002Fpub.dev) è il passo finale, ma non è obbligatorio: puoi tenerlo privato con `path`, `git` o un *pub server* self-hosted. In questa guida vediamo tutto il percorso, con particolare attenzione alle cose che di solito si scoprono tardi: il punteggio di pub.dev, il versionamento e la pubblicazione automatizzata.\n\n## Package o plugin?\n\nLa terminologia di Dart distingue tre casi:\n\n| Tipo | Comando | Quando usarlo |\n| --- | --- | --- |\n| **Dart package** | `dart create -t package nome` | Solo codice Dart, nessuna dipendenza da Flutter (parser, client HTTP, utility) |\n| **Flutter package** | `flutter create -t package nome` | Widget, `ThemeExtension`, codice che importa `package:flutter` |\n| **Flutter plugin** | `flutter create -t plugin --platforms=android,ios nome` | Serve codice nativo (Kotlin\u002FSwift) o FFI |\n\nRegola pratica: **non dipendere da Flutter se non serve**. Un package Dart puro può essere usato anche da un backend Dart o da uno script CLI, e ottiene punti in più nella sezione \"Platform support\" di pub.dev.\n\n```bash\nflutter create --template=package --org com.esempio pretty_duration\ncd pretty_duration\n```\n\n## Struttura consigliata\n\n```\npretty_duration\u002F\n├── lib\u002F\n│   ├── pretty_duration.dart        # barrel file: unica API pubblica\n│   └── src\u002F                        # implementazione (non esportata direttamente)\n│       ├── formatter.dart\n│       └── locale_data.dart\n├── test\u002F\n│   └── formatter_test.dart\n├── example\u002F                        # app o progetto Dart di esempio\n├── CHANGELOG.md\n├── LICENSE\n├── README.md\n├── analysis_options.yaml\n└── pubspec.yaml\n```\n\nIl pattern chiave è **`lib\u002Fsrc\u002F` + barrel file**: tutto ciò che sta in `src\u002F` è considerato implementazione interna, e solo il file di ingresso decide cosa esporre.\n\n```dart\n\u002F\u002F lib\u002Fpretty_duration.dart\n\u002F\u002F\u002F Formattazione leggibile di [Duration] in più lingue.\nlibrary;\n\nexport 'src\u002Fformatter.dart' show DurationFormatter, DurationStyle;\nexport 'src\u002Flocale_data.dart' show DurationLocale;\n```\n\nUsare `show` esplicito evita di esporre per sbaglio classi interne che poi non potrai più rimuovere senza una *breaking change*.\n\n## Progettare l'API pubblica\n\nUn package si giudica dalla sua API, non dalla sua implementazione. Alcune linee guida che pagano nel lungo periodo:\n\n1. **Parametri nominati e opzionali con default**, così potrai aggiungerne altri senza rompere nulla.\n2. **Nessun singleton globale obbligatorio**: offri un'istanza di default, ma consenti di crearne altre.\n3. **Errori tipizzati**: definisci le tue eccezioni invece di lanciare `Exception('...')`.\n4. **Immutabilità** dove possibile: costruttori `const`, campi `final`, `copyWith`.\n\n```dart\n\u002F\u002F lib\u002Fsrc\u002Fformatter.dart\nimport 'locale_data.dart';\n\nenum DurationStyle { compact, verbose }\n\n\u002F\u002F\u002F Formatta una [Duration] in una stringa leggibile.\n\u002F\u002F\u002F\n\u002F\u002F\u002F Esempio:\n\u002F\u002F\u002F ```dart\n\u002F\u002F\u002F const formatter = DurationFormatter();\n\u002F\u002F\u002F formatter.format(const Duration(minutes: 95)); \u002F\u002F '1h 35m'\n\u002F\u002F\u002F ```\nclass DurationFormatter {\n  const DurationFormatter({\n    this.locale = DurationLocale.it,\n    this.style = DurationStyle.compact,\n    this.maxUnits = 2,\n  }) : assert(maxUnits > 0, 'maxUnits deve essere positivo');\n\n  final DurationLocale locale;\n  final DurationStyle style;\n  final int maxUnits;\n\n  String format(Duration duration) {\n    if (duration.isNegative) {\n      throw ArgumentError.value(duration, 'duration', 'Durata negativa');\n    }\n    final parts = \u003CString>[];\n    var remaining = duration;\n\n    for (final unit in _units) {\n      if (parts.length == maxUnits) break;\n      final value = remaining.inSeconds ~\u002F unit.seconds;\n      if (value == 0) continue;\n      parts.add(locale.label(unit, value, style));\n      remaining -= Duration(seconds: value * unit.seconds);\n    }\n\n    return parts.isEmpty ? locale.zero(style) : parts.join(' ');\n  }\n\n  DurationFormatter copyWith({\n    DurationLocale? locale,\n    DurationStyle? style,\n    int? maxUnits,\n  }) =>\n      DurationFormatter(\n        locale: locale ?? this.locale,\n        style: style ?? this.style,\n        maxUnits: maxUnits ?? this.maxUnits,\n      );\n}\n```\n\n## Documentazione: dartdoc conta davvero\n\npub.dev assegna punti in base alla percentuale di API pubbliche documentate (l'obiettivo è **almeno il 20%**, ma punta molto più in alto). Regole utili:\n\n- La prima riga del commento `\u002F\u002F\u002F` è una frase breve che finisce con un punto: è quella che appare negli elenchi.\n- Usa i riferimenti tra parentesi quadre (`[Duration]`, `[DurationFormatter.format]`): dartdoc li trasforma in link.\n- Inserisci esempi eseguibili nei commenti; con `dart doc` diventano blocchi formattati.\n- Aggiungi `dartdoc_options.yaml` se vuoi categorie personalizzate.\n\nGenera e controlla la documentazione in locale:\n\n```bash\ndart doc .\n# apre doc\u002Fapi\u002Findex.html\n```\n\nAbilita anche il lint che segnala i membri pubblici non documentati:\n\n```yaml\n# analysis_options.yaml\ninclude: package:flutter_lints\u002Fflutter.yaml\n\nlinter:\n  rules:\n    - public_member_api_docs\n    - prefer_relative_imports\n    - require_trailing_commas\n```\n\n## L'esempio non è opzionale\n\nLa cartella `example\u002F` è il secondo punto più guardato dopo il README, e pub.dev le dà un peso nel punteggio. Per un Flutter package crea una vera app:\n\n```bash\nflutter create example\n```\n\nnel cui `pubspec.yaml` referenzi il package in locale:\n\n```yaml\ndependencies:\n  pretty_duration:\n    path: ..\u002F\n```\n\nPer un package Dart puro basta un singolo `example\u002Fexample.dart` con un `main()` commentato. Tieni l'esempio **minimale e compilabile**: se non compila, chi valuta il package se ne accorge subito.\n\n## Test\n\nI test di un package sono la tua rete di sicurezza contro le regressioni segnalate dagli utenti. Per la logica pura usa `package:test`, per i widget `flutter_test`.\n\n```dart\n\u002F\u002F test\u002Fformatter_test.dart\nimport 'package:pretty_duration\u002Fpretty_duration.dart';\nimport 'package:test\u002Ftest.dart';\n\nvoid main() {\n  group('DurationFormatter', () {\n    const formatter = DurationFormatter();\n\n    test('formatta ore e minuti', () {\n      expect(formatter.format(const Duration(minutes: 95)), '1h 35m');\n    });\n\n    test('rispetta maxUnits', () {\n      final f = formatter.copyWith(maxUnits: 1);\n      expect(f.format(const Duration(hours: 2, minutes: 30)), '2h');\n    });\n\n    test('rifiuta durate negative', () {\n      expect(\n        () => formatter.format(const Duration(seconds: -1)),\n        throwsArgumentError,\n      );\n    });\n  });\n}\n```\n\nSe il package espone widget, aggiungi **golden test** per fissare l'aspetto visivo: chi lo integra si aspetta stabilità grafica tra versioni patch.\n\n## Il pubspec.yaml che fa punteggio\n\nQuesto è il file che determina buona parte della scheda su pub.dev.\n\n```yaml\nname: pretty_duration\ndescription: >-\n  Formattazione leggibile e localizzata di oggetti Duration,\n  con stili compatti o estesi e supporto multi-lingua.\nversion: 1.0.0\nrepository: https:\u002F\u002Fgithub.com\u002Futente\u002Fpretty_duration\nissue_tracker: https:\u002F\u002Fgithub.com\u002Futente\u002Fpretty_duration\u002Fissues\ndocumentation: https:\u002F\u002Fgithub.com\u002Futente\u002Fpretty_duration#readme\nfunding:\n  - https:\u002F\u002Fgithub.com\u002Fsponsors\u002Futente\n\ntopics:\n  - duration\n  - formatting\n  - i18n\n\nenvironment:\n  sdk: ^3.5.0\n\ndependencies:\n  meta: ^1.15.0\n\ndev_dependencies:\n  lints: ^5.0.0\n  test: ^1.25.0\n```\n\nAttenzione a questi dettagli:\n\n- **`description`** tra 60 e 180 caratteri: fuori da questo intervallo perdi punti.\n- **`topics`** (massimo 5, minuscolo, kebab-case) rende il package trovabile per argomento.\n- **`repository`** abilita il badge del codice sorgente e la verifica del publisher su GitHub.\n- **`screenshots`** (con `path` e `description`) è molto efficace per i package di UI.\n- **Vincoli di versione**: usa il caret (`^`) e mantieni i limiti superiori aggiornati, altrimenti i tuoi utenti restano bloccati.\n\nUn errore frequente è mettere in `dependencies` pacchetti che servono solo per lo sviluppo (linter, generatori, mock): vanno in `dev_dependencies`, perché le dipendenze di runtime vengono scaricate da tutti.\n\n## LICENSE e CHANGELOG\n\nSenza un file `LICENSE` riconosciuto (MIT, BSD-3, Apache-2.0…) perdi punti e molte aziende non potranno adottare il package. Il `CHANGELOG.md` deve avere una voce per ogni versione pubblicata, con l'intestazione esattamente uguale al numero di versione:\n\n```markdown\n## 1.1.0\n\n* Aggiunto `DurationStyle.verbose`.\n* `maxUnits` ora accetta valori fino a 4.\n\n## 1.0.0\n\n* Prima release stabile.\n```\n\n## Semantic versioning in pratica\n\nDart applica il semantic versioning in modo rigoroso, e il caret nei vincoli dipende da questo:\n\n- **PATCH** (`1.0.0 → 1.0.1`): bugfix, nessun cambiamento di API.\n- **MINOR** (`1.0.0 → 1.1.0`): nuove funzionalità **retrocompatibili**.\n- **MAJOR** (`1.0.0 → 2.0.0`): breaking change.\n\nCosa conta come breaking change in Dart, spesso sottovalutato:\n\n- rimuovere o rinominare qualsiasi membro pubblico;\n- aggiungere un parametro **richiesto** a un costruttore o metodo;\n- aggiungere un metodo astratto a una classe che gli utenti possono implementare;\n- alzare il vincolo minimo dell'SDK.\n\nPer le versioni `0.x.y` la regola cambia: è la **seconda** cifra a rappresentare le breaking change (`^0.3.0` accetta solo `>=0.3.0 \u003C0.4.0`).\n\nInvece di rimuovere subito un'API, deprecala per almeno un ciclo:\n\n```dart\n@Deprecated('Usa format() al posto di formatDuration(). Rimosso in 2.0.0.')\nString formatDuration(Duration d) => format(d);\n```\n\n## Verificare il punteggio prima di pubblicare\n\npub.dev calcola il punteggio con `pana`. Puoi eseguirlo in locale ed evitare sorprese:\n\n```bash\ndart pub global activate pana\npana .\n```\n\nIl report elenca punto per punto cosa manca: documentazione, esempio, licenza, dipendenze obsolete, supporto piattaforme, compatibilità con la null safety e con l'ultima versione dell'SDK.\n\nPoi esegui la simulazione di pubblicazione:\n\n```bash\ndart pub publish --dry-run\n```\n\nControlla l'elenco dei file inclusi: se compaiono cartelle pesanti o segreti, escludili con `.pubignore` (ha la stessa sintassi di `.gitignore` e, se presente, sostituisce `.gitignore` ai fini della pubblicazione).\n\n```\n# .pubignore\ndoc\u002F\ncoverage\u002F\nexample\u002Fbuild\u002F\n*.log\n```\n\n## Pubblicare\n\nLa prima pubblicazione si fa da terminale, con l'account Google che diventerà uploader del package:\n\n```bash\ndart pub publish\n```\n\nRicorda che **una versione pubblicata non si può cancellare**. Se ti accorgi di un problema entro 7 giorni puoi usare *retract* dalla dashboard di pub.dev: la versione resta risolvibile per chi l'ha già nel lockfile, ma non viene più selezionata per le nuove installazioni.\n\nSubito dopo, valuta di creare un **publisher verificato** (basato su un dominio che controlli): dà molta più credibilità rispetto a un account personale.\n\n## Pubblicazione automatica con GitHub Actions\n\npub.dev supporta la pubblicazione automatizzata via OIDC, senza credenziali da conservare nei secret. La configurazione ha due passaggi.\n\n**1. Su pub.dev**, nella pagina Admin del package, abilita \"Automated publishing\" indicando il repository GitHub e il pattern dei tag (per esempio `v{{version}}`).\n\n**2. Nel repository**, crea il workflow:\n\n```yaml\n# .github\u002Fworkflows\u002Fpublish.yml\nname: Publish to pub.dev\n\non:\n  push:\n    tags:\n      - 'v[0-9]+.[0-9]+.[0-9]+*'\n\njobs:\n  publish:\n    permissions:\n      id-token: write   # necessario per l'autenticazione OIDC\n    uses: dart-lang\u002Fsetup-dart\u002F.github\u002Fworkflows\u002Fpublish.yml@v1\n```\n\nDa quel momento il rilascio è: aggiorni `version` nel pubspec, aggiungi la voce nel CHANGELOG, fai il commit, crei il tag `v1.1.0` e lo pushi. Il workflow esegue `dart pub publish` per te.\n\nAffianca sempre un workflow di verifica su ogni pull request:\n\n```yaml\nname: CI\non: [push, pull_request]\n\njobs:\n  analyze-and-test:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@v4\n      - uses: subosito\u002Fflutter-action@v2\n        with:\n          channel: stable\n      - run: flutter pub get\n      - run: dart format --output=none --set-exit-if-changed .\n      - run: flutter analyze --fatal-infos\n      - run: flutter test\n```\n\n## Alternative alla pubblicazione pubblica\n\nNon tutto deve finire su pub.dev. Per codice aziendale hai tre strade:\n\n```yaml\ndependencies:\n  # 1. Repository Git privato (con SSH configurato in CI)\n  design_system:\n    git:\n      url: git@github.com:azienda\u002Fdesign_system.git\n      ref: v2.3.0\n      path: packages\u002Fdesign_system\n\n  # 2. Percorso locale, tipico dei monorepo\n  core_models:\n    path: ..\u002Fcore_models\n\n  # 3. Server pub privato\n  billing_client:\n    hosted: https:\u002F\u002Fpub.azienda.it\n    version: ^1.2.0\n```\n\nIn un package destinato a restare privato, aggiungi anche questa riga al pubspec per evitare pubblicazioni accidentali:\n\n```yaml\npublish_to: none\n```\n\n## Checklist finale\n\nPrima di premere invio su `dart pub publish`:\n\n- [ ] `README.md` con installazione, esempio d'uso e screenshot;\n- [ ] `CHANGELOG.md` aggiornato con la versione esatta;\n- [ ] `LICENSE` presente e riconosciuto;\n- [ ] `example\u002F` compilabile;\n- [ ] `description` tra 60 e 180 caratteri e `topics` compilati;\n- [ ] `repository` e `issue_tracker` valorizzati;\n- [ ] `dart format`, `flutter analyze` e `flutter test` verdi;\n- [ ] `pana .` senza penalità gravi;\n- [ ] `dart pub publish --dry-run` senza warning;\n- [ ] versione coerente con il semantic versioning.\n\nUn package ben confezionato non è solo cortesia verso la community: è codice che il tuo team futuro riuscirà ad aggiornare senza doverlo riscrivere. E il lavoro di documentazione e test che fai per pubblicarlo ripaga anche quando il package resta dentro il tuo monorepo.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Farticles\u002F76b335b8-1f84-4c97-805b-f883fe909b77.jpg","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1763568258179-fa561d623323?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzA2NTJ8MHwxfHJhbmRvbXx8fHx8fHx8fDE3ODg0MDgxMDh8&ixlib=rb-4.1.0&q=80&w=1080",{"name":12,"author_url":13,"photo_url":14},"Daniil Komov","https:\u002F\u002Funsplash.com\u002F@dkomow","https:\u002F\u002Funsplash.com\u002Fphotos\u002Fcomputer-screen-displaying-code-and-data-pBHloi8vrnw",null,"published","2026-09-03T04:01:49+00:00","Pubblicare un package Flutter su pub.dev: guida","Come creare, documentare, testare, versionare e pubblicare un package Dart o Flutter su pub.dev, con punteggio pana e release automatica via GitHub Actions.",{"id":21,"name":22,"slug":23},1,"Guide","guide",{"id":21,"name":25},"Flutter Bot",[],1789205509641]