Skip to main content

Overview

The SpeechController class provides a wrapper around Android’s SpeechRecognizer for voice input functionality. It supports real-time partial results, final transcription, and comprehensive error handling. Location: com.demodogo.ev_sum_2.services.SpeechController

Constructor

class SpeechController(
    context: Context,
    private val locale: Locale = Locale.forLanguageTag("es-CL")
)
context
Context
required
The Android application context required for creating the speech recognizer
locale
Locale
default:"Locale.forLanguageTag(\"es-CL\")"
The locale for speech recognition. Defaults to Spanish (Chile)

Properties

The controller initializes with the following configuration:
  • Language Model: LANGUAGE_MODEL_FREE_FORM for natural speech
  • Partial Results: Enabled for real-time transcription
  • Prompt: “Habla ahora…” (Speak now…)

Methods

setListener

Configures callback functions for speech recognition events.
fun setListener(
    onReady: () -> Unit,
    onPartial: (String) -> Unit,
    onFinal: (String) -> Unit,
    onError: (Int) -> Unit,
    onEnd: () -> Unit
)
onReady
() -> Unit
required
Called when the recognizer is ready to start listening
onPartial
(String) -> Unit
required
Called with partial transcription results as the user speaks. Only invoked when the partial text is not blank.
onFinal
(String) -> Unit
required
Called with the final transcription result when the user stops speaking
onError
(Int) -> Unit
required
Called when an error occurs. Receives a SpeechRecognizer error code.
onEnd
() -> Unit
required
Called when speech input ends
The onPartial callback filters out blank text automatically. You will only receive non-empty partial results.

Usage Example

val speechController = SpeechController(context)

speechController.setListener(
    onReady = {
        println("Ready to listen")
        showListeningIndicator()
    },
    onPartial = { text ->
        println("Partial: $text")
        updateTranscription(text)
    },
    onFinal = { text ->
        println("Final: $text")
        saveTranscription(text)
    },
    onError = { errorCode ->
        println("Error: $errorCode")
        handleError(errorCode)
    },
    onEnd = {
        println("Speech ended")
        hideListeningIndicator()
    }
)

start

Starts listening for speech input.
fun start()
Ensure you have set up the listener using setListener() before calling start(), otherwise you won’t receive any callbacks.

Usage Example

val speechController = SpeechController(context)
speechController.setListener(
    onReady = { /* ... */ },
    onPartial = { /* ... */ },
    onFinal = { /* ... */ },
    onError = { /* ... */ },
    onEnd = { /* ... */ }
)
speechController.start()

stop

Stops listening for speech input.
fun stop()
Calling stop() will trigger the onFinal callback with the current transcription result.

Usage Example

// Stop listening manually
speechController.stop()

destroy

Releases all resources used by the speech recognizer.
fun destroy()
Always call destroy() when you’re done with the controller to free up system resources. This should typically be done in your Activity’s or Fragment’s onDestroy() method.

Usage Example

override fun onDestroy() {
    super.onDestroy()
    speechController.destroy()
}

Error Codes

The onError callback receives one of the following error codes from SpeechRecognizer:
CodeConstantDescription
1ERROR_NETWORK_TIMEOUTNetwork operation timed out
2ERROR_NETWORKOther network related errors
3ERROR_AUDIOAudio recording error
4ERROR_SERVERServer sends error status
5ERROR_CLIENTClient side error
6ERROR_SPEECH_TIMEOUTNo speech input
7ERROR_NO_MATCHNo recognition result matched
8ERROR_RECOGNIZER_BUSYRecognitionService busy
9ERROR_INSUFFICIENT_PERMISSIONSInsufficient permissions

Handling Errors

fun handleSpeechError(errorCode: Int) {
    val message = when (errorCode) {
        SpeechRecognizer.ERROR_AUDIO -> "Error de audio"
        SpeechRecognizer.ERROR_CLIENT -> "Error del cliente"
        SpeechRecognizer.ERROR_INSUFFICIENT_PERMISSIONS -> "Permisos insuficientes"
        SpeechRecognizer.ERROR_NETWORK -> "Error de red"
        SpeechRecognizer.ERROR_NETWORK_TIMEOUT -> "Tiempo de espera de red agotado"
        SpeechRecognizer.ERROR_NO_MATCH -> "No se encontró coincidencia"
        SpeechRecognizer.ERROR_RECOGNIZER_BUSY -> "Reconocedor ocupado"
        SpeechRecognizer.ERROR_SERVER -> "Error del servidor"
        SpeechRecognizer.ERROR_SPEECH_TIMEOUT -> "No se detectó voz"
        else -> "Error desconocido"
    }
    showError(message)
}

Permission Requirements

Speech recognition requires the RECORD_AUDIO permission:
<uses-permission android:name="android.permission.RECORD_AUDIO" />

Requesting Permission

val recordAudioPermission = registerForActivityResult(
    ActivityResultContracts.RequestPermission()
) { isGranted ->
    if (isGranted) {
        speechController.start()
    } else {
        showPermissionDeniedMessage()
    }
}

recordAudioPermission.launch(Manifest.permission.RECORD_AUDIO)

Complete Usage Example

class VoiceInputActivity : AppCompatActivity() {
    private lateinit var speechController: SpeechController
    private val _transcription = MutableStateFlow("")
    val transcription: StateFlow<String> = _transcription.asStateFlow()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        speechController = SpeechController(this)
        setupSpeechRecognition()
    }

    private fun setupSpeechRecognition() {
        speechController.setListener(
            onReady = {
                showStatus("Listening...")
            },
            onPartial = { text ->
                _transcription.value = text
            },
            onFinal = { text ->
                _transcription.value = text
                saveResult(text)
            },
            onError = { errorCode ->
                handleError(errorCode)
                showStatus("Error occurred")
            },
            onEnd = {
                showStatus("Stopped")
            }
        )
    }

    fun startListening() {
        if (checkSelfPermission(Manifest.permission.RECORD_AUDIO) 
            == PackageManager.PERMISSION_GRANTED) {
            speechController.start()
        } else {
            requestPermissions(
                arrayOf(Manifest.permission.RECORD_AUDIO),
                REQUEST_RECORD_AUDIO
            )
        }
    }

    fun stopListening() {
        speechController.stop()
    }

    override fun onDestroy() {
        super.onDestroy()
        speechController.destroy()
    }

    companion object {
        private const val REQUEST_RECORD_AUDIO = 1
    }
}

Lifecycle Management

Follow these best practices for managing the speech controller lifecycle:
  1. Create the controller in onCreate() or when needed
  2. Set listener before starting recognition
  3. Start/Stop recognition as needed
  4. Destroy in onDestroy() to release resources
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    speechController = SpeechController(this)
    speechController.setListener(/* ... */)
}

override fun onDestroy() {
    super.onDestroy()
    speechController.destroy()
}

See Also

Build docs developers (and LLMs) love