easy_rest_sync 0.0.1 copy "easy_rest_sync: ^0.0.1" to clipboard
easy_rest_sync: ^0.0.1 copied to clipboard

An offline-first REST API synchronization package for Flutter with hidden local storage, reactive data, outbox queue, retry handling, cursor-based sync, and conflict management.

example/lib/main.dart

import 'package:easy_rest_sync/easy_rest_sync.dart';
import 'package:flutter/material.dart';

late final EasyRestSync easySync;

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  easySync = EasyRestSync(
    config: EasyRestSyncConfig(
      baseUrl: 'https://your-domain.com/api',
      pushEndpoint: '/sync/push',
      pullEndpoint: '/sync/pull',
      scope: 'user_506618',
      autoSyncOnWrite: false,
      syncOnStart: false,
      tokenProvider: () async {
        // Return your login token here.
        return null;
      },
      conflictStrategy: EasySyncConflictStrategy.serverWins,
    ),
  );

  easySync.registerEntity<Product>(
    EasySyncEntity<Product>(
      name: 'products',
      idSelector: (product) => product.id,
      toJson: (product) => product.toJson(),
      fromJson: Product.fromJson,
    ),
  );

  await easySync.initialize();
  runApp(const EasySyncExampleApp());
}

class Product {
  final String id;
  final String itemCode;
  final String itemName;
  final double quantity;

  const Product({
    required this.id,
    required this.itemCode,
    required this.itemName,
    required this.quantity,
  });

  factory Product.fromJson(Map<String, dynamic> json) {
    return Product(
      id: json['id']?.toString() ?? '',
      itemCode: json['itemCode']?.toString() ?? '',
      itemName: json['itemName']?.toString() ?? '',
      quantity: double.tryParse(json['quantity']?.toString() ?? '0') ?? 0,
    );
  }

  Map<String, dynamic> toJson() {
    return <String, dynamic>{
      'id': id,
      'itemCode': itemCode,
      'itemName': itemName,
      'quantity': quantity,
    };
  }

  Product copyWith({
    String? id,
    String? itemCode,
    String? itemName,
    double? quantity,
  }) {
    return Product(
      id: id ?? this.id,
      itemCode: itemCode ?? this.itemCode,
      itemName: itemName ?? this.itemName,
      quantity: quantity ?? this.quantity,
    );
  }
}

class EasySyncExampleApp extends StatelessWidget {
  const EasySyncExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Easy REST Sync Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
        useMaterial3: true,
      ),
      home: const ProductPage(),
    );
  }
}

class ProductPage extends StatefulWidget {
  const ProductPage({super.key});

  @override
  State<ProductPage> createState() => _ProductPageState();
}

class _ProductPageState extends State<ProductPage> {
  final TextEditingController _itemCodeController = TextEditingController();
  final TextEditingController _itemNameController = TextEditingController();
  final TextEditingController _quantityController = TextEditingController();

  String? _editingId;
  bool _saving = false;
  String? _message;

  @override
  void dispose() {
    _itemCodeController.dispose();
    _itemNameController.dispose();
    _quantityController.dispose();
    super.dispose();
  }

  Future<void> _saveProduct() async {
    final itemCode = _itemCodeController.text.trim();
    final itemName = _itemNameController.text.trim();
    final quantity = double.tryParse(_quantityController.text.trim());

    if (itemCode.isEmpty || itemName.isEmpty || quantity == null) {
      setState(() {
        _message = 'Item code, item name and valid quantity are required.';
      });
      return;
    }

    setState(() {
      _saving = true;
      _message = null;
    });

    try {
      final product = Product(
        id: _editingId ?? DateTime.now().microsecondsSinceEpoch.toString(),
        itemCode: itemCode,
        itemName: itemName,
        quantity: quantity,
      );

      await easySync.save<Product>(
        entity: 'products',
        data: product,
      );

      _clearForm();
      setState(() {
        _message = 'Saved locally. Press Sync to send it to the server.';
      });
    } catch (error) {
      setState(() {
        _message = error.toString();
      });
    } finally {
      if (mounted) {
        setState(() {
          _saving = false;
        });
      }
    }
  }

  void _editProduct(Product product) {
    setState(() {
      _editingId = product.id;
      _itemCodeController.text = product.itemCode;
      _itemNameController.text = product.itemName;
      _quantityController.text = product.quantity.toString();
      _message = null;
    });
  }

  Future<void> _deleteProduct(Product product) async {
    await easySync.delete(
      entity: 'products',
      id: product.id,
    );

    if (_editingId == product.id) {
      _clearForm();
    }
  }

  Future<void> _syncNow() async {
    final result = await easySync.sync();
    if (!mounted) return;

    setState(() {
      _message = result.isSuccess
          ? 'Sync complete: ${result.pushedCount} pushed, '
          '${result.pulledCount} pulled, '
          '${result.pendingCount} pending.'
          : 'Sync failed: ${result.message}';
    });
  }

  void _clearForm() {
    _editingId = null;
    _itemCodeController.clear();
    _itemNameController.clear();
    _quantityController.clear();
    if (mounted) setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Easy REST Sync'),
        actions: <Widget>[
          StreamBuilder<EasySyncStatus>(
            stream: easySync.statusStream,
            initialData: easySync.status,
            builder: (context, snapshot) {
              final status = snapshot.data ?? EasySyncStatus.idle;
              return Padding(
                padding: const EdgeInsets.symmetric(horizontal: 12),
                child: Center(child: Text(status.name)),
              );
            },
          ),
          IconButton(
            onPressed: _syncNow,
            tooltip: 'Sync now',
            icon: const Icon(Icons.sync),
          ),
        ],
      ),
      body: SafeArea(
        child: Column(
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                children: <Widget>[
                  TextField(
                    controller: _itemCodeController,
                    decoration: const InputDecoration(
                      labelText: 'Item code',
                      border: OutlineInputBorder(),
                    ),
                  ),
                  const SizedBox(height: 10),
                  TextField(
                    controller: _itemNameController,
                    decoration: const InputDecoration(
                      labelText: 'Item name',
                      border: OutlineInputBorder(),
                    ),
                  ),
                  const SizedBox(height: 10),
                  TextField(
                    controller: _quantityController,
                    keyboardType: const TextInputType.numberWithOptions(
                      decimal: true,
                    ),
                    decoration: const InputDecoration(
                      labelText: 'Quantity',
                      border: OutlineInputBorder(),
                    ),
                  ),
                  const SizedBox(height: 12),
                  Row(
                    children: <Widget>[
                      Expanded(
                        child: FilledButton.icon(
                          onPressed: _saving ? null : _saveProduct,
                          icon: const Icon(Icons.save),
                          label: Text(
                            _editingId == null ? 'Save' : 'Update',
                          ),
                        ),
                      ),
                      if (_editingId != null) ...<Widget>[
                        const SizedBox(width: 10),
                        OutlinedButton(
                          onPressed: _clearForm,
                          child: const Text('Cancel'),
                        ),
                      ],
                    ],
                  ),
                  if (_message != null) ...<Widget>[
                    const SizedBox(height: 10),
                    Align(
                      alignment: Alignment.centerLeft,
                      child: Text(_message!),
                    ),
                  ],
                ],
              ),
            ),
            const Divider(height: 1),
            Expanded(
              child: StreamBuilder<List<Product>>(
                stream: easySync.watchList<Product>(
                  entity: 'products',
                  sort: (a, b) => a.itemName.compareTo(b.itemName),
                ),
                builder: (context, snapshot) {
                  if (snapshot.hasError) {
                    return Center(child: Text(snapshot.error.toString()));
                  }

                  final products = snapshot.data ?? const <Product>[];
                  if (products.isEmpty) {
                    return const Center(child: Text('No local product found.'));
                  }

                  return ListView.separated(
                    itemCount: products.length,
                    separatorBuilder: (_, __) => const Divider(height: 1),
                    itemBuilder: (context, index) {
                      final product = products[index];
                      return ListTile(
                        onTap: () => _editProduct(product),
                        title: Text(product.itemName),
                        subtitle: Text(
                          'Code: ${product.itemCode} | Qty: ${product.quantity}',
                        ),
                        trailing: IconButton(
                          onPressed: () => _deleteProduct(product),
                          icon: const Icon(Icons.delete_outline),
                        ),
                      );
                    },
                  );
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}
1
likes
140
points
28
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

An offline-first REST API synchronization package for Flutter with hidden local storage, reactive data, outbox queue, retry handling, cursor-based sync, and conflict management.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

dio, flutter, hive_ce_flutter, uuid

More

Packages that depend on easy_rest_sync