Build a voice-controlled interface using a Custom Action with the speech_to_text package that listens for spoken commands, parses keywords to map them to app actions like navigation and search, and provides visual feedback during listening. A command map routes keywords to specific actions (navigate home, search for X, open settings, go back). Animated waveform and pulsing indicators show listening state. Fallback suggestion chips appear when a command is not recognized so users learn the available voice vocabulary.
Adding Voice Commands to Your FlutterFlow App
Voice control makes apps accessible and hands-free. This tutorial integrates the speech_to_text package to listen for spoken commands, maps keywords to navigation and search actions, shows animated visual feedback while listening, and gracefully handles unrecognized commands with suggestion chips.
Prerequisites
- FlutterFlow project with multiple pages to navigate between
- Microphone permission configured in your app settings
- Basic understanding of Custom Actions in FlutterFlow
- speech_to_text package added to pubspec dependencies
Step-by-step guide
Set up the speech_to_text Custom Action for voice input
Set up the speech_to_text Custom Action for voice input
Create a Custom Action called startListening that initializes the SpeechToText instance, checks availability, and starts listening. The action takes an Action Parameter callback onResult that returns the recognized transcript string. On initialization, call speech.initialize() to request microphone permission. When the user activates listening, call speech.listen() with a localeId matching your app's language. The onResult callback fires with the final recognized text when the user stops speaking or after a silence timeout.
1// Custom Action: startListening2import 'package:speech_to_text/speech_to_text.dart' as stt;34Future<String> startListening() async {5 final speech = stt.SpeechToText();6 bool available = await speech.initialize(7 onStatus: (status) => print('Status: $status'),8 onError: (error) => print('Error: $error'),9 );10 if (!available) return 'Speech recognition not available';1112 String result = '';13 await speech.listen(14 onResult: (val) {15 if (val.finalResult) {16 result = val.recognizedWords;17 }18 },19 listenFor: const Duration(seconds: 10),20 pauseFor: const Duration(seconds: 3),21 localeId: 'en_US',22 );23 // Wait for result24 await Future.delayed(const Duration(seconds: 4));25 await speech.stop();26 return result;27}Expected result: The Custom Action listens for speech input and returns the recognized transcript as a string.
Build the command parser to map keywords to actions
Build the command parser to map keywords to actions
Create a Custom Function called parseCommand that takes the transcript string and returns a command name. Use contains() checks on the lowercased transcript for fuzzy matching: if it contains 'home' return 'navigate_home', if it contains 'search' extract the query text after the keyword and return 'search:query', if it contains 'settings' return 'navigate_settings', if it contains 'back' return 'go_back'. If no keyword matches, return 'unknown'. This approach handles natural variations like 'take me home' or 'go to settings' instead of requiring exact phrases.
1// Custom Function: parseCommand2String parseCommand(String transcript) {3 final text = transcript.toLowerCase().trim();4 5 if (text.contains('home')) return 'navigate_home';6 if (text.contains('settings')) return 'navigate_settings';7 if (text.contains('profile')) return 'navigate_profile';8 if (text.contains('back')) return 'go_back';9 if (text.contains('search')) {10 final query = text.replaceAll(11 RegExp(r'.*search\s*(for)?\s*'), ''12 ).trim();13 return query.isNotEmpty ? 'search:$query' : 'search:';14 }15 return 'unknown';16}Expected result: The parser converts natural speech like 'take me home' into structured command strings like navigate_home.
Execute commands with an Action Flow dispatcher
Execute commands with an Action Flow dispatcher
In the page where voice control is active, after the startListening action returns a transcript, pass it to parseCommand. Then use Conditional Actions to execute the result: if command equals 'navigate_home', use Navigate To action to the HomePage. If command equals 'navigate_settings', navigate to SettingsPage. If command starts with 'search:', extract the query and set the search TextField value plus trigger the search. If command equals 'go_back', use Navigator Pop. If command equals 'unknown', update a Page State variable showSuggestions to true and display a SnackBar with the message 'Command not recognized'.
Expected result: Voice commands trigger real app actions like navigation, search, and back navigation.
Add visual feedback with a pulsing listening indicator
Add visual feedback with a pulsing listening indicator
Add a Stack at the center of the screen (or overlay via a global Stack) with a Container that is visible only when Page State isListening equals true. Inside the Container, add three concentric circles with scale animations (use FlutterFlow's built-in animations: scale from 1.0 to 1.5, infinite loop, different delays per circle to create a ripple effect). Add a microphone Icon at the center. Below the circles, add a Text widget showing 'Listening...' that updates to show the partial transcript as the user speaks. This gives clear visual feedback that the app is actively processing voice input.
Expected result: An animated pulsing indicator appears while the app is listening for voice commands.
Show suggestion chips when a command is not recognized
Show suggestion chips when a command is not recognized
When the parseCommand function returns 'unknown', display a Wrap widget with tappable chips showing available commands: 'Go Home', 'Search', 'Open Settings', 'Go Back', 'Open Profile'. Each chip is a Container with rounded corners and an InkWell. Tapping a chip executes that command directly (same as if the user had spoken it). Use Conditional Visibility on the Wrap so it appears only when showSuggestions is true. Add a dismiss timer or close button so the suggestions disappear after 5 seconds or on the next voice activation.
Expected result: Unrecognized voice commands show tappable suggestion chips that teach users the available voice vocabulary.
Complete working example
1CUSTOM ACTION: startListening2 Initialize SpeechToText3 Check availability + request mic permission4 Listen for up to 10 seconds, 3-second silence pause5 Return final recognized transcript67CUSTOM FUNCTION: parseCommand(transcript)8 Lowercase + trim transcript9 Contains 'home' → 'navigate_home'10 Contains 'settings' → 'navigate_settings'11 Contains 'profile' → 'navigate_profile'12 Contains 'back' → 'go_back'13 Contains 'search' → 'search:{extracted_query}'14 No match → 'unknown'1516PAGE STATE:17 isListening: bool (default false)18 showSuggestions: bool (default false)19 currentTranscript: String2021APPBAR:22 IconButton (microphone icon)23 On tap → set isListening = true24 → startListening Custom Action25 → set isListening = false26 → parseCommand(transcript)27 → Conditional Actions based on command result:28 navigate_home → Navigate to HomePage29 navigate_settings → Navigate to SettingsPage30 navigate_profile → Navigate to ProfilePage31 go_back → Navigator Pop32 search:{query} → Set search field + trigger search33 unknown → showSuggestions = true + SnackBar3435LISTENING OVERLAY (Conditional: isListening == true):36 Stack:37 Three Containers (concentric circles)38 Scale animation 1.0→1.5, loop, staggered delays39 Icon (microphone, center)40 Text (currentTranscript, updates live)4142SUGGESTION CHIPS (Conditional: showSuggestions == true):43 Wrap:44 Chip 'Go Home' → execute navigate_home45 Chip 'Search' → execute search46 Chip 'Settings' → execute navigate_settings47 Chip 'Go Back' → execute go_back48 Chip 'Profile' → execute navigate_profile49 Auto-dismiss after 5 secondsCommon mistakes when building a Voice Controlled User Interface in FlutterFlow
Why it's a problem: Trying to match exact spoken phrases like 'navigate home' instead of fuzzy keyword matching
How to avoid: Use contains() checks for key words (home, settings, search, back) rather than exact phrase matching. This handles natural speech variation.
Why it's a problem: Not showing any visual indicator while the app is listening
How to avoid: Show an animated pulsing indicator and display the partial transcript in real-time so users see their words being recognized.
Why it's a problem: Not handling the case where speech recognition is unavailable
How to avoid: Check speech.initialize() return value. If false, show a SnackBar explaining that speech recognition is not available on this device and hide the microphone button.
Best practices
- Use fuzzy keyword matching with contains() instead of exact phrase matching
- Show a pulsing visual indicator and live transcript while listening
- Display suggestion chips for unrecognized commands to teach the vocabulary
- Keep the command vocabulary simple and discoverable (5-10 commands maximum)
- Add a microphone button in the AppBar for consistent access across pages
- Set a reasonable listening timeout (10 seconds) to avoid indefinite listening
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
Build a voice-controlled interface for a FlutterFlow app using speech_to_text. I need a Custom Action for listening, a command parser that maps keywords to navigation actions (home, settings, search, back), visual feedback with a pulsing listening indicator, and fallback suggestion chips when a command is not recognized. Include the Dart code for the listener and parser.
Add a microphone IconButton to the AppBar. When tapped, show a centered overlay with three concentric circles animating with scale pulse and a microphone icon in the center. Below the circles show a Text widget that updates with the recognized speech.
Frequently asked questions
Which languages does speech_to_text support?
The speech_to_text package supports all languages available on the device's speech engine. Pass the localeId parameter (e.g., 'en_US', 'es_ES', 'fr_FR') to specify the language. Call speech.locales() to get the list of available locales on the current device.
Can I add custom commands for my specific app?
Yes. Extend the parseCommand function with additional contains() checks for your domain-specific keywords. For example, add 'cart' for e-commerce or 'appointments' for scheduling apps.
Does voice control work on both iOS and Android?
Yes. The speech_to_text package supports both platforms. iOS requires the NSMicrophoneUsageDescription key in Info.plist. Android requires RECORD_AUDIO permission in the manifest.
How do I handle noisy environments?
Set a confidence threshold. The SpeechRecognitionResult includes a confidence score between 0 and 1. Only act on results above 0.7 confidence. Below that, show the transcript and ask the user to confirm.
Can I keep the app always listening for a wake word?
Continuous listening drains battery significantly. Instead, use a tap-to-speak pattern with the microphone button. For always-on mode, consider a lightweight wake word detector that only activates full speech recognition after the trigger word.
Can RapidDev help build voice-enabled apps?
Yes. RapidDev can integrate speech recognition, natural language processing, and voice-controlled navigation into FlutterFlow apps with custom command vocabularies and multi-language support.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation