-
Notifications
You must be signed in to change notification settings - Fork 0
Streaming
Amayyas edited this page Dec 5, 2025
·
1 revision
Flutter AI SDK supports real-time streaming for all providers, allowing you to display responses as they're generated.
await for (final chunk in ai.streamChat('Tell me a story')) {
if (chunk.isDelta) {
print(chunk.delta); // Print each text chunk
}
}The SDK emits different event types during streaming:
enum StreamEventType {
start, // Stream started
delta, // Text content chunk
toolCallDelta, // Tool call chunk
done, // Stream completed
error, // Error occurred
}await for (final chunk in ai.streamChat('Write a poem')) {
switch (chunk.type) {
case StreamEventType.start:
print('Starting response...');
break;
case StreamEventType.delta:
stdout.write(chunk.delta); // No newline for smooth output
break;
case StreamEventType.done:
print('\n\nDone!');
print('Tokens: ${chunk.usage?.totalTokens}');
print('Finish reason: ${chunk.finishReason}');
break;
case StreamEventType.error:
print('Error: ${chunk.error}');
break;
case StreamEventType.toolCallDelta:
print('Tool call: ${chunk.toolCallDelta}');
break;
}
}| Property | Type | Description |
|---|---|---|
type |
StreamEventType |
Event type |
delta |
String? |
Text content (for delta events) |
toolCallDelta |
ToolCallContent? |
Tool call data |
finishReason |
FinishReason? |
Why generation stopped |
usage |
Usage? |
Token usage (in done event) |
error |
Object? |
Error details |
chunk.isStart // true if type == start
chunk.isDelta // true if type == delta
chunk.isDone // true if type == done
chunk.isError // true if type == errorHere's how to use streaming in a Flutter app:
import 'package:flutter/material.dart';
import 'package:flutter_ai_sdk/flutter_ai_sdk.dart';
class ChatScreen extends StatefulWidget {
@override
_ChatScreenState createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
final ai = FlutterAI(
provider: AIProvider.openai,
config: AIConfig(apiKey: 'your-key'),
);
String _response = '';
bool _isLoading = false;
Future<void> _sendMessage(String message) async {
setState(() {
_isLoading = true;
_response = '';
});
try {
await for (final chunk in ai.streamChat(message)) {
if (chunk.isDelta && chunk.delta != null) {
setState(() {
_response += chunk.delta!;
});
}
}
} catch (e) {
setState(() {
_response = 'Error: $e';
});
} finally {
setState(() {
_isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(
child: SingleChildScrollView(
child: Text(_response),
),
),
if (_isLoading)
LinearProgressIndicator(),
_buildInputField(),
],
),
);
}
Widget _buildInputField() {
// ... TextField implementation
}
@override
void dispose() {
ai.dispose();
super.dispose();
}
}class StreamingController {
final ai = FlutterAI(
provider: AIProvider.anthropic,
config: AIConfig(apiKey: 'your-key'),
);
final response = ValueNotifier<String>('');
final isStreaming = ValueNotifier<bool>(false);
Future<void> send(String message) async {
response.value = '';
isStreaming.value = true;
try {
await for (final chunk in ai.streamChat(message)) {
if (chunk.isDelta) {
response.value += chunk.delta ?? '';
}
}
} finally {
isStreaming.value = false;
}
}
void dispose() {
response.dispose();
isStreaming.dispose();
ai.dispose();
}
}class StreamingChatWidget extends StatelessWidget {
final Stream<StreamChunk> stream;
const StreamingChatWidget({required this.stream});
@override
Widget build(BuildContext context) {
return StreamBuilder<String>(
stream: stream
.where((chunk) => chunk.isDelta)
.map((chunk) => chunk.delta ?? '')
.scan((acc, delta) => acc + delta, ''),
builder: (context, snapshot) {
return Text(snapshot.data ?? '');
},
);
}
}You can cancel a stream by breaking out of the loop:
bool shouldCancel = false;
Future<void> sendWithCancel(String message) async {
shouldCancel = false;
await for (final chunk in ai.streamChat(message)) {
if (shouldCancel) {
break; // Exits the stream
}
if (chunk.isDelta) {
print(chunk.delta);
}
}
}
void cancel() {
shouldCancel = true;
}If you need both streaming and the full response:
Future<String> streamAndCollect(String message) async {
final buffer = StringBuffer();
await for (final chunk in ai.streamChat(message)) {
if (chunk.isDelta && chunk.delta != null) {
buffer.write(chunk.delta);
print(chunk.delta); // Real-time output
}
}
return buffer.toString();
}try {
await for (final chunk in ai.streamChat(message)) {
if (chunk.isError) {
throw chunk.error!;
}
// Handle chunk...
}
} on AIRateLimitError catch (e) {
print('Rate limited: ${e.retryAfter}');
} on AIError catch (e) {
print('AI Error: ${e.message}');
} catch (e) {
print('Unexpected error: $e');
}- Use StringBuffer for collecting responses instead of string concatenation
- Throttle UI updates if needed for very fast streams
- Dispose properly to avoid memory leaks
- Handle errors within the stream loop
- Basic Usage - Non-streaming chat
- Error Handling - Error management
- Example-Streaming-UI - Complete UI example
Getting Started
Core Concepts
Advanced Features
API Reference
Examples
Other