flutter_mind
Any AI. One interface.
Why flutter_mind?
Most AI packages for Flutter just wrap the API โ you still have to write the prompts, handle errors, manage tokens, and figure out streaming yourself.
flutter_mind does more:
- ๐ One API for all providers โ switch from Gemini to Claude in one line
- ๐ฌ Multi-turn chat โ conversation history with automatic token trimming
- โก Streaming โ typing-effect UI out of the box
- ๐ง Thinking models โ built-in support for reasoning budgets
- ๐ก๏ธ Safe by default โ input validation, retry logic, and clear error messages
- ๐ฏ Zero Firebase required โ just an API key
Supported Providers
| Provider | Status | Models |
|---|---|---|
| Google Gemini | โ v1 | Flash 2.5, Pro 2.5, Flash-Lite, and more |
| OpenAI | ๐ v2 | GPT-4o, GPT-4o Mini |
| Anthropic Claude | ๐ v2 | Sonnet, Opus, Haiku |
| Ollama (local) | ๐ v2 | Llama, Mistral, DeepSeek |
| Grok | ๐ v2 | โ |
| DeepSeek | ๐ v2 | โ |
Installation
dependencies:
flutter_mind: ^0.0.1
flutter pub get
Quick Start
import 'package:flutter_mind/flutter_mind.dart';
void main() {
FlutterMind.init(
engine: GeminiEngine(apiKey: 'YOUR_GEMINI_API_KEY'),
);
runApp(MyApp());
}
// Anywhere in your app โ no imports, no passing around
final response = await FlutterMind.send(userMessage: 'suggest a game');
print(response.text);
Three lines in main(). Done.
Getting Your API Key
Google Gemini โ Free tier available
- Go to aistudio.google.com/apikey
- Sign in with your Google account
- Click Create API Key โ no credit card required
OpenAI (coming in v2)
- Go to platform.openai.com โ API Keys โ Create new secret key
Anthropic Claude (coming in v2)
- Go to console.anthropic.com โ API Keys โ Create Key
Ollama โ Free, runs locally (coming in v2)
- Download from ollama.com, then run
ollama pull llama3.2โ no API key needed
Usage
Send a message
final response = await FlutterMind.send(userMessage: 'what is Flutter?');
print(response.text); // the response text
print(response.totalTokens); // total tokens used
print(response.inputTokens); // tokens in your message
print(response.outputTokens); // tokens in the response
Streaming โ typing effect UI
FlutterMind.stream(userMessage: 'tell me a story').listen((chunk) {
setState(() => text += chunk); // text appears word by word
});
Multi-turn chat โ conversation with memory
final history = <ChatMessage>[];
// First turn
final r1 = await FlutterMind.send(
userMessage: 'my name is Osama',
history: history,
);
history.add(ChatMessage.user('my name is Osama'));
history.add(ChatMessage.model(r1.text));
// Second turn โ model remembers the name
final r2 = await FlutterMind.send(
userMessage: 'what is my name?',
history: history,
maxHistoryMessages: 20, // oldest turns are dropped automatically
);
print(r2.text); // "Your name is Osama"
Engine configuration
Set your defaults once โ every call uses them automatically:
FlutterMind.init(
engine: GeminiEngine(
apiKey: 'YOUR_KEY',
config: GeminiConfig(
model: GeminiModel.flash25,
systemPrompt: 'You are a game suggestion assistant. Keep responses short.',
temperature: 0.8,
maxOutputTokens: 500,
),
),
);
Per-call config override
Override only what changes for a single call โ defaults stay untouched:
// Uses your default config
await FlutterMind.send(userMessage: 'suggest a game');
// Overrides just for this one call
await FlutterMind.send(
userMessage: 'solve this complex math problem',
config: GeminiConfig(
model: GeminiModel.pro25,
temperature: 0.1,
thinkingLevel: ThinkingLevel.deep,
),
);
Thinking models
Let the model reason before answering โ better results on hard problems:
GeminiConfig(
model: GeminiModel.pro25,
thinkingLevel: ThinkingLevel.moderate,
)
// Or set an exact token budget
GeminiConfig(
model: GeminiModel.pro25,
thinkingLevel: CustomThinkingBudget(tokens: 4000),
)
| Level | Tokens | Best For |
|---|---|---|
ThinkingLevel.none |
0 | Fastest, cheapest |
ThinkingLevel.light |
512 | Simple reasoning |
ThinkingLevel.moderate |
2,048 | Coding, math |
ThinkingLevel.deep |
8,192 | Complex problems |
ThinkingLevel.max |
24,576 | Hardest problems |
Access the model's reasoning in the response:
final response = await FlutterMind.send(
userMessage: 'explain quantum entanglement simply',
config: GeminiConfig(
model: GeminiModel.pro25,
thinkingLevel: ThinkingLevel.moderate,
),
);
print(response.text); // the answer
print(response.thinkingText); // how it got there (null if not a thinking model)
print(response.hasThinking); // true / false
Structured JSON output
Force the model to always return valid, parseable JSON:
GeminiConfig(
model: GeminiModel.flash25,
responseMimeType: 'application/json',
responseSchema: {
'type': 'object',
'properties': {
'name': {'type': 'string'},
'genre': {'type': 'string'},
'rating': {'type': 'number'},
},
'required': ['name', 'genre', 'rating'],
},
)
beforeSend hook โ inject runtime context
Enrich every message with user profile, location, or app state before it reaches the AI:
FlutterMind.init(
engine: GeminiEngine(apiKey: 'YOUR_KEY'),
beforeSend: (message) async {
final user = await UserService.getProfile();
final location = await LocationService.current();
return 'User: ${user.name}, Location: $location\n\n$message';
},
);
// User types: "what restaurants are near me?"
// Model receives: "User: Osama, Location: Cairo, Egypt\n\nwhat restaurants are near me?"
Token management
// Accurate count โ calls the API, always free
final tokens = await FlutterMind.countTokens(userMessage: longText);
if (tokens > 100000) print('Message too long');
// Rough estimate โ instant, no API call
// Note: Arabic text uses 2โ3ร more tokens than English
final estimate = FlutterMind.estimateTokens(message);
Retry configuration
GeminiEngine(
apiKey: 'YOUR_KEY',
// Default โ 2 attempts on 429, 500, 503
retry: RetryConfig(),
// Custom
retry: RetryConfig(
maxAttempts: 5,
delay: Duration(seconds: 2),
retryOn: {429, 503},
),
// Disable
retry: RetryConfig.none,
)
Availability check
if (!await FlutterMind.isAvailable()) {
showDialog(context, 'AI is currently unavailable. Try again later.');
return;
}
Multiple engines in one app
Use FlutterMindClient directly when you need more than one engine:
final chatClient = FlutterMindClient(
engine: GeminiEngine(
apiKey: 'YOUR_KEY',
config: GeminiConfig(
model: GeminiModel.flash25,
systemPrompt: 'You are a friendly chat assistant.',
),
),
);
final summaryClient = FlutterMindClient(
engine: GeminiEngine(
apiKey: 'YOUR_KEY',
config: GeminiConfig(
model: GeminiModel.pro25,
systemPrompt: 'You summarize documents concisely.',
temperature: 0.1,
),
),
);
await chatClient.send(userMessage: 'hello');
await summaryClient.send(userMessage: longDocument);
Gemini Models
| Constant | Model ID | Status | Best For |
|---|---|---|---|
GeminiModel.flash25 |
gemini-2.5-flash | โ Stable | General use โ recommended default |
GeminiModel.flash25Lite |
gemini-2.5-flash-lite | โ Stable | High volume, lowest cost |
GeminiModel.pro25 |
gemini-2.5-pro | โ Stable | Complex reasoning, analysis |
GeminiModel.flash3Preview |
gemini-3-flash-preview | โ ๏ธ Preview | Frontier performance |
GeminiModel.flash31Lite |
gemini-3.1-flash-lite | โ Stable | Fast, affordable, Gemini 3 |
GeminiModel.pro31Preview |
gemini-3.1-pro-preview | โ ๏ธ Preview | Most powerful available |
Use CustomModel for any model not listed:
GeminiConfig(model: CustomModel('gemini-4.0-ultra'))
Error Handling
try {
final response = await FlutterMind.send(userMessage: message);
print(response.text);
} on ValidationException catch (e) {
// Bad input โ empty message or exceeds 50,000 characters
print(e.message);
} on EngineException catch (e) {
// API error โ invalid key, rate limit, network issue
print(e.message);
print(e.statusCode); // 401, 429, 500 ...
} on FlutterMindException catch (e) {
// Any other flutter_mind error
print(e.message);
}
Common status codes
| Code | Meaning | Fix |
|---|---|---|
| 400 | Bad request or invalid API key | Check your key at aistudio.google.com/apikey |
| 401 | Unauthorized | API key rejected |
| 403 | No permission | Key may not have access to this model |
| 404 | Model not found | Check model name or use CustomModel |
| 429 | Rate limit | Add RetryConfig or upgrade your API plan |
| 500 | Server error | Temporary โ try again |
API Key Security
Never hardcode API keys in production apps. Anyone can extract them from your APK or IPA.
// During development โ environment variable
GeminiEngine(
apiKey: const String.fromEnvironment('GEMINI_KEY'),
)
// In production โ proxy through your own backend
// Flutter app โ Your server โ Gemini API
// The key never leaves your server
Use flutter_dotenv for local .env files.
Roadmap
v1 โ Current
xGoogle Gemini enginexSend and streamingxMulti-turn conversation historyxThinking model support (ThinkingLevel presets + custom budget)xStructured JSON outputxToken management (accurate + estimate)xRetry configurationxInput validationxbeforeSend hook
v2 โ Coming Soon
OpenAI engineAnthropic Claude engineOllama engine (local models โ no API key, no cost)Prompt presets (AiPreset.gameSuggestion, etc.)Response parser (JSON โ typed Dart objects)flutter_mind_vision (image generation)flutter_mind_audio (TTS, STT)
Contributing
Contributions are welcome. To contribute:
- Fork the repository
- Create a feature branch:
git checkout -b feature/your-feature - Commit your changes with a clear message
- Push and open a Pull Request
License
MIT โ see LICENSE for details.
Built by Mohamed Osama ยท Egypt ๐ช๐ฌ