Introduzione

Il riconoscimento vocale (speech-to-text) permette agli utenti di interagire con le app parlando invece di digitare. È utile per barre di ricerca vocali, note dettate, funzioni di accessibilità o assistenti conversazionali. In Flutter il pacchetto speech_to_text sfrutta le API native di Android e iOS per offrire una trascrizione affidabile e in tempo reale.

In questo articolo vedremo come integrarlo passo dopo passo, gestendo permessi, stati e risultati parziali.

Installazione

Aggiungiamo la dipendenza al pubspec.yaml:

dependencies:
  speech_to_text: ^7.0.0
  permission_handler: ^11.3.1

Configurazione Android

Nel file android/app/src/main/AndroidManifest.xml aggiungiamo i permessi e la query per il servizio di riconoscimento:

<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />

<queries>
    <intent>
        <action android:name="android.speech.RecognitionService" />
    </intent>
</queries>

Assicurati inoltre di avere minSdkVersion almeno a 21.

Configurazione iOS

Nel file ios/Runner/Info.plist aggiungiamo le descrizioni per l'uso del microfono e del riconoscimento vocale:

<key>NSMicrophoneUsageDescription</key>
<string>L'app usa il microfono per il riconoscimento vocale</string>
<key>NSSpeechRecognitionUsageDescription</key>
<string>L'app converte la tua voce in testo</string>

Inizializzazione

Il primo passo è creare un'istanza di SpeechToText e inizializzarla. Il metodo initialize restituisce true se il riconoscimento è disponibile sul dispositivo:

import 'package:speech_to_text/speech_to_text.dart';

class SpeechService {
  final SpeechToText _speech = SpeechToText();
  bool _available = false;

  Future<bool> init() async {
    _available = await _speech.initialize(
      onStatus: (status) => print('Stato: $status'),
      onError: (error) => print('Errore: ${error.errorMsg}'),
    );
    return _available;
  }
}

Gli stati principali passati a onStatus sono listening, notListening e done. Sono utili per aggiornare l'interfaccia (ad esempio mostrando un'animazione mentre l'app ascolta).

Avviare l'ascolto

Una volta inizializzato il servizio, avviamo l'ascolto con listen. Il callback onResult riceve un oggetto SpeechRecognitionResult con la trascrizione:

Future<void> startListening(void Function(String) onText) async {
  if (!_speech.isAvailable) return;

  await _speech.listen(
    onResult: (result) {
      onText(result.recognizedWords);
    },
    localeId: 'it_IT',
    listenFor: const Duration(seconds: 30),
    pauseFor: const Duration(seconds: 3),
    listenOptions: SpeechListenOptions(
      partialResults: true,
      cancelOnError: true,
    ),
  );
}

void stopListening() => _speech.stop();

Alcuni parametri chiave:

  • localeId: la lingua da riconoscere. Usa _speech.locales() per ottenere l'elenco delle lingue supportate dal dispositivo.
  • listenFor: durata massima dell'ascolto.
  • pauseFor: tempo di silenzio dopo cui l'ascolto si interrompe automaticamente.
  • partialResults: se true, onResult viene invocato man mano che l'utente parla, permettendo la trascrizione in tempo reale.

Il campo result.finalResult indica se la trascrizione è definitiva o ancora parziale.

Esempio completo di widget

Ecco un widget che integra il riconoscimento vocale con gestione dei permessi tramite permission_handler:

import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:speech_to_text/speech_to_text.dart';

class VoiceInputScreen extends StatefulWidget {
  const VoiceInputScreen({super.key});

  @override
  State<VoiceInputScreen> createState() => _VoiceInputScreenState();
}

class _VoiceInputScreenState extends State<VoiceInputScreen> {
  final SpeechToText _speech = SpeechToText();
  bool _isListening = false;
  String _text = 'Premi il microfono e parla';

  Future<void> _toggleListening() async {
    if (_isListening) {
      await _speech.stop();
      setState(() => _isListening = false);
      return;
    }

    final status = await Permission.microphone.request();
    if (!status.isGranted) return;

    final available = await _speech.initialize(
      onStatus: (s) {
        if (s == 'done' || s == 'notListening') {
          setState(() => _isListening = false);
        }
      },
      onError: (e) => setState(() => _isListening = false),
    );

    if (available) {
      setState(() => _isListening = true);
      await _speech.listen(
        localeId: 'it_IT',
        onResult: (r) => setState(() => _text = r.recognizedWords),
        listenOptions: SpeechListenOptions(partialResults: true),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Speech to Text')),
      body: Padding(
        padding: const EdgeInsets.all(24),
        child: Center(
          child: Text(_text, style: const TextStyle(fontSize: 22)),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _toggleListening,
        backgroundColor: _isListening ? Colors.red : null,
        child: Icon(_isListening ? Icons.mic : Icons.mic_none),
      ),
    );
  }
}

Buone pratiche

  • Verifica sempre la disponibilità: non tutti i dispositivi (specialmente emulatori) supportano il riconoscimento vocale. Controlla il valore di ritorno di initialize.
  • Gestisci i permessi in modo esplicito: chiedi il permesso microfono prima di avviare l'ascolto e gestisci il caso in cui l'utente lo neghi.
  • Fornisci feedback visivo: mostra all'utente quando l'app sta ascoltando, ad esempio con un'icona o un'animazione.
  • Attenzione al consumo: interrompi l'ascolto quando non serve per risparmiare batteria e rispettare la privacy.
  • Considera l'offline: su Android alcune lingue richiedono la connessione, mentre iOS può usare il riconoscimento on-device. Non dare per scontata la disponibilità offline.

Conclusione

Con speech_to_text è possibile integrare rapidamente il riconoscimento vocale in un'app Flutter, offrendo un'esperienza più naturale e accessibile. Combinandolo con permission_handler per la gestione dei permessi e con un feedback visivo curato, si ottiene una funzionalità robusta e cross-platform. Un ottimo complemento è il text-to-speech, per creare interfacce conversazionali complete.