flutter-databases

MVVM-compliant data layer for Flutter with Repository pattern, stateless Services, and intelligent local caching strategies. Guides selection of persistence technology (shared_preferences, sqflite, drift, hive_ce, isar_community, or file I/O) based on data type, size, and relational complexity Implements Repository as single source of truth, isolating DatabaseService and ApiClient as private stateless dependencies Provides complete code examples for domain models, SQLite operations, and offline-first sync patterns with parameterized queries to prevent SQL injection Enforces architectural constraints: UI layer accesses only Repository, services remain stateless, database connection verified before operations

INSTALLATION
npx skills add https://github.com/flutter/skills --skill flutter-databases
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

$2a

Instructions

-

Analyze Data Requirements

STOP AND ASK THE USER: "What specific data entities need to be managed in the data layer, and what are their persistence requirements (e.g., size, relational complexity, offline-first capabilities)?"

Wait for the user's response before proceeding to step 2.

-

Configure Dependencies

Based on the decision logic, add the required dependencies. For a standard SQLite implementation, execute:

flutter pub add sqflite path

-

Define Domain Models

Create pure Dart data classes representing the domain models. These models should contain only the information needed by the rest of the app.

class Todo {

  final int? id;

  final String title;

  final bool isCompleted;

  const Todo({this.id, required this.title, required this.isCompleted});

  Map<String, dynamic> toMap() {

    return {

      'id': id,

      'title': title,

      'isCompleted': isCompleted ? 1 : 0,

    };

  }

  factory Todo.fromMap(Map<String, dynamic> map) {

    return Todo(

      id: map['id'] as int?,

      title: map['title'] as String,

      isCompleted: map['isCompleted'] == 1,

    );

  }

}

-

Implement the Database Service

Create a stateless service class to handle direct interactions with the SQLite database.

import 'package:path/path.dart';

import 'package:sqflite/sqflite.dart';

class DatabaseService {

  Database? _database;

  Future<void> open() async {

    if (_database != null &#x26;&#x26; _database!.isOpen) return;

    _database = await openDatabase(

      join(await getDatabasesPath(), 'app_database.db'),

      onCreate: (db, version) {

        return db.execute(

          'CREATE TABLE todos(id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, isCompleted INTEGER)',

        );

      },

      version: 1,

    );

  }

  bool get isOpen => _database != null &#x26;&#x26; _database!.isOpen;

  Future<int> insertTodo(Todo todo) async {

    return await _database!.insert(

      'todos',

      todo.toMap(),

      conflictAlgorithm: ConflictAlgorithm.replace,

    );

  }

  Future<List<Todo>> fetchTodos() async {

    final List<Map<String, dynamic>> maps = await _database!.query('todos');

    return maps.map((map) => Todo.fromMap(map)).toList();

  }

  Future<void> deleteTodo(int id) async {

    await _database!.delete(

      'todos',

      where: 'id = ?',

      whereArgs: [id],

    );

  }

}

-

Implement the API Client Service (Optional/If Applicable)

Create a stateless service for remote data fetching.

class ApiClient {

  Future<List<dynamic>> fetchRawTodos() async {

    // Implementation for HTTP GET request

    return [];

  }

}

-

Implement the Repository

Create the Repository class. This is the single source of truth for the application data. It must encapsulate the services as private members.

class TodoRepository {

  final DatabaseService _databaseService;

  final ApiClient _apiClient;

  TodoRepository({

    required DatabaseService databaseService,

    required ApiClient apiClient,

  })  : _databaseService = databaseService,

        _apiClient = apiClient;

  Future<List<Todo>> getTodos() async {

    await _ensureDbOpen();

    // Example of offline-first logic: fetch local, optionally sync with remote

    return await _databaseService.fetchTodos();

  }

  Future<void> createTodo(Todo todo) async {

    await _ensureDbOpen();

    await _databaseService.insertTodo(todo);

    // Trigger API sync here if necessary

  }

  Future<void> removeTodo(int id) async {

    await _ensureDbOpen();

    await _databaseService.deleteTodo(id);

  }

  Future<void> _ensureDbOpen() async {

    if (!_databaseService.isOpen) {

      await _databaseService.open();

    }

  }

}

-

Validate-and-Fix

Review the generated implementation against the following checks:

  • Check: Are the services (_databaseService, _apiClient) private members of the Repository? If not, refactor to restrict UI layer access.
  • Check: Does the Repository explicitly ensure the database is open before executing queries? If not, inject the _ensureDbOpen() pattern.
  • Check: Are primary keys (id) used effectively in SQLite queries to optimize update/delete times?

Constraints

  • Single Source of Truth: The UI layer MUST NEVER interact directly with a Service (e.g., DatabaseService or ApiClient). All data requests must route through the Repository.
  • Stateless Services: Service classes must remain stateless and contain no side effects outside of their specific external API/DB wrapper responsibilities.
  • Domain Model Isolation: Repositories must transform raw data (from APIs or DBs) into Domain Models before passing them to the UI layer.
  • SQL Injection Prevention: Always use parameterized queries (e.g., whereArgs: [id]) in sqflite operations. Never use string interpolation for SQL queries.
  • Database State: The Repository must guarantee the database connection is open before attempting any read/write operations.
BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card