diff --git a/hooks/useSpeechRecognition.ts b/hooks/useSpeechRecognition.ts index a84501f..c14f985 100644 --- a/hooks/useSpeechRecognition.ts +++ b/hooks/useSpeechRecognition.ts @@ -4,6 +4,10 @@ * Wraps @jamsch/expo-speech-recognition for easy use in components. * Provides start/stop controls, recognized text, and status states. * + * NOTE: Gracefully handles missing native module (Expo Go) + * - In Expo Go: isAvailable = false, all methods are no-ops + * - In Dev Build: Full functionality + * * Usage: * ```typescript * const { startListening, stopListening, isListening, recognizedText, error } = useSpeechRecognition(); @@ -19,12 +23,20 @@ */ import { useState, useCallback, useRef, useEffect } from 'react'; -import { - ExpoSpeechRecognitionModule, - useSpeechRecognitionEvent, -} from '@jamsch/expo-speech-recognition'; import { Platform } from 'react-native'; +// Try to import the native module - may fail in Expo Go +let ExpoSpeechRecognitionModule: any = null; +let useSpeechRecognitionEvent: any = () => {}; // no-op by default + +try { + const speechRecognition = require('@jamsch/expo-speech-recognition'); + ExpoSpeechRecognitionModule = speechRecognition.ExpoSpeechRecognitionModule; + useSpeechRecognitionEvent = speechRecognition.useSpeechRecognitionEvent; +} catch (e) { + console.warn('[SpeechRecognition] Native module not available (Expo Go?). Speech recognition disabled.'); +} + export interface UseSpeechRecognitionOptions { /** Language for recognition (default: 'en-US') */ lang?: string; @@ -83,7 +95,7 @@ export function useSpeechRecognition( } = options; const [isListening, setIsListening] = useState(false); - const [isAvailable, setIsAvailable] = useState(true); + const [isAvailable, setIsAvailable] = useState(!!ExpoSpeechRecognitionModule); const [recognizedText, setRecognizedText] = useState(''); const [partialTranscript, setPartialTranscript] = useState(''); const [error, setError] = useState(null); @@ -95,6 +107,11 @@ export function useSpeechRecognition( // Check availability on mount useEffect(() => { + if (!ExpoSpeechRecognitionModule) { + setIsAvailable(false); + return; + } + const checkAvailability = async () => { try { // Check if we can get permissions (indirect availability check) @@ -131,7 +148,7 @@ export function useSpeechRecognition( }); // Event: Result available - useSpeechRecognitionEvent('result', (event) => { + useSpeechRecognitionEvent('result', (event: any) => { const results = event.results; if (results && results.length > 0) { const result = results[results.length - 1]; @@ -159,7 +176,7 @@ export function useSpeechRecognition( }); // Event: Error occurred - useSpeechRecognitionEvent('error', (event) => { + useSpeechRecognitionEvent('error', (event: any) => { const errorMessage = event.message || event.error || 'Speech recognition error'; console.error('[SpeechRecognition] Error:', errorMessage); @@ -178,6 +195,11 @@ export function useSpeechRecognition( * @returns true if started successfully, false otherwise */ const startListening = useCallback(async (): Promise => { + if (!ExpoSpeechRecognitionModule) { + console.warn('[SpeechRecognition] Cannot start - native module not available'); + return false; + } + if (isListening || isStartingRef.current) { console.log('[SpeechRecognition] Already listening or starting'); return false; @@ -239,6 +261,8 @@ export function useSpeechRecognition( * Stop listening and process final result */ const stopListening = useCallback(() => { + if (!ExpoSpeechRecognitionModule) return; + if (!isListening && !isStartingRef.current) { console.log('[SpeechRecognition] Not listening, nothing to stop'); return; @@ -256,6 +280,8 @@ export function useSpeechRecognition( * Abort listening without processing */ const abortListening = useCallback(() => { + if (!ExpoSpeechRecognitionModule) return; + if (!isListening && !isStartingRef.current) { return; }