> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cocobase.cc/llms.txt
> Use this file to discover all available pages before exploring further.

# Flutter / Dart SDK

> Complete reference for the CocoBase Flutter and Dart SDK (v1.2)

# 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](https://pub.dev/packages/coco_base_flutter)

## Installation

Add to `pubspec.yaml`:

```yaml theme={null}
dependencies:
  coco_base_flutter: ^1.2.0
```

Then run:

```bash theme={null}
flutter pub get
```

***

## Initialization

```dart theme={null}
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, ...)`

```dart theme={null}
// 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)`

```dart theme={null}
final doc = await db.getDocument<Post>("posts", "doc-id");
print(doc.data.title);
```

### `createDocument<T>(collection, data)`

```dart theme={null}
// 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.

```dart theme={null}
await db.updateDocument("posts", "doc-id", {"title": "New Title"});
```

### `deleteDocument(collection, id)`

```dart theme={null}
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`:

```dart theme={null}
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

```dart theme={null}
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:

```dart theme={null}
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:

```dart theme={null}
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()`.

```dart theme={null}
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:

```dart theme={null}
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.

```dart theme={null}
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

```dart theme={null}
// 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.

```dart theme={null}
final authenticated = await db.auth.initAuth();
if (authenticated) {
  print("Welcome back, ${db.auth.user?.email}");
} else {
  showLoginScreen();
}
```

### Reactive auth — `onAuthStateChange`

```dart theme={null}
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

```dart theme={null}
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

```dart theme={null}
final updated = await db.auth.updateUser(
  data: {"bio": "Hello!"},
  email: "new@example.com",
  password: "NewPassword123!",
);
```

***

## Real-time

### Watch a collection

```dart theme={null}
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

```dart theme={null}
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

```dart theme={null}
final room = db.realtime.room("room-id", userId, userName);
room.connect();
room.onMessage((msg) => appendMessage(msg));
room.send({"text": "Hey!"});
room.disconnect();
```

***

## Cloud Functions

```dart theme={null}
// 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:

```dart theme={null}
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`.

***

## Widgets (`package:coco_base_flutter/widgets.dart`)

Import the widget library:

```dart theme={null}
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.

```dart theme={null}
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`.

```dart theme={null}
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.

```dart theme={null}
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.

```dart theme={null}
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.

```dart theme={null}
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.

```dart theme={null}
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.

```dart theme={null}
CocobaseErrorWidget(
  error: cocobaseError,
  onRetry: () => setState(() {}),
)
```

### `CocobaseFutureBuilder<T>` — generic async wrapper

```dart theme={null}
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.

```dart theme={null}
CocobaseUserAvatar(
  user: db.auth.user!,
  radius: 24,
)
```

***

## Next Steps

* [JavaScript SDK](/sdk-reference/javascript) — JS/TS equivalent
* [MCP Server](/sdk-reference/mcp) — AI assistant integration
* [Authentication guide](/core-concepts/authentication)
* [Querying guide](/features/querying)
* [Real-time guide](/features/realtime)
