# How to Build a Voice Controlled User Interface in FlutterFlow

- Tool: FlutterFlow
- Difficulty: Beginner
- Time required: 20-30 min
- Compatibility: FlutterFlow Pro+ (Custom Actions require code)
- Last updated: March 2026

## TL;DR

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.

## Before you start

- 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

### 1. 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.

```
// Custom Action: startListening
import 'package:speech_to_text/speech_to_text.dart' as stt;

Future<String> startListening() async {
  final speech = stt.SpeechToText();
  bool available = await speech.initialize(
    onStatus: (status) => print('Status: $status'),
    onError: (error) => print('Error: $error'),
  );
  if (!available) return 'Speech recognition not available';

  String result = '';
  await speech.listen(
    onResult: (val) {
      if (val.finalResult) {
        result = val.recognizedWords;
      }
    },
    listenFor: const Duration(seconds: 10),
    pauseFor: const Duration(seconds: 3),
    localeId: 'en_US',
  );
  // Wait for result
  await Future.delayed(const Duration(seconds: 4));
  await speech.stop();
  return result;
}
```

**Expected result:** The Custom Action listens for speech input and returns the recognized transcript as a string.

### 2. 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.

```
// Custom Function: parseCommand
String parseCommand(String transcript) {
  final text = transcript.toLowerCase().trim();
  
  if (text.contains('home')) return 'navigate_home';
  if (text.contains('settings')) return 'navigate_settings';
  if (text.contains('profile')) return 'navigate_profile';
  if (text.contains('back')) return 'go_back';
  if (text.contains('search')) {
    final query = text.replaceAll(
      RegExp(r'.*search\s*(for)?\s*'), ''
    ).trim();
    return query.isNotEmpty ? 'search:$query' : 'search:';
  }
  return 'unknown';
}
```

**Expected result:** The parser converts natural speech like 'take me home' into structured command strings like navigate_home.

### 3. 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.

### 4. 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.

### 5. 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 code example

File: `FlutterFlow Voice Controlled UI`

```text
CUSTOM ACTION: startListening
  Initialize SpeechToText
  Check availability + request mic permission
  Listen for up to 10 seconds, 3-second silence pause
  Return final recognized transcript

CUSTOM FUNCTION: parseCommand(transcript)
  Lowercase + trim transcript
  Contains 'home' → 'navigate_home'
  Contains 'settings' → 'navigate_settings'
  Contains 'profile' → 'navigate_profile'
  Contains 'back' → 'go_back'
  Contains 'search' → 'search:{extracted_query}'
  No match → 'unknown'

PAGE STATE:
  isListening: bool (default false)
  showSuggestions: bool (default false)
  currentTranscript: String

APPBAR:
  IconButton (microphone icon)
    On tap → set isListening = true
    → startListening Custom Action
    → set isListening = false
    → parseCommand(transcript)
    → Conditional Actions based on command result:
      navigate_home → Navigate to HomePage
      navigate_settings → Navigate to SettingsPage
      navigate_profile → Navigate to ProfilePage
      go_back → Navigator Pop
      search:{query} → Set search field + trigger search
      unknown → showSuggestions = true + SnackBar

LISTENING OVERLAY (Conditional: isListening == true):
  Stack:
    Three Containers (concentric circles)
      Scale animation 1.0→1.5, loop, staggered delays
    Icon (microphone, center)
    Text (currentTranscript, updates live)

SUGGESTION CHIPS (Conditional: showSuggestions == true):
  Wrap:
    Chip 'Go Home' → execute navigate_home
    Chip 'Search' → execute search
    Chip 'Settings' → execute navigate_settings
    Chip 'Go Back' → execute go_back
    Chip 'Profile' → execute navigate_profile
  Auto-dismiss after 5 seconds
```

## Common mistakes

- **Trying to match exact spoken phrases like 'navigate home' instead of fuzzy keyword matching** — Users say things like 'take me home', 'go home', 'bring me to the home page'. Exact matching rejects most natural speech variations and frustrates users. Fix: Use contains() checks for key words (home, settings, search, back) rather than exact phrase matching. This handles natural speech variation.
- **Not showing any visual indicator while the app is listening** — Users have no way to know whether the app heard them or is still waiting for input. They repeat themselves or assume the feature is broken. Fix: Show an animated pulsing indicator and display the partial transcript in real-time so users see their words being recognized.
- **Not handling the case where speech recognition is unavailable** — Some devices do not support speech recognition, or the user denies microphone permission. The app crashes or fails silently. Fix: 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

## 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.

---

Source: https://www.rapidevelopers.com/flutterflow-tutorials/how-to-build-a-voice-controlled-user-interface-in-flutterflow
© RapidDev — https://www.rapidevelopers.com/flutterflow-tutorials/how-to-build-a-voice-controlled-user-interface-in-flutterflow
