Skip to main content

Dart SDK

The CocoBASE Dart SDK provides a comprehensive Flutter/Dart client for interacting with your CocoBASE backend. Build powerful mobile and web applications with real-time capabilities, authentication, and seamless database operations.

Installation

Add the CocoBASE SDK to your pubspec.yaml:

dependencies:
cocobase: ^1.0.0
dio: ^5.0.0
web_socket_channel: ^2.4.0
shared_preferences: ^2.0.0

Then run:

flutter pub get

Quick Start

Initialize CocoBASE

import 'package:cocobase/cocobase.dart';

void main() {
final cocobase = Cocobase(CocobaseConfig(
apiKey: 'your-api-key-here',
));

runApp(MyApp(cocobase: cocobase));
}

Basic Usage

class MyApp extends StatelessWidget {
final Cocobase cocobase;

const MyApp({Key? key, required this.cocobase}) : super(key: key);


Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(cocobase: cocobase),
);
}
}

Configuration

CocobaseConfig

final config = CocobaseConfig(
apiKey: 'your-api-key-here',
);

final cocobase = Cocobase(config);

The SDK automatically connects to https://api.cocobase.com and handles:

  • Request timeout configuration (5s connect, 3s receive)
  • Automatic API key injection in headers
  • JSON content-type headers
  • Bearer token authentication

Database Operations

Document Management

Create Document

// Create a user document
final newUser = await cocobase.createDocument<Map<String, dynamic>>(
'users',
{
'name': 'John Doe',
'email': 'john@example.com',
'age': 30,
'roles': ['user'],
},
);

print('Created user: ${newUser.id}');
print('User data: ${newUser.data}');

Get Single Document

// Fetch a specific document
try {
final user = await cocobase.getDocument<Map<String, dynamic>>(
'users',
'document-id-here',
);

print('User: ${user.data['name']}');
print('Created: ${user.createdAt}');
} catch (e) {
print('Document not found: $e');
}

Update Document

// Update a document
final updatedUser = await cocobase.updateDocument<Map<String, dynamic>>(
'users',
'document-id-here',
{
'name': 'Jane Doe',
'age': 31,
'last_login': DateTime.now().toIso8601String(),
},
);

print('Updated user: ${updatedUser.data['name']}');

Delete Document

// Delete a document
final result = await cocobase.deleteDocument('users', 'document-id-here');
if (result['success'] == true) {
print('Document deleted successfully');
}

List Documents

// Get all documents
final users = await cocobase.listDocuments<Map<String, dynamic>>('users');
print('Total users: ${users.length}');

// Get documents with query
final activeUsers = await cocobase.listDocuments<Map<String, dynamic>>(
'users',
query: Query(
where: {'status': 'active'},
limit: 10,
offset: 0,
orderBy: 'created_at',
),
);

print('Active users: ${activeUsers.length}');

Query Options

The Query class supports the following options:

final query = Query(
where: {
'status': 'active',
'age': '25',
'role': 'admin',
},
orderBy: 'created_at', // Field to sort by
limit: 20, // Maximum number of results
offset: 0, // Skip first N results
);

final results = await cocobase.listDocuments('users', query: query);

Working with Typed Data

You can work with custom Dart classes:

class User {
final String name;
final String email;
final int age;

User({required this.name, required this.email, required this.age});

factory User.fromJson(Map<String, dynamic> json) {
return User(
name: json['name'],
email: json['email'],
age: json['age'],
);
}

Map<String, dynamic> toJson() {
return {
'name': name,
'email': email,
'age': age,
};
}
}

// Create with typed data
final userData = User(name: 'John', email: 'john@example.com', age: 30);
final document = await cocobase.createDocument('users', userData.toJson());

// Retrieve and convert
final doc = await cocobase.getDocument<Map<String, dynamic>>('users', document.id);
final user = User.fromJson(doc.data);

Authentication

Initialize Authentication

Before using authentication features, initialize the auth system:

void initializeApp() async {
await cocobase.initAuth();

if (cocobase.isAuthenticated()) {
print('User is logged in: ${cocobase.user?.email}');
} else {
print('User is not authenticated');
}
}

User Registration

Future<void> registerUser(String email, String password) async {
try {
await cocobase.register(
email,
password,
data: {
'firstName': 'John',
'lastName': 'Doe',
'preferences': {
'theme': 'dark',
'notifications': true,
},
},
);

print('Registration successful!');
print('User: ${cocobase.user?.email}');
} catch (e) {
print('Registration failed: $e');
}
}

User Login

Future<void> loginUser(String email, String password) async {
try {
await cocobase.login(email, password);
print('Login successful!');
print('Welcome back, ${cocobase.user?.email}');
} catch (e) {
print('Login failed: $e');
}
}

User Information

// Check authentication status
if (cocobase.isAuthenticated()) {
final currentUser = cocobase.user!;
print('ID: ${currentUser.id}');
print('Email: ${currentUser.email}');
print('Created: ${currentUser.createdAt}');
print('Custom data: ${currentUser.data}');
}

// Refresh user data
try {
final user = await cocobase.getCurrentUser();
print('Updated user data: ${user.data}');
} catch (e) {
print('Failed to fetch user: $e');
}

Update User Profile

Future<void> updateUserProfile() async {
try {
final updatedUser = await cocobase.updateUser(
email: 'newemail@example.com',
password: 'newpassword123',
data: {
'firstName': 'Jane',
'preferences': {
'theme': 'light',
'notifications': false,
},
},
);

print('Profile updated: ${updatedUser.email}');
} catch (e) {
print('Update failed: $e');
}
}

Logout

void logoutUser() {
cocobase.logout();
print('User logged out');
}

Real-time Features

Watch Collection Changes

Connection? connection;

void watchUsers() {
connection = cocobase.watchCollection(
'users',
(event) {
print('Event received: ${event['event']}');
print('Data: ${event['data']}');

// Handle different event types
switch (event['event']) {
case 'create':
handleUserCreated(event['data']);
break;
case 'update':
handleUserUpdated(event['data']);
break;
case 'delete':
handleUserDeleted(event['data']);
break;
}
},
connectionName: 'users-watcher',
onOpen: () {
print('Connected to users collection');
},
onError: () {
print('Connection error occurred');
},
);
}

void handleUserCreated(Map<String, dynamic> userData) {
print('New user created: ${userData['name']}');
// Update your UI here
}

void handleUserUpdated(Map<String, dynamic> userData) {
print('User updated: ${userData['id']}');
// Update your UI here
}

void handleUserDeleted(Map<String, dynamic> userData) {
print('User deleted: ${userData['id']}');
// Update your UI here
}

Manage Connections

// Close specific connection
void closeUsersWatcher() {
if (connection != null) {
cocobase.closeConnection(connection!);
print('Connection closed');
}
}

// Check connection status
void checkConnection() {
if (connection != null && !connection!.closed) {
print('Connection is active');
} else {
print('Connection is closed');
}
}

Error Handling

The SDK provides detailed error information:

try {
final user = await cocobase.getDocument('users', 'invalid-id');
} catch (e) {
print('Error: $e');

// The error includes:
// - HTTP status code
// - Request URL and method
// - Error details from server
// - Helpful suggestions for fixing the issue
}

Common Error Scenarios

Future<void> handleCommonErrors() async {
try {
await cocobase.createDocument('users', {'name': 'Test'});
} catch (e) {
final errorStr = e.toString();

if (errorStr.contains('401')) {
print('Authentication error - check your API key');
} else if (errorStr.contains('403')) {
print('Permission denied - verify access rights');
} else if (errorStr.contains('404')) {
print('Resource not found - check collection name and document ID');
} else if (errorStr.contains('429')) {
print('Rate limit exceeded - wait before retrying');
} else {
print('Unexpected error: $e');
}
}
}

Flutter Integration Examples

User Authentication Flow

class AuthScreen extends StatefulWidget {
final Cocobase cocobase;

const AuthScreen({Key? key, required this.cocobase}) : super(key: key);


_AuthScreenState createState() => _AuthScreenState();
}

class _AuthScreenState extends State<AuthScreen> {
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
bool _isLoading = false;

Future<void> _login() async {
setState(() => _isLoading = true);

try {
await widget.cocobase.login(
_emailController.text,
_passwordController.text,
);

// Navigate to home screen
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => HomeScreen(cocobase: widget.cocobase)),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Login failed: $e')),
);
} finally {
setState(() => _isLoading = false);
}
}


Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
TextField(
controller: _emailController,
decoration: const InputDecoration(labelText: 'Email'),
),
TextField(
controller: _passwordController,
decoration: const InputDecoration(labelText: 'Password'),
obscureText: true,
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _isLoading ? null : _login,
child: _isLoading
? const CircularProgressIndicator()
: const Text('Login'),
),
],
),
),
);
}
}

Real-time Data List

class UsersList extends StatefulWidget {
final Cocobase cocobase;

const UsersList({Key? key, required this.cocobase}) : super(key: key);


_UsersListState createState() => _UsersListState();
}

class _UsersListState extends State<UsersList> {
List<Document<Map<String, dynamic>>> users = [];
Connection? _connection;


void initState() {
super.initState();
_loadUsers();
_watchUsers();
}


void dispose() {
if (_connection != null) {
widget.cocobase.closeConnection(_connection!);
}
super.dispose();
}

Future<void> _loadUsers() async {
try {
final usersList = await widget.cocobase.listDocuments<Map<String, dynamic>>('users');
setState(() {
users = usersList;
});
} catch (e) {
print('Failed to load users: $e');
}
}

void _watchUsers() {
_connection = widget.cocobase.watchCollection(
'users',
(event) {
setState(() {
switch (event['event']) {
case 'create':
users.add(Document<Map<String, dynamic>>.fromJson(event['data']));
break;
case 'update':
final updatedDoc = Document<Map<String, dynamic>>.fromJson(event['data']);
final index = users.indexWhere((u) => u.id == updatedDoc.id);
if (index != -1) {
users[index] = updatedDoc;
}
break;
case 'delete':
users.removeWhere((u) => u.id == event['data']['id']);
break;
}
});
},
);
}


Widget build(BuildContext context) {
return ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) {
final user = users[index];
return ListTile(
title: Text(user.data['name'] ?? 'Unknown'),
subtitle: Text(user.data['email'] ?? ''),
trailing: Text(user.createdAt.toString()),
);
},
);
}
}

Data Submission Form

class CreateUserForm extends StatefulWidget {
final Cocobase cocobase;

const CreateUserForm({Key? key, required this.cocobase}) : super(key: key);


_CreateUserFormState createState() => _CreateUserFormState();
}

class _CreateUserFormState extends State<CreateUserForm> {
final _formKey = GlobalKey<FormState>();
final _nameController = TextEditingController();
final _emailController = TextEditingController();
final _ageController = TextEditingController();

Future<void> _submitForm() async {
if (_formKey.currentState!.validate()) {
try {
final newUser = await widget.cocobase.createDocument<Map<String, dynamic>>(
'users',
{
'name': _nameController.text,
'email': _emailController.text,
'age': int.parse(_ageController.text),
'created_at': DateTime.now().toIso8601String(),
},
);

ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('User created: ${newUser.id}')),
);

// Clear form
_nameController.clear();
_emailController.clear();
_ageController.clear();
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to create user: $e')),
);
}
}
}


Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _nameController,
decoration: const InputDecoration(labelText: 'Name'),
validator: (value) => value?.isEmpty == true ? 'Name is required' : null,
),
TextFormField(
controller: _emailController,
decoration: const InputDecoration(labelText: 'Email'),
validator: (value) => value?.isEmpty == true ? 'Email is required' : null,
),
TextFormField(
controller: _ageController,
decoration: const InputDecoration(labelText: 'Age'),
keyboardType: TextInputType.number,
validator: (value) {
if (value?.isEmpty == true) return 'Age is required';
if (int.tryParse(value!) == null) return 'Age must be a number';
return null;
},
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _submitForm,
child: const Text('Create User'),
),
],
),
);
}
}

Best Practices

1. Initialize Authentication Early

class MyApp extends StatefulWidget {
final Cocobase cocobase;

const MyApp({Key? key, required this.cocobase}) : super(key: key);


_MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
bool _isInitialized = false;


void initState() {
super.initState();
_initializeAuth();
}

Future<void> _initializeAuth() async {
await widget.cocobase.initAuth();
setState(() {
_isInitialized = true;
});
}


Widget build(BuildContext context) {
if (!_isInitialized) {
return const MaterialApp(
home: Scaffold(
body: Center(child: CircularProgressIndicator()),
),
);
}

return MaterialApp(
home: widget.cocobase.isAuthenticated()
? HomeScreen(cocobase: widget.cocobase)
: AuthScreen(cocobase: widget.cocobase),
);
}
}

2. Handle Connection Lifecycle

class RealtimeScreen extends StatefulWidget {

_RealtimeScreenState createState() => _RealtimeScreenState();
}

class _RealtimeScreenState extends State<RealtimeScreen>
with WidgetsBindingObserver {
Connection? _connection;


void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_setupRealtimeConnection();
}


void dispose() {
WidgetsBinding.instance.removeObserver(this);
_closeConnection();
super.dispose();
}


void didChangeAppLifecycleState(AppLifecycleState state) {
switch (state) {
case AppLifecycleState.paused:
_closeConnection();
break;
case AppLifecycleState.resumed:
_setupRealtimeConnection();
break;
default:
break;
}
}

void _setupRealtimeConnection() {
_connection = cocobase.watchCollection('users', (event) {
// Handle events
});
}

void _closeConnection() {
if (_connection != null) {
cocobase.closeConnection(_connection!);
_connection = null;
}
}


Widget build(BuildContext context) {
// Your UI here
return Container();
}
}

3. Error Handling Patterns

Future<T?> safeApiCall<T>(Future<T> Function() apiCall) async {
try {
return await apiCall();
} catch (e) {
print('API call failed: $e');

// Show user-friendly error message
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Something went wrong. Please try again.')),
);

return null;
}
}

// Usage
final users = await safeApiCall(() =>
cocobase.listDocuments<Map<String, dynamic>>('users')
);

if (users != null) {
setState(() {
this.users = users;
});
}

The CocoBASE Dart SDK provides everything you need to build robust Flutter applications with real-time capabilities, secure authentication, and seamless database operations.