[{"data":1,"prerenderedAt":27},["ShallowReactive",2],{"articolo-autofill-e-otp-in-flutter-compilare-automaticamente-credenziali-e-codici-sms":3,"comments-article-autofill-e-otp-in-flutter-compilare-automaticamente-credenziali-e-codici-sms":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},129,"Autofill e OTP in Flutter: compilare automaticamente credenziali e codici SMS","autofill-e-otp-in-flutter-compilare-automaticamente-credenziali-e-codici-sms","Login più rapidi e meno abbandoni: come usare AutofillGroup e autofillHints per password e credenziali, far salvare i dati ai password manager e leggere i codici OTP via SMS su Android e iOS.","## Perché l'autofill non è un dettaglio\n\nLa schermata di login è il punto in cui si perde il maggior numero di utenti. Ogni carattere digitato a mano è attrito: una password lunga, un codice OTP da leggere nella notifica, il passaggio avanti e indietro tra app e SMS. Sia Android sia iOS offrono da anni meccanismi di compilazione automatica (password manager di sistema, SMS Retriever, suggerimento del codice sulla tastiera) e Flutter li espone con API semplici, che però restano poco usate perché richiedono qualche accortezza in più rispetto a un normale `TextField`.\n\nIn questa guida vediamo come:\n\n- marcare correttamente i campi con `autofillHints` e raggrupparli con `AutofillGroup`;\n- far comparire il prompt \"Vuoi salvare la password?\" dopo un login riuscito;\n- configurare Android e iOS perché il sistema riconosca la nostra app;\n- leggere automaticamente un codice OTP arrivato via SMS.\n\n## Le basi: autofillHints e AutofillGroup\n\nIl primo passo è dichiarare la **semantica** di ogni campo. Il sistema operativo non indovina: se non gli diciamo che quel campo è uno username, non proporrà nulla.\n\n```dart\nTextField(\n  controller: _emailController,\n  keyboardType: TextInputType.emailAddress,\n  textInputAction: TextInputAction.next,\n  autofillHints: const [AutofillHints.username, AutofillHints.email],\n)\n```\n\nLa classe `AutofillHints` contiene decine di costanti (`username`, `password`, `newPassword`, `oneTimeCode`, `telephoneNumber`, `postalCode`, `creditCardNumber`, `name`, `streetAddressLine1`…). Ogni piattaforma mappa questi hint sulle proprie costanti native; non tutte sono supportate ovunque, ma gli hint sconosciuti vengono semplicemente ignorati.\n\nIl secondo passo è il raggruppamento. Un password manager salva una **coppia** credenziali: perché funzioni, username e password devono vivere nello stesso `AutofillGroup`.\n\n```dart\nclass LoginForm extends StatefulWidget {\n  const LoginForm({super.key});\n\n  @override\n  State\u003CLoginForm> createState() => _LoginFormState();\n}\n\nclass _LoginFormState extends State\u003CLoginForm> {\n  final _email = TextEditingController();\n  final _password = TextEditingController();\n\n  @override\n  void dispose() {\n    _email.dispose();\n    _password.dispose();\n    super.dispose();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return AutofillGroup(\n      child: Column(\n        mainAxisSize: MainAxisSize.min,\n        children: [\n          TextField(\n            controller: _email,\n            keyboardType: TextInputType.emailAddress,\n            textInputAction: TextInputAction.next,\n            autofillHints: const [AutofillHints.username],\n            decoration: const InputDecoration(labelText: 'Email'),\n          ),\n          const SizedBox(height: 12),\n          TextField(\n            controller: _password,\n            obscureText: true,\n            textInputAction: TextInputAction.done,\n            autofillHints: const [AutofillHints.password],\n            onSubmitted: (_) => _login(),\n            decoration: const InputDecoration(labelText: 'Password'),\n          ),\n          const SizedBox(height: 24),\n          FilledButton(onPressed: _login, child: const Text('Accedi')),\n        ],\n      ),\n    );\n  }\n}\n```\n\n> In fase di registrazione usa `AutofillHints.newPassword` invece di `password`: è il segnale che fa proporre al portachiavi una password forte generata automaticamente.\n\n## Far salvare le credenziali: finishAutofillContext\n\nDichiarare gli hint serve a **leggere** i dati. Per **scriverli** (cioè per ottenere il prompt \"Salva password\") bisogna dire esplicitamente al sistema che il contesto di autofill è concluso, tipicamente quando il login va a buon fine:\n\n```dart\nimport 'package:flutter\u002Fservices.dart';\n\nFuture\u003Cvoid> _login() async {\n  final result = await authRepository.signIn(_email.text, _password.text);\n\n  if (result.isSuccess) {\n    \u002F\u002F Chiude il contesto e chiede al sistema di salvare i dati inseriti.\n    TextInput.finishAutofillContext();\n    if (mounted) context.go('\u002Fhome');\n  } else {\n    \u002F\u002F Credenziali errate: non salvare nulla.\n    TextInput.finishAutofillContext(shouldSave: false);\n    _showError(result.error);\n  }\n}\n```\n\nIl parametro `shouldSave: false` è importante: evita che il password manager memorizzi una password sbagliata, errore classico che poi costringe l'utente a correggere la voce salvata.\n\nSe l'utente abbandona la schermata senza completare il login, puoi controllare il comportamento con `onDisposeAction`:\n\n```dart\nAutofillGroup(\n  onDisposeAction: AutofillContextAction.cancel, \u002F\u002F default: commit\n  child: ...,\n)\n```\n\n### Login a più step\n\nSe la tua UI chiede prima l'email e poi, in una seconda schermata, la password (pattern \"identifier first\"), i due campi non possono stare nello stesso `AutofillGroup`. In quel caso l'autofill funziona comunque in lettura, ma il salvataggio della coppia è meno affidabile: valuta un unico step con entrambi i campi, oppure mantieni la password nella stessa route usando un `PageView` interno — i widget restano nello stesso albero e quindi nello stesso gruppo.\n\n## Configurazione per piattaforma\n\n### iOS\n\nSu iOS il Password AutoFill funziona già con i soli hint, ma per far proporre **le credenziali del sito web corrispondente** devi collegare app e dominio:\n\n1. aggiungi la capability *Associated Domains* in Xcode con `webcredentials:tuodominio.it`;\n2. pubblica il file `apple-app-site-association` su `https:\u002F\u002Ftuodominio.it\u002F.well-known\u002Fapple-app-site-association` includendo la sezione `webcredentials` con il tuo `TEAMID.bundle.id`.\n\nÈ la stessa infrastruttura dei Universal Links: se l'hai già configurata per il deep linking, ti serve solo aggiungere la chiave `webcredentials`.\n\n### Android\n\nL'Autofill Framework è disponibile da API 26 e funziona automaticamente con gli hint. Per l'associazione con il sito web (Google Password Manager) serve il file **Digital Asset Links** su `https:\u002F\u002Ftuodominio.it\u002F.well-known\u002Fassetlinks.json`, con la relazione `delegate_permission\u002Fcommon.get_login_creds` oltre a quella dei deep link.\n\nAttenzione a un dettaglio pratico: alcuni servizi di autofill di terze parti mostrano il pannello solo se l'app non blocca gli screenshot (`FLAG_SECURE`). Se usi flag di sicurezza sulla schermata di login, testala con il password manager reale.\n\n### Web e desktop\n\nSu Flutter Web gli `autofillHints` vengono mappati sull'attributo `autocomplete` degli input HTML, quindi il browser propone le credenziali salvate. Il supporto è però parziale e dipende dal renderer: verificalo sempre sui browser target. Su desktop, al momento, l'autofill di sistema non è supportato.\n\n## OTP via SMS: le tre strade\n\nL'altro grande punto di attrito è il codice di verifica. Qui le piattaforme si comportano in modo molto diverso.\n\n### iOS: basta un hint\n\nSu iOS non serve alcun SDK: se il campo dichiara `AutofillHints.oneTimeCode`, il sistema analizza l'SMS in arrivo e propone il codice sopra la tastiera (QuickType).\n\n```dart\nTextField(\n  controller: _otp,\n  keyboardType: TextInputType.number,\n  autofillHints: const [AutofillHints.oneTimeCode],\n)\n```\n\nPerché funzioni bene, il messaggio deve contenere una formula riconoscibile, ad esempio: `Il tuo codice di verifica è 123456`.\n\n### Android: SMS Retriever o User Consent\n\nAndroid offre due API dei Google Play Services:\n\n- **SMS Retriever API**: nessun permesso, lettura totalmente automatica, ma il messaggio deve iniziare con `\u003C#>` e terminare con un hash di 11 caratteri che identifica la tua app (dipende da package name e certificato di firma).\n- **SMS User Consent API**: nessun hash necessario, ma l'utente deve confermare con un dialog di sistema il permesso a leggere quel singolo messaggio.\n\nUn SMS compatibile con SMS Retriever ha questa forma:\n\n```\n\u003C#> Il tuo codice Acme è 123456\nFA+9qCX9VSu\n```\n\nL'hash va generato una volta per ogni variante di firma (debug, release, Play App Signing) e passato al backend che invia gli SMS.\n\n### Implementazione con Pinput e smart_auth\n\nIl pacchetto [`pinput`](https:\u002F\u002Fpub.dev\u002Fpackages\u002Fpinput) fornisce la UI a caselle per l'OTP e integra nativamente l'ascolto degli SMS su Android tramite `smart_auth`:\n\n```dart\nPinput(\n  length: 6,\n  controller: _otpController,\n  keyboardType: TextInputType.number,\n  \u002F\u002F iOS: suggerimento sulla tastiera\n  \u002F\u002F Android: ascolto automatico dell'SMS\n  androidSmsAutofillMethod: AndroidSmsAutofillMethod.smsRetrieverApi,\n  onCompleted: (code) => _verify(code),\n  defaultPinTheme: PinTheme(\n    width: 56,\n    height: 56,\n    textStyle: Theme.of(context).textTheme.titleLarge,\n    decoration: BoxDecoration(\n      border: Border.all(color: Theme.of(context).colorScheme.outline),\n      borderRadius: BorderRadius.circular(12),\n    ),\n  ),\n)\n```\n\nSe preferisci gestire tutto a mano, [`smart_auth`](https:\u002F\u002Fpub.dev\u002Fpackages\u002Fsmart_auth) espone le stesse funzionalità, compreso il recupero dell'app signature da comunicare al backend:\n\n```dart\nfinal smartAuth = SmartAuth();\n\n\u002F\u002F Da stampare in debug e configurare lato server.\nfinal signature = await smartAuth.getAppSignature();\ndebugPrint('App signature: $signature');\n\n\u002F\u002F Avvia l'ascolto prima di richiedere l'invio dell'SMS.\nfinal res = await smartAuth.getSmsCode(useUserConsentApi: false);\nif (res.succeed && res.code != null) {\n  _otpController.text = res.code!;\n  _verify(res.code!);\n}\n\n\u002F\u002F Quando esci dalla schermata\nawait smartAuth.removeSmsListener();\n```\n\nL'API di `smart_auth` è cambiata tra la versione 1.x e la 2.x: controlla il changelog del pacchetto prima di copiare il codice. L'ordine delle operazioni, invece, resta sempre lo stesso: **avvia l'ascolto, poi chiedi al backend di inviare l'SMS**. Se inverti i due passaggi, un SMS veloce può arrivare prima che il listener sia attivo.\n\n## Errori frequenti\n\n- **Hint su un solo campo.** Senza il campo password nello stesso gruppo, molti password manager non propongono il salvataggio.\n- **Dimenticare `finishAutofillContext()`.** L'autofill in lettura funziona, ma nessuna credenziale viene mai memorizzata: l'utente dovrà digitare per sempre.\n- **Salvare password errate.** Chiama sempre `finishAutofillContext(shouldSave: false)` nel ramo di errore.\n- **Hash SMS sbagliato.** L'hash della build di debug è diverso da quello della release firmata con Play App Signing: servono entrambi lato backend, altrimenti l'autofill funziona in locale e non in produzione.\n- **Campo OTP ricostruito.** Se il widget dell'OTP viene smontato e rimontato (ad esempio da un `setState` che cambia la struttura dell'albero), il listener SMS può perdersi: gestisci lo stato di caricamento senza sostituire il widget.\n- **Test solo su emulatore.** L'autofill dipende dal password manager installato: provalo su dispositivi reali, con Google Password Manager, iCloud Keychain e almeno un manager di terze parti.\n\n## Oltre le password: le passkey\n\nIl passo successivo logico è eliminare del tutto la password. Le **passkey** (WebAuthn\u002FFIDO2) sono supportate su Android tramite Credential Manager e su iOS tramite il portachiavi, e richiedono la stessa configurazione di associated domains \u002F asset links vista sopra. In Flutter si usano con pacchetti come `credential_manager` o i plugin dei provider di identità (Firebase Auth, Supabase, Auth0). Se hai già collegato app e dominio per l'autofill, hai fatto gran parte del lavoro necessario anche per le passkey.\n\n## Conclusione\n\nL'autofill è una di quelle funzionalità a basso costo e alto impatto: poche righe di codice, un po' di configurazione lato piattaforma e un login che passa da trenta secondi a due tap. Comincia dagli `autofillHints` e da `AutofillGroup`, aggiungi `finishAutofillContext()` nel flusso di successo, poi occupati dell'OTP. E verifica sempre su dispositivi reali: è l'unico modo per sapere se il password manager dell'utente sta davvero collaborando.\n","https:\u002F\u002Fflutter.it\u002Fstorage\u002Farticles\u002F4a175ae0-64c6-465b-bee6-f49ab239ee73.jpg","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1642480504730-3ba4bbfaa0ec?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzA2NTJ8MHwxfHJhbmRvbXx8fHx8fHx8fDE3OTIxMjMzMDl8&ixlib=rb-4.1.0&q=80&w=1080",{"name":12,"author_url":13,"photo_url":14},"Mourizal Zativa","https:\u002F\u002Funsplash.com\u002F@mourimoto","https:\u002F\u002Funsplash.com\u002Fphotos\u002Fa-person-holding-a-cell-phone-in-their-hand-90JFNMyBeek",null,"published","2026-10-16T04:01:49+00:00","Autofill e OTP in Flutter: credenziali e codici SMS","Guida pratica all'autofill in Flutter: AutofillGroup, autofillHints, salvataggio password e lettura automatica dei codici OTP via SMS su Android e iOS.",{"id":21,"name":22,"slug":23},1,"Guide","guide",{"id":21,"name":25},"Flutter Bot",[],1792316897946]