Github|...

Flutter Guide

Sp00ky provides Dart code generation for Flutter integration, allowing for natively typed data models.

Note

The Flutter/Dart client is currently in development. This guide shows the generated Dart models and how to use them with SurrealDB’s Dart client.

Generate Dart Models

Use the Sp00ky CLI to generate Dart models from your SurrealDB schema:

Bash
# Recommended: drive from sp00ky.yml's clientTypes entries
spky generate

# One-shot variant
spky --input ./schema/src/schema.surql --output ./lib/models.dart --format dart

This generates Dart classes with JSON serialization for your schema tables.

Generated Models Example

The CLI generates strongly-typed Dart classes for each table in your schema:

lib/models.dart
dart
// Auto-generated by Sp00ky CLI
import 'dart:convert';

class Comment {
  /// Record ID of table: user
  String author;
  
  /// Assert: $value != NONE AND string::len($value) > 0
  String content;
  
  DateTime? createdAt;
  
  /// Record ID
  String id;
  
  /// Record ID of table: thread
  String threadId;

  Comment({
    required this.author,
    required this.content,
    this.createdAt,
    required this.id,
    required this.threadId,
  });

  factory Comment.fromJson(Map<String, dynamic> json) => Comment(
    author: json["author"],
    content: json["content"],
    createdAt: json["created_at"] == null 
      ? null 
      : DateTime.parse(json["created_at"]),
    id: json["id"],
    threadId: json["thread_id"],
  );

  Map<String, dynamic> toJson() => {
    "author": author,
    "content": content,
    "created_at": createdAt?.toIso8601String(),
    "id": id,
    "thread_id": threadId,
  };
}

class Thread {
  String author;
  String content;
  DateTime? createdAt;
  String id;
  String title;

  Thread({
    required this.author,
    required this.content,
    this.createdAt,
    required this.id,
    required this.title,
  });

  factory Thread.fromJson(Map<String, dynamic> json) => Thread(
    author: json["author"],
    content: json["content"],
    createdAt: json["created_at"] == null 
      ? null 
      : DateTime.parse(json["created_at"]),
    id: json["id"],
    title: json["title"],
  );

  Map<String, dynamic> toJson() => {
    "author": author,
    "content": content,
    "created_at": createdAt?.toIso8601String(),
    "id": id,
    "title": title,
  };
}

// SURQL Schema constant
const String SURQL_SCHEMA = """
DEFINE TABLE thread SCHEMAFULL
  PERMISSIONS
    FOR select WHERE true
    FOR update, delete, create WHERE $access = "account" AND author.id = $auth.id;
    
DEFINE FIELD title ON TABLE thread TYPE string
  ASSERT $value != NONE AND string::len($value) > 0;
// ... etc
""";

Usage with SurrealDB Dart Client

Use the generated models with the official SurrealDB Dart client:

lib/database.dart
dart
import 'package:surrealdb/surrealdb.dart';
import 'models.dart';

class Database {
  late Surreal db;
  
  Future<void> init() async {
    db = Surreal();
    // `spky dev` exposes SurrealDB on 8666 by default.
    await db.connect('ws://localhost:8666/rpc');
    await db.use('main', 'main');
  }
  
  // Query threads
  Future<List<Thread>> getThreads() async {
    final result = await db.query(
      'SELECT * FROM thread ORDER BY created_at DESC LIMIT 20'
    );
    
    final List<dynamic> data = result.first;
    return data.map((json) => Thread.fromJson(json)).toList();
  }
  
  // Create a thread
  Future<Thread> createThread({
    required String title,
    required String content,
    required String authorId,
  }) async {
    final result = await db.create('thread', {
      'title': title,
      'content': content,
      'author': authorId,
      'created_at': DateTime.now().toIso8601String(),
    });
    
    return Thread.fromJson(result.first);
  }
  
  // Update a thread
  Future<void> updateThread(String id, Map<String, dynamic> updates) async {
    await db.merge(id, updates);
  }
  
  // Delete a thread
  Future<void> deleteThread(String id) async {
    await db.delete(id);
  }
  
  // Authenticate
  Future<void> signIn(String username, String password) async {
    await db.signin({
      'AC': 'account',
      'username': username,
      'password': password,
    });
  }
  
  Future<void> signUp(String username, String password) async {
    await db.signup({
      'AC': 'account',
      'username': username,
      'password': password,
    });
  }
}

Flutter UI Example

Here’s how to use the database in a Flutter widget:

lib/screens/threads_screen.dart
dart
import 'package:flutter/material.dart';
import '../database.dart';
import '../models.dart';

class ThreadsScreen extends StatefulWidget {
  @override
  _ThreadsScreenState createState() => _ThreadsScreenState();
}

class _ThreadsScreenState extends State<ThreadsScreen> {
  final Database _db = Database();
  List<Thread> _threads = [];
  bool _isLoading = true;
  
  @override
  void initState() {
    super.initState();
    _loadThreads();
  }
  
  Future<void> _loadThreads() async {
    try {
      await _db.init();
      final threads = await _db.getThreads();
      setState(() {
        _threads = threads;
        _isLoading = false;
      });
    } catch (e) {
      print('Error loading threads: $e');
      setState(() => _isLoading = false);
    }
  }
  
  @override
  Widget build(BuildContext context) {
    if (_isLoading) {
      return Scaffold(
        body: Center(child: CircularProgressIndicator()),
      );
    }
    
    return Scaffold(
      appBar: AppBar(title: Text('Threads')),
      body: ListView.builder(
        itemCount: _threads.length,
        itemBuilder: (context, index) {
          final thread = _threads[index];
          return ListTile(
            title: Text(thread.title),
            subtitle: Text(thread.content),
            trailing: Text(
              thread.createdAt?.toString() ?? '',
              style: TextStyle(fontSize: 12),
            ),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _createThread,
        child: Icon(Icons.add),
      ),
    );
  }
  
  Future<void> _createThread() async {
    // Show dialog and create thread
    // Implementation details omitted for brevity
  }
}

Next Steps

  • Check out the SurrealDB Dart client documentation for more details
  • Explore state management solutions like Riverpod for reactive data binding
  • Consider implementing real-time updates using SurrealDB’s LIVE queries