flutter_voice_commands 0.0.1 copy "flutter_voice_commands: ^0.0.1" to clipboard
flutter_voice_commands: ^0.0.1 copied to clipboard

Voice command recognition and processing for Flutter applications

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter_voice_commands/flutter_voice_commands.dart';

void main() {
  runApp(const VoiceCommandsExampleApp());
}

class VoiceCommandsExampleApp extends StatelessWidget {
  const VoiceCommandsExampleApp({super.key});

  @override
  Widget build(BuildContext context) => MaterialApp(
        title: 'Flutter Voice Commands Example',
        theme: ThemeData(
          primarySwatch: Colors.blue,
          useMaterial3: true,
        ),
        home: const VoiceCommandsExample(),
      );
}

class VoiceCommandsExample extends StatefulWidget {
  const VoiceCommandsExample({super.key});

  @override
  State<VoiceCommandsExample> createState() => _VoiceCommandsExampleState();
}

class _VoiceCommandsExampleState extends State<VoiceCommandsExample> {
  final FlutterVoiceCommands _voiceCommands = FlutterVoiceCommands();
  bool _isListening = false;
  bool _isInitialized = false;
  String _recognizedText = '';
  String _lastCommand = '';
  final List<String> _commandHistory = [];

  @override
  void initState() {
    super.initState();
    _initializeVoiceCommands();
    _setupCommands();
  }

  Future<void> _initializeVoiceCommands() async {
    try {
      await _voiceCommands.initialize();
      setState(() => _isInitialized = true);
    } catch (e) {
      _showError('Initialization failed: $e');
    }
  }

  void _setupCommands() {
    final commands = [
      VoiceCommand(
        pattern: 'open (\\w+)',
        action: (matches) => _executeCommand('open', matches[1]),
        description: 'Open an application or file',
        priority: 3,
      ),
      VoiceCommand(
        pattern: 'close (\\w+)',
        action: (matches) => _executeCommand('close', matches[1]),
        description: 'Close an application or file',
        priority: 3,
      ),
      VoiceCommand(
        pattern: 'search for (\\w+)',
        action: (matches) => _executeCommand('search', matches[1]),
        description: 'Search for something',
        priority: 2,
      ),
      VoiceCommand(
        pattern: 'play (\\w+)',
        action: (matches) => _executeCommand('play', matches[1]),
        description: 'Play media or start something',
        priority: 2,
      ),
      VoiceCommand(
        pattern: 'stop',
        action: (matches) => _executeCommand('stop', ''),
        description: 'Stop current action',
        priority: 1,
      ),
      VoiceCommand(
        pattern: 'help',
        action: (matches) => _showHelp(),
        description: 'Show available commands',
        priority: 1,
      ),
    ];

    _voiceCommands.registerCommands(commands);
  }

  void _executeCommand(String command, String target) {
    final commandText = target.isNotEmpty ? '$command $target' : command;
    setState(() {
      _lastCommand = commandText;
      _commandHistory.insert(0, commandText);
      if (_commandHistory.length > 10) {
        _commandHistory.removeLast();
      }
    });

    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text('Executed: $commandText'),
        duration: const Duration(seconds: 2),
      ),
    );
  }

  void _showHelp() {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('Available Voice Commands'),
        content: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: _voiceCommands.commands.map((cmd) {
            return Padding(
              padding: const EdgeInsets.symmetric(vertical: 4),
              child: Text(
                '• "${cmd.pattern}" - ${cmd.description ?? "No description"}',
                style: const TextStyle(fontSize: 14),
              ),
            );
          }).toList(),
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.of(context).pop(),
            child: const Text('OK'),
          ),
        ],
      ),
    );
  }

  Future<void> _startListening() async {
    if (!_isInitialized) {
      _showError('Voice recognition not initialized');
      return;
    }

    if (await _voiceCommands.hasPermission()) {
      setState(() => _isListening = true);

      try {
        await _voiceCommands.listenWithCommands(
          onCommand: (command, matches) {
            command.execute(matches);
          },
          onUnknown: (text) {
            setState(() => _recognizedText = text);
          },
        );
      } catch (e) {
        _showError('Error starting voice recognition: $e');
        setState(() => _isListening = false);
      }
    } else {
      final granted = await _voiceCommands.requestPermission();
      if (granted) {
        _startListening();
      } else {
        _showError('Microphone permission denied');
      }
    }
  }

  Future<void> _stopListening() async {
    await _voiceCommands.stop();
    setState(() => _isListening = false);
  }

  void _showError(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(message),
        backgroundColor: Colors.red,
        duration: const Duration(seconds: 3),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Voice Commands Example'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        actions: [
          IconButton(
            icon: const Icon(Icons.help),
            onPressed: _showHelp,
            tooltip: 'Show available commands',
          ),
        ],
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // Status Card
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'Status',
                      style: Theme.of(context).textTheme.titleLarge,
                    ),
                    const SizedBox(height: 8),
                    Row(
                      children: [
                        Icon(
                          _isInitialized ? Icons.check_circle : Icons.error,
                          color: _isInitialized ? Colors.green : Colors.red,
                        ),
                        const SizedBox(width: 8),
                        Text('Initialized: ${_isInitialized ? "Yes" : "No"}'),
                      ],
                    ),
                    const SizedBox(height: 4),
                    Row(
                      children: [
                        Icon(
                          _isListening ? Icons.mic : Icons.mic_off,
                          color: _isListening ? Colors.green : Colors.grey,
                        ),
                        const SizedBox(width: 8),
                        Text('Listening: ${_isListening ? "Yes" : "No"}'),
                      ],
                    ),
                  ],
                ),
              ),
            ),

            const SizedBox(height: 16),

            // Control Buttons
            Row(
              children: [
                Expanded(
                  child: ElevatedButton.icon(
                    onPressed: _isListening ? _stopListening : _startListening,
                    icon: Icon(_isListening ? Icons.stop : Icons.mic),
                    label: Text(
                        _isListening ? 'Stop Listening' : 'Start Listening'),
                    style: ElevatedButton.styleFrom(
                      backgroundColor: _isListening ? Colors.red : Colors.blue,
                      foregroundColor: Colors.white,
                      padding: const EdgeInsets.symmetric(vertical: 16),
                    ),
                  ),
                ),
              ],
            ),

            const SizedBox(height: 16),

            // Recognized Text Card
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'Recognized Text',
                      style: Theme.of(context).textTheme.titleLarge,
                    ),
                    const SizedBox(height: 8),
                    Container(
                      width: double.infinity,
                      padding: const EdgeInsets.all(12),
                      decoration: BoxDecoration(
                        border: Border.all(color: Colors.grey.shade300),
                        borderRadius: BorderRadius.circular(8),
                        color: Colors.grey.shade50,
                      ),
                      child: Text(
                        _recognizedText.isEmpty
                            ? 'No text recognized yet'
                            : _recognizedText,
                        style: const TextStyle(fontSize: 16),
                      ),
                    ),
                  ],
                ),
              ),
            ),

            const SizedBox(height: 16),

            // Last Command Card
            if (_lastCommand.isNotEmpty) ...[
              Card(
                child: Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        'Last Command',
                        style: Theme.of(context).textTheme.titleLarge,
                      ),
                      const SizedBox(height: 8),
                      Container(
                        width: double.infinity,
                        padding: const EdgeInsets.all(12),
                        decoration: BoxDecoration(
                          border: Border.all(color: Colors.green.shade300),
                          borderRadius: BorderRadius.circular(8),
                          color: Colors.green.shade50,
                        ),
                        child: Text(
                          _lastCommand,
                          style: const TextStyle(
                            fontSize: 16,
                            fontWeight: FontWeight.bold,
                            color: Colors.green,
                          ),
                        ),
                      ),
                    ],
                  ),
                ),
              ),
              const SizedBox(height: 16),
            ],

            // Command History Card
            if (_commandHistory.isNotEmpty) ...[
              Expanded(
                child: Card(
                  child: Padding(
                    padding: const EdgeInsets.all(16.0),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(
                          'Command History',
                          style: Theme.of(context).textTheme.titleLarge,
                        ),
                        const SizedBox(height: 8),
                        Expanded(
                          child: ListView.builder(
                            itemCount: _commandHistory.length,
                            itemBuilder: (context, index) {
                              return ListTile(
                                leading: const Icon(Icons.history),
                                title: Text(_commandHistory[index]),
                                dense: true,
                              );
                            },
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ),
            ],
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    _voiceCommands.dispose();
    super.dispose();
  }
}
3
likes
160
points
116
downloads

Publisher

verified publisherbechattaoui.dev

Weekly Downloads

Voice command recognition and processing for Flutter applications

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter, permission_handler, speech_to_text

More

Packages that depend on flutter_voice_commands