Skip to main content

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:
flutter pub get

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

OptionTypeDefaultDescription
apiKeyStringYour project API key (required)
projectIdString?nullProject ID (required for cloud functions)
timeoutint30000Per-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

SuffixMeaningExample
(none)Equal{'status': 'active'}
__gtGreater than{'age__gt': 18}
__gteGreater or equal{'age__gte': 18}
__ltLess than{'price__lt': 100}
__lteLess or equal{'price__lte': 100}
__neNot equal{'status__ne': 'deleted'}
__containsSubstring{'title__contains': 'flutter'}
__startswithStarts with{'email__startswith': 'admin'}
__endswithEnds with{'domain__endswith': '.com'}
__inIn list (comma-sep){'status__in': 'active,pending'}
__notinNot in list{'role__notin': 'bot,spam'}
__isnullIs 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:
MethodDescription
.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,
})

Pagination Helper — paginate

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}");
}
ClassStatusWhen
NotFoundError404Resource doesn’t exist
UnauthorizedError401Not authenticated
ForbiddenError403Lacks permission
RateLimitError429Too many requests
ValidationError422Invalid input
ServerError5xxServer-side failure
TimeoutErrorRequest timed out
All errors expose statusCode, url, method, detail, suggestion.

Widgets (package:coco_base_flutter/widgets.dart)

Import the widget library:
import 'package:coco_base_flutter/widgets.dart';

CocobaseQuery<T> — fetch-once widget

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 */ },
  ),
)

CocobaseLiveQuery<T> — real-time widget

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),
)

CocobaseDocument<T> — single document widget

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),
)

CocobaseInfiniteList<T> — infinite scroll

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(),
)

CocobaseAuthBuilder — auth-aware widget

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(),
)

CocobaseErrorWidget — typed error display

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),
)

CocobaseUserAvatar — user avatar widget

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