widget_chat is live on pub.dev — drop-in AI chat for Flutter, FlutterFlow, React & Web. Start free →

← Back to Blog
Flutter speech_to_text Stops After 5 Seconds? The Real Fix

Flutter speech_to_text Stops After 5 Seconds? The Real Fix

flutterflutterflowspeech-to-textvoice-assistantandroid

Flutter speech_to_text Stops After 5 Seconds? The Real Fix

You wired up the speech_to_text plugin, tapped the mic, said a sentence — and the moment you paused to think, listening died. On Android it happens within roughly five seconds of silence, no matter what you pass to pauseFor. Your code isn't broken, and neither is the plugin. If you're building a voice assistant, though, you're about to fight the operating system itself — so let's look at what's actually happening, what the restart-loop workaround can and can't do, and what a genuinely continuous voice conversation in Flutter looks like.

Why speech_to_text stops listening on Android

The speech_to_text package (v7.4.0 at the time of writing) is a thin bridge to each platform's native recognizer — on Android, that's android.speech.SpeechRecognizer, which delegates to whatever recognition service the device ships (usually Google's). The package README is unusually honest about the consequence:

"The target use cases for this library are commands and short phrases, not continuous spoken conversion or always on listening."

and, on the Android silence timeout specifically:

"Android speech recognition has a very short timeout when the speaker pauses. The duration of the timeout varies by device and version of the Android OS. In the devices I've used none have had a pause longer than 5 seconds. Unfortunately there appears to be no way to change that behaviour."

That's the whole story of the flutter speech_to_text stops listening android search you just did. pauseFor is a request, not a guarantee: on Android the OS endpointer decides when speech has ended, and it will cut the session earlier than your pauseFor value whenever it feels like it. Under the hood, RecognizerIntent even documents extras like EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS — but they're hints, and the stock recognition service largely ignores them. When the timeout fires you'll see the status flip to done (often accompanied by an error_no_match or error_speech_timeout in your error callback), and the mic is closed.

So: it's an OS-level limitation. No plugin flag fixes it, which is why every android speech recognition timeout flutter thread ends the same way — with a restart loop.

The restart-loop workaround (and where it leaks)

Since you can't stop Android from ending the session, the standard workaround for speech_to_text continuous listening in Flutter is to detect the end and immediately start a new session. GitHub issues #253 and #619 both walk this road. A version that avoids the classic error_busy trap looks like this:

import 'dart:async';
import 'package:speech_to_text/speech_to_text.dart';

class ContinuousListener {
  final SpeechToText _speech = SpeechToText();
  bool _keepListening = false;

  Future<void> start(void Function(String) onWords) async {
    final ok = await _speech.initialize(
      onStatus: (status) {
        // Android fires 'done' when its silence timeout kills the session.
        if (status == 'done' && _keepListening) _restart(onWords);
      },
      onError: (e) {
        // 'error_no_match' / 'error_speech_timeout' just mean silence — keep going.
        if (_keepListening && !e.permanent) _restart(onWords);
      },
    );
    if (!ok) return;
    _keepListening = true;
    _listen(onWords);
  }

  void _listen(void Function(String) onWords) {
    _speech.listen(
      onResult: (r) => onWords(r.recognizedWords),
      pauseFor: const Duration(seconds: 10), // a hint — Android may cut earlier
      listenFor: const Duration(seconds: 60),
      listenOptions: SpeechListenOptions(
        partialResults: true,
        cancelOnError: false,
      ),
    );
  }

  Future<void> _restart(void Function(String) onWords) async {
    await _speech.stop();
    // Restarting instantly usually throws 'error_busy' (see issue #253).
    await Future.delayed(const Duration(milliseconds: 300));
    if (_keepListening) _listen(onWords);
  }

  Future<void> stop() async {
    _keepListening = false;
    await _speech.stop();
  }
}

This is the honest ceiling of the flutter speech to text restart listening approach — and it leaks in ways no amount of tuning fixes:

  • Dead air at every boundary. The recognizer takes real time to tear down and spin up. Words spoken during that ~300–800 ms gap are simply lost, and transcripts fragment across sessions (#619).
  • The beep. Many Android devices play system earcons on every recognition start/stop. In a restart loop your app chirps at the user every few seconds unless you mute system audio, with its own side effects.
  • error_busy races. Restart too fast and Android rejects the new session; too slow and you lose more speech. The right delay varies by device and Google app version.
  • It's only half a voice assistant. You still need TTS for replies, turn detection, and barge-in. And if you bolt on flutter_tts, the mic happily transcribes your own assistant's voice back to you (#591) — now you're writing echo-suppression logic too.

For its intended use — short commands, one-shot dictation — the restart loop is fine. If your flutter voice assistant stops after pause problem exists because you're building an actual conversation, it's the wrong foundation.

The honest fix: a real-time voice call, not a dictation loop

What you're really trying to build is speech-to-speech: the user talks, an AI answers out loud, the user can interrupt. That's a streaming, full-duplex audio session — not something SpeechRecognizer's command-oriented API was ever meant to hold open.

This is exactly what WidgetChat's live voice does inside the same AI support chatbot widget you embed in a Flutter or FlutterFlow app. The user taps the mic and gets a real-time voice call with your assistant:

  • It keeps listening. No 5-second silence cliff, no restart loop, no lost words at session boundaries — the session is continuous for its whole duration.
  • Speech-to-speech. It listens and replies out loud in a natural voice, so you don't wire up a separate STT and TTS pipeline.
  • Barge-in. The user can interrupt mid-reply and the assistant stops and listens — the failure mode you'd otherwise hand-build around the TTS-echo problem.
  • Live captions during the call, and the assistant can show rich product cards on screen while it speaks.
  • Same widget, same conversation, same dashboard as text chat, across iOS, Android, and web Flutter apps.
  • Provider API keys stay server-side — nothing sensitive ships inside your APK for someone to extract.

Voice is plan-gated by a monthly voice-minute pool, and you configure it per project in the dashboard's Voice section: enable/disable, voice name, max session length, and whether captions default on.

Wiring WidgetChat into your Flutter app

WidgetChat doesn't require a proprietary SDK — the chat side is a plain HTTP integration, which is also why it drops into FlutterFlow as a custom action. Text replies stream token-by-token over Server-Sent Events from POST https://api.widgetchat.app/v1/chat/stream:

import 'dart:convert';
import 'package:http/http.dart' as http;

Future<void> streamReply(String userText, void Function(String) onToken) async {
  final client = http.Client();
  final request = http.Request(
    'POST',
    Uri.parse('https://api.widgetchat.app/v1/chat/stream'),
  )
    ..headers['Content-Type'] = 'application/json'
    ..body = jsonEncode({'message': userText}); // plus your project fields from the dashboard

  final response = await client.send(request);
  await response.stream
      .transform(utf8.decoder)
      .transform(const LineSplitter())
      .where((line) => line.startsWith('data: '))
      .forEach((line) => onToken(line.substring(6)));
  client.close();
}

That gets you streaming text chat. For voice, there's nothing extra to code on the client: once voice is enabled in your project's Voice section, the embedded widget shows the mic, and tapping it starts the live speech-to-speech call — listening, barge-in, captions, and spoken replies all handled inside the widget, with the conversation landing in the same dashboard as your text chats.

Stop fighting the timeout

The Android silence timeout isn't going away, and the restart loop will always drop words at the seams. Keep speech_to_text for what it's good at — short commands — and give your users an assistant they can actually talk to.

Try WidgetChat free — embed the widget in your Flutter or FlutterFlow app, flip on live voice in the dashboard, and have a continuous voice conversation running today.

Author

About the author

Widget Chat is a team of developers and designers passionate about creating the best AI chatbot experience for Flutter, web, and mobile apps.

Comments

Comments are coming soon. We'd love to hear your thoughts!