Implement voice-to-text in FlutterFlow using Custom Actions with the speech_to_text package. Create two actions: startListening initializes SpeechToText, checks availability, and calls listen() with an onResult callback that updates Page State with recognized words in real-time. stopListening calls stop() to end recognition. Add a mic button that toggles between listening and idle states, and bind a Text widget to the transcription Page State variable for live display.
Adding Speech-to-Text Dictation in FlutterFlow
Voice-to-text lets users dictate instead of type, useful for search bars, note-taking, and accessibility. Since speech recognition is a background service (not a visual widget), it is implemented as Custom Actions in FlutterFlow. This tutorial covers the full setup including permissions, real-time transcription, and UI state management.
Prerequisites
- FlutterFlow Pro plan or higher (Custom Actions required)
- A FlutterFlow project open in the builder
- Microphone permissions configured in app settings
- A physical device for testing (speech recognition requires a microphone)
Step-by-step guide
Add the speech_to_text dependency and configure permissions
Add the speech_to_text dependency and configure permissions
Go to Custom Code → Pubspec Dependencies and add speech_to_text with version ^6.6.0. In your FlutterFlow project settings, ensure microphone permission is enabled for both iOS and Android. For iOS, add the NSMicrophoneUsageDescription and NSSpeechRecognitionUsageDescription permission descriptions in the iOS settings. Speech recognition requires both microphone access and speech recognition authorization on iOS.
Expected result: The speech_to_text package is added and microphone permissions are configured for both platforms.
Create the startListening Custom Action with real-time updates
Create the startListening Custom Action with real-time updates
Go to Custom Code → Custom Actions → Add. Name it startListening. Set the return type to void. Add a parameter updateTranscription of type Action (callback). In the code, create a SpeechToText instance, call initialize() to check availability, then call listen() with onResult callback that invokes the updateTranscription callback passing result.recognizedWords. Set listenFor to Duration(seconds: 30) and pauseFor to Duration(seconds: 3) for automatic stop after silence.
1import 'package:speech_to_text/speech_to_text.dart';23Future startListening(4 Future Function(String text) updateTranscription,5) async {6 final speech = SpeechToText();7 bool available = await speech.initialize(8 onError: (error) => debugPrint('STT error: $error'),9 onStatus: (status) => debugPrint('STT status: $status'),10 );11 if (!available) {12 updateTranscription('Speech recognition not available on this device');13 return;14 }15 speech.listen(16 onResult: (result) {17 updateTranscription(result.recognizedWords);18 },19 listenFor: const Duration(seconds: 30),20 pauseFor: const Duration(seconds: 3),21 listenMode: ListenMode.dictation,22 );23}Expected result: The Custom Action initializes speech recognition and starts listening with real-time word updates.
Create the stopListening Custom Action
Create the stopListening Custom Action
Create another Custom Action named stopListening. This action creates a SpeechToText instance and calls stop() to end the current listening session. Call this when the user taps the mic button again or navigates away from the page. Without explicitly stopping, the listener continues consuming battery in the background.
1import 'package:speech_to_text/speech_to_text.dart';23Future stopListening() async {4 final speech = SpeechToText();5 await speech.stop();6}Expected result: Speech recognition stops cleanly when called.
Build the voice input UI with mic button and live transcription display
Build the voice input UI with mic button and live transcription display
On your page, add Page State variables: isListening (Boolean, default false) and transcriptionText (String, default empty). Add an IconButton with a mic icon. Use Conditional Styling: if isListening is true, show mic_off icon with red color; if false, show mic icon with default color. Bind On Tap to an action flow: if not listening → set isListening true → call startListening Custom Action (pass a callback that updates transcriptionText) → else → set isListening false → call stopListening. Below the button, add a Text widget bound to transcriptionText.
Expected result: Tapping the mic button starts listening and shows recognized words in real-time below the button.
Add the transcription result to a TextField for editing
Add the transcription result to a TextField for editing
Instead of just displaying the text, bind the transcription to a TextField's initial value so users can edit the result after dictation. On the startListening callback, update both the Page State variable and the TextField's controller value. After stopping, the user sees the dictated text in an editable field and can correct any recognition errors before submitting the form. This makes voice-to-text practical for forms, search, and notes.
Expected result: Dictated text appears in an editable TextField that users can review and modify.
Complete working example
1import 'package:speech_to_text/speech_to_text.dart';2import 'package:speech_to_text/speech_recognition_result.dart';3import 'package:speech_to_text/speech_recognition_error.dart';45/// Custom Action: Start listening for speech and update transcription6/// via callback in real-time.7Future startListening(8 Future Function(String text) updateTranscription,9) async {10 final speech = SpeechToText();1112 bool available = await speech.initialize(13 onError: (SpeechRecognitionError error) {14 debugPrint('Speech recognition error: ${error.errorMsg}');15 if (error.permanent) {16 updateTranscription(17 'Speech recognition error. Please check permissions.',18 );19 }20 },21 onStatus: (String status) {22 debugPrint('Speech recognition status: $status');23 },24 );2526 if (!available) {27 await updateTranscription(28 'Speech recognition is not available on this device.',29 );30 return;31 }3233 await speech.listen(34 onResult: (SpeechRecognitionResult result) {35 updateTranscription(result.recognizedWords);36 },37 listenFor: const Duration(seconds: 30),38 pauseFor: const Duration(seconds: 3),39 listenMode: ListenMode.dictation,40 cancelOnError: false,41 partialResults: true,42 );43}4445/// Custom Action: Stop listening46Future stopListening() async {47 final speech = SpeechToText();48 await speech.stop();49}Common mistakes when creating a Voice-to-Text Feature in FlutterFlow
Why it's a problem: Not checking speech.isAvailable before calling listen()
How to avoid: Always call speech.initialize() first and check the returned boolean. Show a user-friendly message if not available.
Why it's a problem: Forgetting to stop the listener when navigating away from the page
How to avoid: Call stopListening in the page's On Page Dispose action trigger or in the dispose method of any wrapping Custom Widget.
Why it's a problem: Not requesting both microphone and speech recognition permissions on iOS
How to avoid: Add both permission descriptions in your FlutterFlow iOS settings. The speech_to_text package prompts for both on first use.
Best practices
- Always check speech.isAvailable before starting recognition to handle unsupported devices
- Show a visual indicator (red recording dot, pulsing animation) when actively listening
- Stop the speech listener on page disposal to prevent background resource consumption
- Set partialResults to true for real-time word-by-word transcription display
- Provide an editable TextField for the result so users can correct recognition errors
- Use listenMode: ListenMode.dictation for free-form text and ListenMode.search for short queries
- Request microphone and speech recognition permissions early in the app lifecycle
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
Write two FlutterFlow Custom Actions in Dart for speech-to-text using the speech_to_text package: startListening that initializes STT and updates a callback with recognized words in real-time, and stopListening that cleanly ends recognition. Include error handling and availability checks.
Add a mic icon button to my page. When tapped, it should change to a stop icon with red color. Add a text area below to display voice transcription results.
Frequently asked questions
Does speech-to-text work offline?
On iOS, speech recognition requires an internet connection by default. On Android, some languages have offline models that can be downloaded. The speech_to_text package uses the platform's built-in engine.
What languages are supported?
All languages supported by the device's speech engine. Call speech.locales() to get the list of available locales and pass the localeId to listen() for non-English recognition.
How accurate is the transcription?
Accuracy depends on the device's speech engine, background noise, and speaking clarity. Modern iOS and Android engines achieve 90-95% accuracy for clear speech in supported languages.
Can I use voice-to-text in the FlutterFlow web build?
Yes, but with limitations. Web uses the Web Speech API which is supported in Chrome and Edge but not in all browsers. It requires a secure context (HTTPS).
How do I handle continuous long-form dictation?
Set listenFor to a long duration (120 seconds) and listenMode to ListenMode.dictation. When the listener stops (after pauseFor silence), auto-restart it. Concatenate results in your Page State.
Can RapidDev help build voice-powered features?
Yes. RapidDev can implement voice commands, voice search, continuous dictation with auto-restart, and integration with external speech APIs for higher accuracy.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation