Flutter Enterprise AI App: Cross-Platform Mobile RAG Architecture
TL;DR — Flutter is ideal for enterprise AI apps: single codebase (iOS, Android, web, desktop), native performance (AOT),
StreamBuilderfor streaming LLM responses,flutter_secure_storagefor encrypted tokens, WebSocket for real-time chat, and offline-first with SQLite caching. Architecture: Flutter frontend (chat UI, streaming, offline cache) + FastAPI backend (RAG orchestration, pgvector retrieval, FalkorDB graph, Ollama LLM). On-device LLM via llamafu (llama.cpp FFI) for 3-8B models — fully offline, zero API cost, complete privacy. Hybrid: on-device for simple queries, self-hosted for complex. Flutter AI Toolkit provides ready-made chat widgets. Offline RAG via flutter_rag (pure Dart: PDF/CSV loaders, chunking, vector search). Auth: JWT in secure storage. State: Riverpod. Deploy backend with Docker Compose. Enterprise features: document-level ACLs, citation tagging, hallucination prevention, audit logging. Works in VPNs, behind firewalls, air-gapped.
Flutter's single codebase reaches iOS, Android, web, and desktop with native performance. For enterprise AI, this means one team builds the mobile AI experience for all platforms. The StreamBuilder widget handles streaming LLM responses naturally — tokens appear as they generate, just like ChatGPT.
Enterprise RAG chatbots built with Flutter have demonstrated: seamless integration into existing apps, offline capability with cached Q&A, voice input for field employees, role-based access control, and 60fps animations on all devices.
Architecture
StreamBuilder"] Cache["SQLite Cache
(offline Q&A)"] Secure["Secure Storage
(JWT tokens)"] Voice["Voice Input
(speech-to-text)"] end subgraph Network["Communication"] WS["WebSocket
(real-time chat)"] REST["REST API
(document search)"] end subgraph Backend["Self-Hosted Backend"] API["FastAPI
(RAG orchestration)"] PG["PostgreSQL
+ pgvector"] Falkor["FalkorDB
(knowledge graph)"] Ollama["Ollama
(LLM inference)"] end subgraph OnDevice["On-Device AI (optional)"] llamafu["llamafu
(llama.cpp FFI)"] LocalModel["Llama 3 8B
(offline)"] end UI --> WS UI --> REST WS --> API REST --> API API --> PG API --> Falkor API --> Ollama UI -.->|"offline"| Cache UI -.->|"offline"| llamafu llamafu --> LocalModel Secure -.->|"auth"| WS Voice -.->|"input"| UI
Flutter Chat UI with Streaming
class AIChatScreen extends StatefulWidget {
@override
State<AIChatScreen> createState() => _AIChatScreenState();
}
class _AIChatScreenState extends State<AIChatScreen> {
final TextEditingController _controller = TextEditingController();
final List<ChatMessage> _messages = [];
final AIService _aiService = AIService();
void _sendMessage() async {
final query = _controller.text;
_controller.clear();
setState(() {
_messages.add(ChatMessage(role: 'user', content: query));
_messages.add(ChatMessage(role: 'assistant', content: ''));
});
// Stream tokens from backend
final stream = _aiService.streamChat(query);
stream.listen((token) {
setState(() {
_messages.last.content += token;
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('AI Assistant')),
body: Column(
children: [
Expanded(
child: ListView.builder(
itemCount: _messages.length,
itemBuilder: (context, index) {
final msg = _messages[index];
return ChatBubble(message: msg);
},
),
),
Padding(
padding: EdgeInsets.all(8),
child: Row(
children: [
Expanded(
child: TextField(
controller: _controller,
decoration: InputDecoration(
hintText: 'Ask a question...',
border: OutlineInputBorder(),
),
onSubmitted: (_) => _sendMessage(),
),
),
IconButton(
icon: Icon(Icons.send),
onPressed: _sendMessage,
),
],
),
),
],
),
);
}
}
Streaming Service with WebSocket
class AIService {
final String baseUrl = 'wss://api.internal.company.com/ws';
WebSocketChannel? _channel;
Stream<String> streamChat(String query) async* {
_channel = WebSocketChannel.connect(Uri.parse(baseUrl));
// Send query with auth token
final token = await SecureStorage.getToken();
_channel!.sink.add(jsonEncode({
'query': query,
'token': token,
'stream': true,
}));
// Receive streamed tokens
await for (final message in _channel!.stream) {
final data = jsonDecode(message);
if (data['type'] == 'token') {
yield data['content'] as String;
} else if (data['type'] == 'sources') {
// Handle citation sources
yield '\n\nSources: ${data['sources'].join(', ')}';
} else if (data['type'] == 'done') {
_channel!.sink.close();
return;
}
}
}
}
Offline RAG with SQLite Cache
class OfflineRAGService {
final Database db;
Future<void> cacheQA(String query, String answer, List<String> sources) async {
await db.insert('qa_cache', {
'query': query,
'answer': answer,
'sources': jsonEncode(sources),
'embedding': await computeEmbedding(query),
'created_at': DateTime.now().toIso8601String(),
});
}
Future<CachedResult?> findSimilar(String query, {double threshold = 0.92}) async {
final queryEmbedding = await computeEmbedding(query);
final rows = await db.query('qa_cache', limit: 100);
CachedResult? bestMatch;
double bestScore = 0;
for (final row in rows) {
final cachedEmbedding = jsonDecode(row['embedding']);
final similarity = cosineSimilarity(queryEmbedding, cachedEmbedding);
if (similarity > threshold && similarity > bestScore) {
bestScore = similarity;
bestMatch = CachedResult(
answer: row['answer'],
sources: jsonDecode(row['sources']),
similarity: similarity,
);
}
}
return bestMatch;
}
}
On-Device LLM with llamafu
import 'package:llamafu/llamafu.dart';
class OnDeviceLLM {
LlamaModel? _model;
Future<void> loadModel() async {
_model = await LlamaModel.fromFile(
'/path/to/llama3-8b.gguf',
contextLength: 4096,
threads: 4,
);
}
Stream<String> generate(String prompt) async* {
final stream = _model!.generate(prompt, temperature: 0.7);
await for (final token in stream) {
yield token;
}
}
void dispose() {
_model?.dispose();
}
}
Architecture Patterns
| Pattern | Use Case | Pros | Cons |
|---|---|---|---|
| Cloud-first | All processing on server | Best model quality, no mobile limits | Requires network, latency |
| On-device | All processing on mobile | Offline, private, zero cost | Limited to 3-8B models |
| Hybrid | On-device for simple, server for complex | Best of both | Complexity, routing logic |
| Offline-first | Cache + queue + sync | Works offline, fast for cached | Stale data, sync conflicts |
Enterprise Features
| Feature | Implementation | Why It Matters |
|---|---|---|
| JWT auth | flutter_secure_storage + interceptor | Enterprise SSO integration |
| Document ACLs | Backend RLS + metadata filtering | Users only see authorized docs |
| Citation tagging | Backend returns sources with answers | Audit trail |
| Voice input | speech_to_text package | Field employees, accessibility |
| Offline cache | SQLite + semantic similarity | Works without network |
| Audit logging | Backend logs all queries | EU AI Act compliance |
| Rate limiting | Backend Nginx + client-side backoff | Prevent GPU DoS |
| Push notifications | Firebase Cloud Messaging | Alert on async task completion |
| Multi-language | Flutter intl + backend i18n | Global workforce |
| Dark mode | Flutter ThemeData | User preference, battery saving |
Flutter Enterprise AI App Checklist
- [ ] Set up Flutter project with Riverpod or Bloc for state management
- [ ] Implement chat UI with StreamBuilder for streaming responses
- [ ] Set up WebSocket connection to FastAPI backend
- [ ] Implement JWT auth with flutter_secure_storage
- [ ] Add auth interceptor for all API calls
- [ ] Set up SQLite cache for offline Q&A with semantic similarity
- [ ] Implement offline query queue (process when reconnected)
- [ ] Add voice input with speech_to_text package
- [ ] Configure citation display in chat UI
- [ ] Implement document-level ACLs via backend RLS
- [ ] Add typing indicator animation during LLM generation
- [ ] Set up push notifications for async task completion
- [ ] Consider on-device LLM with llamafu for offline inference
- [ ] Use LiteLLM on backend for multi-model routing
- [ ] Connect to self-hosted Ollama for data sovereignty
- [ ] Use pgvector for vector search on backend
- [ ] Integrate FalkorDB for knowledge graph queries
- [ ] Add semantic answer caching on backend
- [ ] Implement hallucination prevention: confidence threshold
- [ ] Configure Docker Compose deployment for backend
- [ ] Set up error handling: network errors, timeout, rate limit, auth failure
- [ ] Add loading states and skeleton screens
- [ ] Implement conversation history with local persistence
- [ ] Add dark mode support with ThemeData
- [ ] Configure multi-language support with Flutter intl
- [ ] Test on low-end Android devices (2GB RAM)
- [ ] Test offline mode: airplane mode, verify cache works
- [ ] Test behind corporate VPN and firewall
- [ ] Set up zero-trust security between app and backend
- [ ] Consider AI company brain integration
- [ ] Evaluate self-hosted AI TCO for backend
- [ ] Implement battery-conscious background processing
- [ ] Compress data transfer for mobile bandwidth optimization
- [ ] Add app store deployment: iOS App Store + Google Play
FAQ
Can Flutter be used for enterprise AI apps?
Yes. Flutter is ideal for enterprise AI apps because: single codebase for iOS, Android, web, and desktop; native performance with AOT compilation; StreamBuilder widget handles streaming LLM responses naturally; strong security with flutter_secure_storage for encrypted token storage; works with WebSocket for real-time chat; supports offline-first architectures with SQLite caching. Enterprise AI apps built with Flutter include RAG chatbots, knowledge base assistants, field service AI, and document search. The Flutter AI Toolkit provides ready-made chat widgets with multi-provider LLM support.
How do you stream LLM responses in Flutter?
Use Flutter's StreamBuilder widget with Server-Sent Events (SSE) or WebSocket. The LLM backend (Ollama, FastAPI, or cloud API) streams tokens as they generate. Flutter receives each token via Stream and appends it to the chat UI in real-time. This gives users a ChatGPT-like typing experience. Use WebSocket for bidirectional communication (chat + voice) or SSE for unidirectional streaming.
Can Flutter apps run LLMs on-device?
Yes, using llamafu (Flutter FFI plugin built on llama.cpp) or Google's MediaPipe. llamafu runs GGUF-format models (Llama 3, Mistral, Phi) directly on iOS and Android with native performance via FFI. This enables fully offline AI: no network, no API costs, complete data privacy. On-device models are limited to 3-8B parameters due to mobile memory constraints. For larger models, use hybrid architecture: on-device for simple queries, cloud/self-hosted for complex reasoning. Use LiteLLM gateway to route between on-device and server models.
How do you implement offline RAG in Flutter?
Offline RAG in Flutter uses: (1) SQLite or Drift for local vector storage with cosine similarity search, (2) Document chunking and embedding computation on-device or pre-computed on server, (3) Cached frequent Q&A pairs for instant offline responses, (4) Queue queries when offline, process when connection restored, (5) Sync conversation history when reconnected. For full offline RAG, use flutter_rag package (pure Dart RAG engine with PDF/CSV loaders, chunking, vector search, and OpenAI integration). For hybrid, cache embeddings locally and fall back to server for complex queries.
What architecture should a Flutter enterprise AI app use?
Use a hybrid architecture: Flutter frontend (chat UI, streaming, offline cache) + FastAPI backend (RAG orchestration, pgvector retrieval, FalkorDB graph, LLM inference). The Flutter app communicates with the backend via WebSocket (real-time chat) or REST API (document search). Auth: JWT tokens stored in flutter_secure_storage. State management: Riverpod or Bloc. Offline: SQLite cache for frequent Q&A, queue for offline queries. Backend: self-hosted Ollama for data sovereignty, LiteLLM for multi-model routing. Deploy backend with Docker Compose. This gives you a cross-platform AI app with enterprise security, offline capability, and self-hosted data sovereignty.
Want a self-hosted AI company brain that does all of this out of the box?
Book a demo →