Building a Text Classifier with Murmuration and Flutter 🐦✨

Introduction

In this tutorial, we will create a text classification application using the Murmuration framework and Flutter. This application will allow users to input text and receive classifications based on sentiment, aggressiveness, and language. We will utilize the Murmuration framework's AI capabilities to analyze the text.

Prerequisites

Step 1: Setting Up Your Flutter Project

  1. Create a new Flutter project:
    flutter create murmuration_text_classifier
    cd murmuration_text_classifier
  2. Add dependencies: Open pubspec.yaml and add the following dependencies:
    dependencies:
      flutter:
        sdk: flutter
      murmuration: ^latest_version
  3. Install the dependencies:
    flutter pub get

Step 2: Building the Text Classifier Application

2.1 Main Application Entry Point

In lib/main.dart, we will set up the main entry point of our application:

import 'package:flutter/material.dart';
import 'package:murmuration/murmuration.dart';
import 'dart:convert';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(const MurmurationTextClassifier());
}

class MurmurationTextClassifier extends StatelessWidget {
  const MurmurationTextClassifier({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Murmuration Text Classifier',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: const ClassifierHome(),
    );
  }
}

2.2 Creating the Text Classifier Logic

Next, we will create the logic for handling text classification using the Murmuration framework:

class TextClassifier {
  final Murmuration _murmuration;
  static const String _threadId = 'text_classification';

  TextClassifier(String apiKey)
      : _murmuration = Murmuration(
          MurmurationConfig(
            apiKey: apiKey,
            debug: true,
            threadId: _threadId,
            maxRetries: 3,
            retryDelay: const Duration(seconds: 1),
          ),
        );

  Future> classify(String text) async {
    try {
      final agent = await _murmuration.createAgent({
        'role': '''You are a text classification expert. Analyze the given text and provide your analysis in this exact JSON format:
{
  "sentiment": ["happy", "neutral", or "sad"],
  "aggressiveness": [number between 1-5],
  "language": ["spanish", "english", "french", "german", or "italian"]
}
Important: Return ONLY the JSON object, no other text.''',
      });

      final result = await agent.execute(text);
      String cleanOutput = result.output.trim();
      if (cleanOutput.startsWith('```json')) {
        cleanOutput = cleanOutput.substring(7);
      }
      if (cleanOutput.endsWith('```')) {
        cleanOutput = cleanOutput.substring(0, cleanOutput.length - 3);
      }
      cleanOutput = cleanOutput.trim();

      final Map parsedOutput = json.decode(cleanOutput);
      if (parsedOutput['aggressiveness'] is String) {
        parsedOutput['aggressiveness'] = int.parse(parsedOutput['aggressiveness']);
      }

      return parsedOutput;
    } catch (e) {
      if (e is FormatException) {
        throw Exception('Failed to parse AI response: ${e.message}');
      }
      throw Exception('Classification failed: $e');
    }
  }

  Future clearHistory() async {
    await _murmuration.clearHistory(_threadId);
  }
}

2.3 Creating the Home Screen

Now, we will create the home screen for our text classifier application:

class ClassifierHome extends StatelessWidget {
  const ClassifierHome({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Murmuration Text Classifier'),
        backgroundColor: Theme.of(context).colorScheme.primaryContainer,
      ),
      body: const SafeArea(
        child: SingleChildScrollView(
          padding: EdgeInsets.all(16.0),
          child: ClassificationWidget(),
        ),
      ),
    );
  }
}

2.4 Building the Classification Widget

Finally, we will create the widget that handles user input and displays classification results:

class ClassificationWidget extends StatefulWidget {
  const ClassificationWidget({Key? key}) : super(key: key);

  @override
  State createState() => _ClassificationWidgetState();
}

class _ClassificationWidgetState extends State {
  final TextEditingController _textController = TextEditingController();
  final TextClassifier _classifier = TextClassifier('your-api-key');
  Map? _classification;
  bool _isLoading = false;
  String? _error;

  @override
  void dispose() {
    _textController.dispose();
    super.dispose();
  }

  Future _classifyText() async {
    if (_textController.text.isEmpty) {
      setState(() => _error = 'Please enter some text to classify');
      return;
    }

    setState(() {
      _isLoading = true;
      _error = null;
    });

    try {
      final classification = await _classifier.classify(_textController.text);
      if (mounted) {
        setState(() {
          _classification = classification;
          _isLoading = false;
        });
      }
    } catch (e) {
      if (mounted) {
        setState(() {
          _error = e.toString();
          _isLoading = false;
        });
      }
    }
  }

  Widget _buildResultCard() {
    if (_error != null) {
      return Card(
        color: Theme.of(context).colorScheme.errorContainer,
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Text(
            _error!,
            style: TextStyle(
              color: Theme.of(context).colorScheme.onErrorContainer,
            ),
          ),
        ),
      );
    }

    if (_classification == null) {
      return const SizedBox.shrink();
    }

    Color sentimentColor;
    switch (_classification!['sentiment']) {
      case 'happy':
        sentimentColor = Colors.green;
        break;
      case 'sad':
        sentimentColor = Colors.red;
        break;
      default:
        sentimentColor = Colors.blue;
    }

    return Card(
      elevation: 2,
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              'Classification Results',
              style: Theme.of(context).textTheme.titleLarge,
            ),
            const SizedBox(height: 16),
            Row(
              children: [
                Icon(Icons.emoji_emotions, color: sentimentColor),
                const SizedBox(width: 8),
                Text(
                  'Sentiment: ${_classification!['sentiment']}',
                  style: TextStyle(color: sentimentColor),
                ),
              ],
            ),
            const SizedBox(height: 16),
            Row(
              children: [
                const Icon(Icons.warning_amber),
                const SizedBox(width: 8),
                Text(
                  'Aggressiveness: ${_classification!['aggressiveness']}/5',
                ),
              ],
            ),
            const SizedBox(height: 8),
            Row(
              children: [
                const Icon(Icons.language),
                const SizedBox(width: 8),
                Text(
                  'Language: ${_classification!['language']}',
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: [
        TextField(
          controller: _textController,
          decoration: InputDecoration(
            labelText: 'Enter text to classify',
            hintText: 'Type or paste your text here...',
            border: const OutlineInputBorder(),
            suffixIcon: IconButton(
              icon: const Icon(Icons.clear),
              onPressed: () {
                _textController.clear();
                setState(() {
                  _classification = null;
                  _error = null;
                });
              },
            ),
          ),
          maxLines: 4,
        ),
        const SizedBox(height: 16),
        ElevatedButton.icon(
          onPressed: _isLoading ? null : _classifyText,
          icon: _isLoading
              ? const SizedBox(
                  width: 20,
                  height: 20,
                  child: CircularProgressIndicator(strokeWidth: 2),
                )
              : const Icon(Icons.psychology),
          label: Text(_isLoading ? 'Classifying...' : 'Classify Text'),
        ),
        const SizedBox(height: 16),
        _buildResultCard(),
      ],
    );
  }
}

Conclusion

Congratulations! You have successfully built a text classification application using the Murmuration framework and Flutter. This application allows users to input text and receive classifications based on sentiment, aggressiveness, and language.

Feel free to expand upon this example by adding more features, such as additional classification categories, user authentication, or integrating other APIs.