pocketbase_drift 0.3.1 copy "pocketbase_drift: ^0.3.1" to clipboard
pocketbase_drift: ^0.3.1 copied to clipboard

A powerful, offline-first PocketBase client backed by a drift database.

PocketBase Drift #

A powerful, offline-first Flutter client for PocketBase, backed by the reactive persistence of Drift (the Flutter & Dart flavor of moor).

This library extends the official PocketBase Dart SDK to provide a seamless offline-first experience. It automatically caches data from your PocketBase instance into a local SQLite database, allowing your app to remain fully functional even without a network connection. Changes made while offline are queued and automatically retried when connectivity is restored.

Features #

  • Offline-First Architecture: Read, create, update, and delete records even without a network connection. The client seamlessly uses the local database as the source of truth.
  • Automatic Synchronization: Changes made while offline are automatically queued and retried when network connectivity is restored.
  • Reactive Data & UI: Build reactive user interfaces with streams that automatically update when underlying data changes, whether from a server push or a local mutation.
  • Local Caching with Drift: All collections and records are cached in a local SQLite database, providing fast, offline access to your data.
  • Powerful Local Querying: Full support for local querying, mirroring the PocketBase API. This includes:
    • Filtering: Complex filter strings are parsed into SQLite WHERE clauses.
    • Sorting: Sort results by any field with sort (e.g., -created,name).
    • Field Selection: Limit the returned fields with fields for improved performance.
    • Pagination: limit and offset are fully supported for local data.
  • Relation Expansion: Support for expanding single and multi-level relations (e.g., post.author) directly from the local cache.
  • Full-Text Search: Integrated Full-Text Search (FTS5) for performing fast, local searches across all your cached record data.
  • Authentication Persistence: User authentication state is persisted locally using shared_preferences, keeping users logged in across app sessions.
  • Cross-Platform Support: Works across all Flutter-supported platforms, including mobile (iOS, Android), web, and desktop (macOS, Windows, Linux).
  • File & Image Caching: Includes a PocketBaseImageProvider that caches images in the local database for offline display.
  • Robust & Performant: Includes optimizations for batching queries and file streaming on all platforms to handle large files efficiently.

Getting Started #

1. Add Dependencies #

Add the following packages to your pubspec.yaml:

dependencies:
  pocketbase_drift: ^0.2.2 # Use the latest version

2. Initialize the Client #

Replace a standard PocketBase client with a $PocketBase.database client. It's that simple.

- import 'package:pocketbase/pocketbase.dart';
+ import 'package:pocketbase_drift/pocketbase_drift.dart';

- final client = PocketBase('http://127.0.0.1:8090');
+ final client = $PocketBase.database('http://127.0.0.1:8090');

3. Cache the Database Schema (Enable Offline Records) #

To enable the offline caching functionality for records, you must provide the database schema to the client. This allows the local database to understand your collection structures for validation and relation expansion without needing to contact the server.

First, download your pb_schema.json file from the PocketBase Admin UI (Settings > Export collections). Then, add it to your project as an asset.

// 1. Load the schema from your assets
final schema = await rootBundle.loadString('assets/pb_schema.json');

// 2. Initialize the client and cache the schema
final client = $PocketBase.database('http://127.0.0.1:8090')
  ..cacheSchema(schema);

4. Web Setup #

For web, you need to follow the instructions for Drift on the Web to copy the sqlite3.wasm binary and drift_worker.js file into your web/ directory.

  1. Download the latest sqlite3.wasm from the sqlite3.dart releases and the latest drift_worker.js from the drift releases.
  2. Place each file inside your project's web/ folder.
  3. Rename drift_worker.js to drift_worker.dart.js.

Core Concepts #

RequestPolicy #

The RequestPolicy enum controls how data is fetched and synchronized between local cache and remote server. Choose the policy that best fits your consistency and availability requirements.

For Read Operations (GET):

  • RequestPolicy.networkFirst: Prioritizes fresh data from the server.

    • Tries to fetch from the remote server first
    • On success, updates the local cache and returns the fresh data
    • On failure (network error or offline), falls back to cached data
    • Use case: When you need the freshest data possible, but can accept stale data if network fails
  • RequestPolicy.cacheFirst: Prioritizes instant UI response.

    • Returns cached data immediately
    • Fetches from network in the background to update cache for next time
    • Use case: When UI responsiveness is critical and slightly stale data is acceptable
  • RequestPolicy.cacheAndNetwork (Default): Balances freshness and availability.

    • For one-time fetches: Same as networkFirst (tries network, falls back to cache)
    • For reactive streams (watchRecords): Emits cache immediately, then updates with network data
    • Use case: General-purpose offline-first behavior
  • RequestPolicy.cacheOnly: Only reads from local cache, never contacts server.

    • Use case: When you only want to work with locally available data
  • RequestPolicy.networkOnly: Only reads from server, never uses cache.

    • Throws an exception if network is unavailable
    • Use case: When you absolutely need fresh data and can't accept stale data

For Write Operations (CREATE/UPDATE/DELETE):

  • RequestPolicy.networkFirst: Server is the source of truth (strict consistency).

    • Writes to server first
    • On success, updates local cache
    • On failure, throws an error (NO cache fallback)
    • Use case: When data integrity is critical and you can't risk conflicts (e.g., financial transactions, inventory management)
  • RequestPolicy.cacheFirst: Local cache is the source of truth (optimistic updates).

    • Writes to cache first, returns success immediately
    • Attempts server sync in the background
    • If background sync fails, the record is marked as "pending sync" and will retry later
    • Use case: When instant UI feedback is critical (e.g., note-taking apps, drafts)
  • RequestPolicy.cacheAndNetwork (Default): Resilient offline-first.

    • Tries to write to server first
    • On success, updates local cache
    • On failure, writes to cache and marks as "pending sync" for automatic retry
    • Use case: General-purpose offline-first apps that need to work offline
  • RequestPolicy.cacheOnly: Only writes to cache, marks as noSync.

    • Records will NEVER sync to server
    • Use case: Local-only data (user preferences, temp data, offline-only features)
  • RequestPolicy.networkOnly: Only writes to server, throws on failure.

    • Never touches the cache
    • Use case: When you need immediate server confirmation

Choosing the Right Policy #

Scenario Read Policy Write Policy
Real-time collaborative app networkFirst networkFirst
Offline-first mobile app cacheAndNetwork cacheAndNetwork
Instant feedback UI (notes, drafts) cacheFirst cacheFirst
Financial transactions networkOnly networkFirst or networkOnly
Analytics/telemetry N/A cacheFirst
Local-only settings cacheOnly cacheOnly

Offline Support & Sync #

When using cacheAndNetwork or cacheFirst policies for write operations, the library automatically handles network failures:

  • When Online: Operations are sent to the server. On success, local cache is updated.
  • When Offline (or network failure):
    • cacheAndNetwork: Operation is applied to local cache and marked as "pending sync"
    • cacheFirst: Operation succeeds locally, background sync is attempted
    • In both cases, the UI responds instantly and the change will automatically sync when connectivity is restored

Usage Examples #

Fetching Records #

// Get a reactive stream of all "posts"
final stream = client.collection('posts').watchRecords();

// Get a one-time list of posts, sorted by creation date
final posts = await client.collection('posts').getFullList(
  sort: '-created',
  requestPolicy: RequestPolicy.cacheAndNetwork, // Explicitly set policy
);

// Get a single record
final post = await client.collection('posts').getOne('RECORD_ID');

Creating and Updating Records #

// Create a new record (works online and offline)
final newRecord = await client.collection('posts').create(
  body: {
    'title': 'My Offline Post',
    'content': 'This was created without a connection.',
  },
  requestPolicy: RequestPolicy.cacheAndNetwork,
);

// Update a record
await client.collection('posts').update(newRecord.id, body: {
  'content': 'The content has been updated.',
});
// Search all fields in the 'posts' collection for the word "flutter"
final results = await client.collection('posts').search('flutter').get();

// Search across all collections
final globalResults = await client.search('flutter').get();

File Handling #

The library automatically caches files for offline use.

// Use the included PocketBaseImageProvider for easy display
Image(
  image: PocketBaseImageProvider(
    client: client,
    recordId: postRecord.id, 
    recordCollectionName: postRecord.collectionName,
    filename: postRecord.get('my_image_field'), // The filename
  ),
);

// Or get the file bytes directly
final bytes = await client.files.getFileData(
  recordId: postRecord.id, 
  recordCollectionName: postRecord.collectionName, 
  fileName: postRecord.get('my_image_field'),
  requestPolicy: RequestPolicy.cacheAndNetwork,
);

Custom API Route Caching #

The library supports offline caching for custom API routes accessed via the send method. This is particularly useful for GET requests to custom endpoints that return data you want available offline.

To use it, simply call the send method on your $PocketBase client and provide a RequestPolicy.

Note: Caching is only applied to GET requests by default to prevent unintended side effects from caching state-changing operations (POST, DELETE, etc.).

// This request will be cached and available offline.
try {
  final customData = await client.send(
    '/api/my-custom-route',
    requestPolicy: RequestPolicy.cacheAndNetwork, // Use the desired policy
  );
} catch (e) {
  // Handle errors, e.g., if networkOnly fails or cache is empty
}

// This POST request will bypass the cache and go directly to the network.
await client.send(
  '/api/submit-form',
  method: 'POST',
  body: {'name': 'test'},
  // No requestPolicy needed, but even if provided, it would be ignored.
);

TODO #

  • ✅ Offline mutations and retry
  • ✅ Offline collections & records
  • ✅ Full-text search
  • ✅ Support for fields (select), sort, expand, and pagination
  • ✅ Robust file caching and streaming for large files
  • ✅ Proactive connectivity handling
  • ✅ Structured logging
  • ✅ Add support for indirect expand (e.g., post.author.avatar)
  • ✅ Add support for more complex query operators (e.g., ~ for LIKE/Contains)
  • ✅ More comprehensive test suite for edge cases

Credits #

7
likes
0
points
571
downloads

Publisher

unverified uploader

Weekly Downloads

A powerful, offline-first PocketBase client backed by a drift database.

Repository (GitHub)
View/report issues

Topics

#pocketbase #offline #cache #drift

License

unknown (license)

Dependencies

connectivity_plus, drift, flutter, http, logging, path, path_provider, pocketbase, shared_preferences, shortid, sqlite3, sqlite3_flutter_libs

More

Packages that depend on pocketbase_drift