synquill 0.5.5+1 copy "synquill: ^0.5.5+1" to clipboard
synquill: ^0.5.5+1 copied to clipboard

Offline-first Flutter data engine with background JSON API sync and custom retry logic.

Synquill #

A powerful Flutter package for offline-first data management with automatic REST API synchronization. Built on top of Drift for robust local storage and featuring intelligent sync queues for seamless online/offline operation.

pub package License: MIT

Note: Synquill is currently under active development. If you choose to use it in production, please proceed with caution and at your own risk. We encourage you to report any issues or feedback via the GitHub repository.

Why Synquill? #

Offline-First Architecture #

  • Local-first operations with Drift-powered SQLite database
  • Automatic background sync with configurable retry mechanisms
  • Smart queue management for operations with related data dependencies
  • Works completely offline - sync when connectivity returns

Intelligent Synchronization #

  • Bidirectional sync between local storage and REST APIs
  • Configurable sync policies: localFirst, remoteFirst, localThenRemote
  • Dependency-based sync ordering with hierarchical task resolution
  • Background processing capabilities, enabling integration with tools like WorkManager
  • Retry logic with exponential backoff for failed operations

Model-Driven Development #

  • Code generation for repositories, DAOs, and database tables
  • Relationship support: OneToMany and ManyToOne with cascade operations
  • JSON serialization compatibility with json_annotation
  • Type-safe queries with filtering, sorting, and pagination

Flexible API Integration #

  • Pluggable API adapters for different REST API patterns
  • Custom HTTP headers and authentication support
  • Error handling with comprehensive exception types
  • Request/response interceptors for logging and debugging

Reactive Data Streams #

  • Real-time UI updates with watchOne() and watchAll() streams
  • Repository change events for fine-grained reactivity
  • Automatic UI synchronization when data changes locally or remotely

Installation #

Add to your pubspec.yaml:

dependencies:
  json_annotation: ^4.9.0
  synquill: ^0.5.5

dev_dependencies:
  synquill_gen: ^0.5.1
  build_runner: ^2.4.14

Quick Start #

1. Define Your Models #

import 'package:synquill/synquill.dart';
import 'package:json_annotation/json_annotation.dart';

part 'user.g.dart';

@JsonSerializable()
@SynquillRepository(
  adapters: [JsonApiAdapter, UserApiAdapter],
  relations: [
    OneToMany(target: Todo, mappedBy: 'userId'),
  ],
)
class User extends SynquillDataModel<User> {
  @override
  final String id;
  final String name;
  final String email;

  User({
    String? id,
    required this.name,
    required this.email,
  }) : id = id ?? generateCuid();

  User.fromDb({
    required this.id,
    required this.name,
    required this.email,
    /// The following fields are optional. You can omit them if you do not require access.
    DateTime? createdAt,
    DateTime? updatedAt,
    SyncStatus? syncStatus,
  }) {
    /// The following fields are optional. You can omit them if you do not require access.
    this.createdAt = createdAt;
    this.updatedAt = updatedAt;
    this.syncStatus = syncStatus ?? SyncStatus.synced;
  }

  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
  
  @override
  Map<String, dynamic> toJson() => _$UserToJson(this);
}

@JsonSerializable()
@SynquillRepository(
  adapters: [JsonApiAdapter, TodoApiAdapter],
  relations: [
    ManyToOne(target: User, foreignKeyColumn: 'userId'),
  ],
)
class Todo extends SynquillDataModel<Todo> {
  @override
  final String id;
  final String title;
  final bool isCompleted;
  final String userId;

  Todo({
    String? id,
    required this.title,
    this.isCompleted = false,
    required this.userId,
  }) : id = id ?? generateCuid();

  Todo.fromDb({
    required this.id,
      required this.title,
      required this.isCompleted,
      required this.userId,
      /// Example constructor omitting createdAt and updatedAt fields
  });

  factory Todo.fromJson(Map<String, dynamic> json) => _$TodoFromJson(json);
  
  @override
  Map<String, dynamic> toJson() => _$TodoToJson(this);
}

2. Create Custom API Adapters #

// Base adapter with shared configuration
mixin JsonApiAdapter on BasicApiAdapter {
  @override
  Uri get baseUrl => Uri.parse('https://api.example.com');

  @override
  FutureOr<Map<String, String>> get baseHeaders async => {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer ${await getAuthToken()}',
  };
}

// Model-specific adapter
mixin UserApiAdapter on BasicApiAdapter<User> {
  @override
  String get type => 'user';
  
  @override
  String get pluralType => 'users';

  @override
  FutureOr<Uri> urlForFindAll({Map<String, dynamic>? extra}) async {
    return baseUrl.resolve('api/v1/users');
  }
}

mixin TodoApiAdapter on BasicApiAdapter<Todo> {
  @override
  String get type => 'todo';
  
  @override
  String get pluralType => 'todos';

  @override
  FutureOr<Uri> urlForFindAll({Map<String, dynamic>? extra}) async {
    // Context-aware URLs - todos belong to users
    final users = await SynquillStorage.instance.users
        .findAll(loadPolicy: DataLoadPolicy.localOnly);
    
    if (users.isNotEmpty) {
      return baseUrl.resolve('api/v1/users/${users.first.id}/$pluralType');
    }
    return baseUrl.resolve('api/v1/$pluralType');
  }
}

3. Initialize the Storage System #

import 'package:path_provider/path_provider.dart';
import 'package:synquill/synquill.generated.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // Initialize database
  final database = SynquillDatabase(
      LazyDatabase(
        () => driftDatabase(
          name: 'synquill.db',
          native: DriftNativeOptions(
            shareAcrossIsolates: true, // to sync between main thread and background isolate
            databaseDirectory: getApplicationSupportDirectory,
          ),
        ),
      ),
    );
  
  // Configure and initialize Synquill
  await SynquillStorage.init(
    database: database,
    config: const SynquillStorageConfig(
      defaultSavePolicy: DataSavePolicy.localFirst,
      defaultLoadPolicy: DataLoadPolicy.localThenRemote,
      foregroundQueueConcurrency: 3,
      backgroundQueueConcurrency: 1,
    ),
    logger: Logger('MyApp'),
    initializeFn: initializeSynquillStorage,
    // provide your own connectivity stream and check function
    connectivityChecker: () async =>
        await InternetConnection().hasInternetAccess,
    connectivityStream: InternetConnection()
        .onStatusChange
        .map((status) => status == InternetStatus.connected),
  );

  runApp(MyApp());
}

Documentation #

For comprehensive documentation, guides, and advanced features, please visit the documentation directory.

Contributing #

Contributions are welcome! Please feel free to submit pull requests, open issues, or ask questions on GitHub.

License #

This project is licensed under the MIT License - see the LICENSE file for details.

Note: This package is inspired by flutter_data, a fantastic data layer solution for Flutter applications.


Built with ❤️ for the Flutter community

6
likes
150
points
160
downloads

Publisher

verified publishersynquill.dev

Weekly Downloads

Offline-first Flutter data engine with background JSON API sync and custom retry logic.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

cuid2, dio, drift, json_annotation, logging, meta, queue, stream_transform, synquill_utils

More

Packages that depend on synquill