Work with databases in a Flutter app
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 flutter-data-layer-persistence Goal Architects and implements a robust, MVVM-compliant data layer in Flutter applications. Establishes a single source of truth using the Repository pattern, isolates external API and local database interactions into stateless Services, and implements optimal local caching strategies (e.g., SQLite via sqflite) based on data requirements. Assumes a pre-configured Flutter environment. Decision Logic Evaluate the user's data persistence requirements using the following decision tree to select the appropriate caching strategy: Is the data small, simple key-value pairs (e.g., user preferences, theme settings)? Yes: Use shared_preferences. Is the data a large, structured, relational dataset requiring fast inserts/queries? Yes: Use On-device relational databases (sqflite or drift). Is the data a large, unstructured/non-relational dataset? Yes: Use On-device non-relational databases (hive_ce or isar_community). Is the data primarily API response caching? Yes: Use a lightweight remote caching system or interceptors. Is the data primarily images? Yes: Use cached_network_image to store images on the file system. Is the data too large for shared_preferences but doesn't require querying? Yes: Use direct File System I/O.
don't have the plugin yet? install it then click "run inline in claude" again.
build a production-grade data layer in flutter using the repository pattern with mvvm compliance. this skill covers selecting the right persistence tech (shared_preferences, sqflite, drift, hive_ce, isar_community, or file i/o) based on your data shape and size, architecting a single source of truth that isolates database and api logic into stateless services, and implementing offline-first sync with proper query parameterization. use this when you need a testable, scalable data layer that keeps your ui logic clean and your business logic centralized.
required:
external connections:
environment setup:
edge cases to account for:
select persistence technology
define domain models
create stateless database service
create stateless api service
implement repository (single source of truth)
verify architectural constraints
if (!_db.isOpen) throw StateError(...))implement offline-first sync pattern
test repository and services
if data is small, simple key-value pairs (user preferences, theme settings, language): use shared_preferences. fast, no schema, zero boilerplate. limit to <10MB of total data. call SharedPreferences.getInstance() once, cache instance as singleton.
else if data is large, structured, relational (users with posts, comments, likes): use sqflite (mature, stable) or drift (type-safe, code-generated). both support schema migrations, transactions, and complex queries. start with sqflite if you want simplicity; use drift if you want compile-time safety. initialize database in main() or in a lazy singleton.
else if data is large, unstructured or non-relational (document collections, no joins): use hive_ce (pure dart, no native dependency) or isar_community (faster, supports indexes). both support nested objects out of the box. hive_ce is lighter; isar is faster at scale (100k+ records).
else if data is primarily api response caching: combine repository pattern with dio interceptors. cache successful responses in memory (with ttl) or local db. on 404/5xx, return stale cache if available. implement cache invalidation strategy (time-based or manual).
else if data is primarily images: use cached_network_image package. it handles disk caching transparently. if you need custom cache control, implement a custom image cache manager with sqflite metadata tracking.
else if data is too large for shared_preferences but doesn't require querying (blobs, large files, binary data): use direct file system i/o (dart:io File class). store files in getApplicationDocumentsDirectory() (persistent) or getTemporaryDirectory() (clearable). index file paths in a lightweight metadata db if needed.
if api auth token expires during sync: catch 401 responses in api_service. do not retry the failed request automatically. emit an AuthExpiredEvent and pause sync. let ui layer handle token refresh, then resume sync.
if network timeout occurs during api call: use Dio timeouts (connectTimeout, receiveTimeout, sendTimeout). implement exponential backoff (1s, 2s, 4s, 8s) for retries, max 3 attempts. on final timeout, fall back to cache or queue for later sync.
if database is locked (concurrent writes on sqlite): sqflite uses wal mode by default which allows concurrent readers. if you hit a locked exception, implement a mutex (Lock from synchronized package) around write operations. or use drift which handles this internally.
if result set from api or cache is empty: return an explicit empty list (not null). let the ui layer decide how to render empty state (empty view, spinner, error message). do not conflate empty result with an error.
the completed data layer produces these artifacts:
data is always returned as Future
you know this skill worked when: