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.