-
Notifications
You must be signed in to change notification settings - Fork 0
Persistence
The SDK can save and restore conversations across app restarts through the
Memory interface. The core package ships two dependency-free
implementations and stays free of storage dependencies (no Hive, SQLite, or
shared_preferences) — you plug in whichever backend fits your app.
-
Memory— the storage interface:saveConversation,loadConversation,deleteConversation,listConversationIds,listSummaries,clearAll. -
InMemoryMemory— stores conversations as JSON snapshots in memory. Good for tests and temporary sessions; lost on restart. -
LimitedMemory— a decorator that caps anotherMemoryto the N most recently used conversations, evicting the oldest. -
JsonFileMemory— one JSON file per conversation on disk (mobile and desktop only — see Web support below). -
ConversationSummary— lightweight metadata (id,title,messageCount,createdAt,updatedAt) for building a conversation-list UI without loading full message histories.
final ai = FlutterAI(
provider: AIProvider.anthropic,
config: AIConfig(apiKey: 'sk-ant-...'),
);
final memory = InMemoryMemory();
// Save the conversation automatically after every turn.
await ai.attachMemory(memory);
await ai.chat('Hello!');
// ... later, e.g. after an app restart with a fresh FlutterAI instance ...
final restored = await ai.loadConversation(memory, savedConversationId);
if (restored) {
print('Resumed: ${ai.conversation.title}');
}attachMemory saves the conversation immediately, then again after every
subsequent update (message added/removed, cleared, truncated) — driven by
the same update stream ContextManager.updates already exposes. Saves are
fire-and-forget (not awaited by chat()/streamChat()), so a slow storage
backend never adds latency to a request.
await ai.attachMemory(memory);
// ... chat normally; every turn is persisted in the background ...
await ai.detachMemory(); // stop auto-savingAttaching a new memory replaces the previous one — only one memory can be attached for auto-save at a time.
await ai.saveConversation(memory); // saves the current conversation oncefinal restored = await ai.loadConversation(memory, id);
// Returns false and leaves the current conversation untouched if `id`
// isn't found in `memory`.final summaries = await memory.listSummaries(); // newest updatedAt first
for (final s in summaries) {
print('${s.title ?? s.id}: ${s.messageCount} messages');
}final memory = LimitedMemory(
delegate: InMemoryMemory(), // or JsonFileMemory, or your own
maxConversations: 50,
);import 'package:path_provider/path_provider.dart';
final dir = await getApplicationDocumentsDirectory();
final memory = JsonFileMemory(
directoryPath: '${dir.path}/conversations',
);path_provider is not a dependency of this SDK — add it to your own app if
you want a standard, per-platform documents directory.
dart:io isn't available on the web, so JsonFileMemory resolves to a
stub there that throws UnsupportedError immediately on construction. Use
InMemoryMemory, or implement Memory on top of a web storage API
(IndexedDB, localStorage) — see below.
Implement the four persistence methods plus listSummaries (or derive it
from listConversationIds + loadConversation, as JsonFileMemory does)
against any backend — Hive, SQLite (sqflite/drift), shared_preferences,
a REST API, IndexedDB on the web, etc.
class HiveMemory implements Memory {
HiveMemory(this._box);
final Box<Map> _box;
@override
Future<void> saveConversation(Conversation conversation) async {
await _box.put(conversation.id, conversation.toJson());
}
@override
Future<Conversation?> loadConversation(String id) async {
final json = _box.get(id);
return json != null ? Conversation.fromJson(json.cast<String, dynamic>()) : null;
}
@override
Future<bool> deleteConversation(String id) async {
if (!_box.containsKey(id)) return false;
await _box.delete(id);
return true;
}
@override
Future<List<String>> listConversationIds() async => _box.keys.cast<String>().toList();
@override
Future<List<ConversationSummary>> listSummaries() async => [
for (final json in _box.values)
ConversationSummary.fromConversation(
Conversation.fromJson(json.cast<String, dynamic>()),
),
]..sort((a, b) => b.updatedAt.compareTo(a.updatedAt));
@override
Future<void> clearAll() => _box.clear();
}Conversation.toJson() / Conversation.fromJson() already handle every
content type (text, images, audio, documents, tool calls/results), so any
backend that can store a Map<String, dynamic> (or its JSON-encoded string
form) works.
For apps that need tags, pinning, or an AI-generated summary alongside the
conversation, wrap it in IndexedConversation:
final indexed = IndexedConversation(
conversation: ai.conversation,
tags: ['travel', 'planning'],
pinned: true,
);
final json = indexed.toJson(); // conversation + tags + summary + pinned + archivedIndexedConversation doesn't have its own Memory implementation — store
its toJson()/fromJson() output the same way you would a plain
Conversation.
Getting Started
Core Concepts
Advanced Features
API Reference
Examples
Other