Documentation Index
Fetch the complete documentation index at: https://docs.cocobase.buzz/llms.txt
Use this file to discover all available pages before exploring further.
Flutter / Dart SDK
The official CocoBase Dart/Flutter SDK — works in Flutter apps, Dart CLI tools, and any Dart runtime.
Package: coco_base_flutter · Version: 1.2.x · pub.dev: pub.dev/packages/coco_base_flutter
Installation
Add to pubspec.yaml:
dependencies:
coco_base_flutter: ^1.2.0
Then run:
Initialization
import 'package:coco_base_flutter/coco_base_flutter.dart';
void main() async {
final db = Cocobase(
CocobaseConfig(
apiKey: "YOUR_API_KEY",
projectId: "YOUR_PROJECT_ID", // required for cloud functions
timeout: 30000, // per-request timeout in ms (default: 30000)
),
);
// Restore session on app start (safe — network errors won't log user out)
final authenticated = await db.auth.initAuth();
runApp(MyApp(db: db, authenticated: authenticated));
}
CocobaseConfig options
| Option | Type | Default | Description |
|---|
apiKey | String | — | Your project API key (required) |
projectId | String? | null | Project ID (required for cloud functions) |
timeout | int | 30000 | Per-request timeout in ms |
Documents — CRUD
listDocuments<T>(collection, ...)
// All documents (untyped)
final docs = await db.listDocuments("posts");
// With filters, sorting, pagination
final docs = await db.listDocuments<Post>(
"posts",
filters: {
'status': 'published',
'age__gt': 18,
},
converter: Post.fromJson,
);
// Each doc is Document<Post>
for (final doc in docs) {
print(doc.id);
print(doc.data.title); // typed!
print(doc.createdAt);
}
getDocument<T>(collection, id)
final doc = await db.getDocument<Post>("posts", "doc-id");
print(doc.data.title);
createDocument<T>(collection, data)
// Untyped
final created = await db.createDocument("posts", {
"title": "Hello World",
"published": true,
});
print(created.id); // server-assigned ID
// Typed — data must have a toJson() method
final created = await db.createDocument<Post>("posts", Post(
title: "Hello World",
published: true,
));
updateDocument(collection, id, data)
Partial update — only fields you pass are changed. Other fields are preserved.
await db.updateDocument("posts", "doc-id", {"title": "New Title"});
deleteDocument(collection, id)
await db.deleteDocument("posts", "doc-id");
Querying
Filter operators
| Suffix | Meaning | Example |
|---|
| (none) | Equal | {'status': 'active'} |
__gt | Greater than | {'age__gt': 18} |
__gte | Greater or equal | {'age__gte': 18} |
__lt | Less than | {'price__lt': 100} |
__lte | Less or equal | {'price__lte': 100} |
__ne | Not equal | {'status__ne': 'deleted'} |
__contains | Substring | {'title__contains': 'flutter'} |
__startswith | Starts with | {'email__startswith': 'admin'} |
__endswith | Ends with | {'domain__endswith': '.com'} |
__in | In list (comma-sep) | {'status__in': 'active,pending'} |
__notin | Not in list | {'role__notin': 'bot,spam'} |
__isnull | Is null | {'deletedAt__isnull': true} |
QueryBuilder (fluent API)
For complex queries, use the fluent QueryBuilder:
final query = QueryBuilder()
.where('status', 'published')
.whereGreaterThan('price', 10)
.whereContains('title', 'flutter')
.orderByDesc('createdAt')
.limit(20)
.offset(0);
final docs = await db.listDocuments<Post>("posts", queryBuilder: query);
Available QueryBuilder methods:
| Method | Description |
|---|
.where(field, value) | Equality filter |
.whereGreaterThan(field, value) | field > value |
.whereGreaterThanOrEqual(field, value) | field >= value |
.whereLessThan(field, value) | field < value |
.whereLessThanOrEqual(field, value) | field <= value |
.whereNotEqual(field, value) | field != value |
.whereContains(field, value) | Substring match |
.whereStartsWith(field, value) | Prefix match |
.whereEndsWith(field, value) | Suffix match |
.whereIn(field, values) | Field in list |
.whereNotIn(field, values) | Field not in list |
.whereIsNull(field, bool) | Null check |
.or(field, value) | OR condition |
.orGroup(groupName, field, value) | Named OR group |
.searchInFields(fields, term) | Multi-field search |
.orderByAsc(field) | Sort ascending |
.orderByDesc(field) | Sort descending |
.limit(n) | Max results |
.offset(n) | Skip n results |
.select(field) | Include only field |
.selectAll(fields) | Include only these fields |
.populate(field) | Load related document |
.populateAll(fields) | Load multiple related docs |
Count & aggregate
final count = await db.countDocuments("users",
queryBuilder: QueryBuilder().where("status", "active"),
);
print(count.count);
final result = await db.aggregateDocuments("orders",
field: "price",
operation: "avg", // "sum" | "avg" | "min" | "max"
filters: {"status": "completed"},
);
print(result.result);
Type Safety
Register a converter once at startup, then all calls for that type are automatically converted:
void main() {
// Register converters once
CocobaseConverters.register<Post>(Post.fromJson);
CocobaseConverters.register<User>(User.fromJson);
runApp(MyApp());
}
// Model definition
class Post {
final String title;
final bool published;
Post({required this.title, required this.published});
factory Post.fromJson(Map<String, dynamic> json) => Post(
title: json['title'] as String,
published: json['published'] as bool? ?? false,
);
Map<String, dynamic> toJson() => {'title': title, 'published': published};
}
// Now all calls auto-convert
final posts = await db.listDocuments<Post>("posts");
print(posts.first.data.title); // typed!
Alternatively pass the converter inline:
final posts = await db.listDocuments<Post>(
"posts",
converter: Post.fromJson,
);
Live Queries — onSnapshot
Subscribe to a collection. Fires immediately with current data, then again on every create/update/delete. Returns a VoidCallback to cancel in dispose().
class PostsPage extends StatefulWidget { /* ... */ }
class _PostsPageState extends State<PostsPage> {
late VoidCallback _unsub;
List<Document<Post>> _posts = [];
@override
void initState() {
super.initState();
_unsub = db.onSnapshot<Post>(
"posts",
converter: Post.fromJson,
onData: (posts) {
setState(() => _posts = posts);
},
onError: (err) => print("Error: $err"),
);
}
@override
void dispose() {
_unsub(); // cancel subscription
super.dispose();
}
}
onSnapshot signature:
VoidCallback onSnapshot<T>(
String collection, {
QueryBuilder? queryBuilder,
Map<String, dynamic>? filters,
T Function(Map<String, dynamic>)? converter,
required void Function(List<Document<T>> docs) onData,
void Function(CocobaseError error)? onError,
})
Stateful pagination — tracks page position, detects hasMore, no backend changes needed.
final pager = db.paginate<Post>(
"posts",
pageSize: 20,
queryBuilder: QueryBuilder().orderByDesc('createdAt'),
converter: Post.fromJson,
);
// Fetch pages
final page1 = await pager.nextPage();
// page1.data → List<Document<Post>>
// page1.hasMore → bool
// page1.page → int (current page index)
// page1.pageSize → int
final page2 = await pager.nextPage();
await pager.prevPage();
await pager.goToPage(3); // zero-indexed
pager.reset(); // back to start
Authentication
Register / Login / Logout
// Register
await db.auth.register(
email: "user@example.com",
password: "SecurePassword123!",
data: {"username": "johndoe"}, // optional
);
// Login
await db.auth.login(
email: "user@example.com",
password: "SecurePassword123!",
);
// Get current user
final user = await db.auth.getCurrentUser();
print(user?.email);
// Cached user (no network)
print(db.auth.user?.email);
print(db.auth.token);
// Is logged in?
print(db.auth.isAuthenticated());
// Logout
db.auth.logout();
Restore session — initAuth
Call once at app startup. Returns true if session was restored. Network errors and server errors do not log the user out — only an explicit 401/403 does.
final authenticated = await db.auth.initAuth();
if (authenticated) {
print("Welcome back, ${db.auth.user?.email}");
} else {
showLoginScreen();
}
Reactive auth — onAuthStateChange
class _MyAppState extends State<MyApp> {
late VoidCallback _unsub;
@override
void initState() {
super.initState();
_unsub = db.auth.onAuthStateChange((user, token) {
if (user != null) {
Navigator.pushReplacementNamed(context, '/home');
} else {
Navigator.pushReplacementNamed(context, '/login');
}
});
}
@override
void dispose() {
_unsub();
super.dispose();
}
}
Auth lifecycle callbacks
db.auth.onAuthEvent(AuthCallbacks(
onLogin: (user, token) => print("Logged in: ${user.email}"),
onRegister: (user, token) => print("Registered: ${user.email}"),
onLogout: () => navigateToLogin(),
onAuthStateChange: (user, token) {
// Fired on initAuth() completion
if (user != null) restoreUserState(user);
},
));
Update user
final updated = await db.auth.updateUser(
data: {"bio": "Hello!"},
email: "new@example.com",
password: "NewPassword123!",
);
Real-time
Watch a collection
final watcher = db.realtime.collection("posts");
watcher.connect();
watcher.onCreate((event) => print("New doc: ${event.document}"));
watcher.onUpdate((event) => print("Updated: ${event.document}"));
watcher.onDelete((event) => print("Deleted: ${event.document}"));
// Called when WebSocket reconnects after a drop
watcher.onReconnect(() {
print("Reconnected! Refreshing...");
});
// Stop watching
watcher.disconnect();
Project broadcast
final broadcast = db.realtime.broadcast(userId, userName);
broadcast.connect();
broadcast.onMessage((data) => print("Broadcast: $data"));
broadcast.send({"type": "announcement", "text": "Hello!"});
broadcast.disconnect();
Room chat
final room = db.realtime.room("room-id", userId, userName);
room.connect();
room.onMessage((msg) => appendMessage(msg));
room.send({"text": "Hey!"});
room.disconnect();
Cloud Functions
// GET
final result = await db.functions.get<Map<String, dynamic>>("getStats");
print(result.result);
// POST
final result = await db.functions.post<Map<String, dynamic>>(
"sendEmail",
payload: {"to": "user@example.com", "subject": "Hello"},
);
print(result.executionTime); // ms
print(result.output); // console output from function
// Full execute with options
final result = await db.functions.execute<String>(
"processOrder",
payload: {"orderId": "123"},
method: FunctionMethod.post,
);
Typed Errors
All API errors throw a typed subclass of CocobaseError. Use is to branch:
import 'package:coco_base_flutter/coco_base_flutter.dart';
try {
await db.getDocument("posts", "bad-id");
} on NotFoundError {
showNotFound(); // 404
} on UnauthorizedError {
redirectToLogin(); // 401
} on ForbiddenError {
showAccessDenied(); // 403
} on RateLimitError {
showRetryMessage(); // 429
} on ValidationError catch (e) {
showFormErrors(e.detail); // 422
} on ServerError {
showServerError(); // 5xx
} on TimeoutError {
showTimeoutMessage();
} on CocobaseError catch (e) {
print("Error ${e.statusCode}: ${e.detail}");
}
| Class | Status | When |
|---|
NotFoundError | 404 | Resource doesn’t exist |
UnauthorizedError | 401 | Not authenticated |
ForbiddenError | 403 | Lacks permission |
RateLimitError | 429 | Too many requests |
ValidationError | 422 | Invalid input |
ServerError | 5xx | Server-side failure |
TimeoutError | — | Request timed out |
All errors expose statusCode, url, method, detail, suggestion.
Import the widget library:
import 'package:coco_base_flutter/widgets.dart';
Fetches a collection once and rebuilds when data arrives. Handles loading and error states automatically.
CocobaseQuery<Post>(
db: db,
collection: "posts",
converter: Post.fromJson,
filters: {'status': 'published'},
builder: (context, posts) {
return ListView.builder(
itemCount: posts.length,
itemBuilder: (ctx, i) => Text(posts[i].data.title),
);
},
loadingBuilder: (context) => const CircularProgressIndicator(),
errorBuilder: (context, error) => CocobaseErrorWidget(
error: error,
onRetry: () { /* trigger rebuild */ },
),
)
Stays in sync via WebSocket. Auto-subscribes in initState, auto-unsubscribes in dispose.
CocobaseLiveQuery<Post>(
db: db,
collection: "posts",
converter: Post.fromJson,
filters: {'published': true},
builder: (context, posts) => PostList(posts: posts),
)
Fetches and displays one document by ID.
CocobaseDocument<Post>(
db: db,
collection: "posts",
documentId: "doc-123",
converter: Post.fromJson,
builder: (context, doc) => PostDetail(doc: doc),
)
CocobasePaginatedList<T> — paginated list
Supports load-more and numbered page modes.
CocobasePaginatedList<Post>(
db: db,
collection: "posts",
converter: Post.fromJson,
pageSize: 20,
mode: PaginationMode.loadMore, // or PaginationMode.numbered
itemBuilder: (context, doc) => PostCard(doc: doc),
)
Auto-loads next page as the user scrolls near the bottom.
CocobaseInfiniteList<Post>(
db: db,
collection: "posts",
converter: Post.fromJson,
pageSize: 20,
itemBuilder: (context, doc) => PostCard(doc: doc),
separatorBuilder: (context, index) => const Divider(),
)
Rebuilds on auth state change. Renders different widgets for authenticated, unauthenticated, and loading states.
CocobaseAuthBuilder(
db: db,
authenticated: (context, user) => HomeScreen(user: user),
unauthenticated: (context) => const LoginScreen(),
loading: (context) => const SplashScreen(),
)
Shows an appropriate icon and message for each error type, with an optional retry button.
CocobaseErrorWidget(
error: cocobaseError,
onRetry: () => setState(() {}),
)
CocobaseFutureBuilder<T> — generic async wrapper
CocobaseFutureBuilder<Post>(
future: db.getDocument<Post>("posts", "doc-id", converter: Post.fromJson),
builder: (context, doc) => PostDetail(doc: doc),
)
Reads data['avatar'], data['avatar_url'], or data['profile_image'] from the user object. Falls back to computed initials if no image is found.
CocobaseUserAvatar(
user: db.auth.user!,
radius: 24,
)
Next Steps