offline_outbox

Pub Version Pub Points Pub Likes CI License

A resilient, offline-first transactional outbox and retry queue for Flutter and Dart with persistent disk storage, exponential backoff with jitter, priority scheduling, and idempotency deduplication.

offline_outbox demo


⚑ Why offline_outbox?

In mobile development, network connections flicker constantly. Standard in-memory retry libraries fail when the app process is terminated or the phone restarts.

offline_outbox provides the Transactional Outbox Pattern for Flutter:

  1. Never Lose a Mutation: When the user places an order, sends a message, or likes a post, the request is written to persistent disk storage before network delivery.
  2. Exponential Backoff with Random Jitter: Failed network requests automatically back off exponentially with randomized jitter to prevent server thundering-herd spikes on reconnect.
  3. Idempotency & Deduplication: Prevents duplicate charges or double orders by checking unique idempotencyKey values before enqueueing.
  4. 4-Tier Priority Scheduling: OutboxPriority.critical requests (payments, bookings) are drained before OutboxPriority.low requests (analytics, logs).
  5. Storage & HTTP Agnostic: Works seamlessly with Dio, package:http, Hive, SQLite, or the built-in FileJsonOutboxStorage.

πŸ“¦ Installation

Add offline_outbox to your pubspec.yaml:

dependencies:
  offline_outbox: ^1.0.0

Or run:

flutter pub add offline_outbox

πŸš€ Quick Start

1. Initialize Engine

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:offline_outbox/offline_outbox.dart';

// Create storage (File-based, Hive, or InMemory)
final storage = FileJsonOutboxStorage(File('/path/to/outbox.json'));

final outbox = OfflineOutboxEngine(
  storage: storage,
  retryPolicy: const RetryPolicy(
    maxAttempts: 5,
    initialDelay: Duration(seconds: 2),
    maxDelay: Duration(minutes: 2),
    backoffMultiplier: 2.0,
    enableJitter: true,
  ),
  executor: (request) async {
    // Perform your actual HTTP / Dio request here:
    // final response = await dio.request(request.endpoint, data: request.payload, ...);
    // return response.statusCode == 200;
    return true; // Return true on success, false/throw on retryable error
  },
);

await outbox.init();

2. Enqueue an Offline Request

await outbox.enqueue(
  OutboxRequest(
    id: 'req_${DateTime.now().millisecondsSinceEpoch}',
    endpoint: '/api/v1/orders',
    method: 'POST',
    payload: {'item_id': 101, 'quantity': 2},
    priority: OutboxPriority.critical,
    idempotencyKey: 'order_uuid_987654',
  ),
);

3. Process & Drain Queue

// Drain the queue manually (e.g. on connectivity reconnect):
final int deliveredCount = await outbox.processQueue();

// Or enable background periodic auto-sync:
outbox.startAutoSync(interval: const Duration(seconds: 15));

4. Real-time Status Stream

// Listen to live pending items count:
outbox.pendingCountStream.listen((count) {
  print('Pending outbox items: $count');
});

// Listen to delivered requests:
outbox.onCompletedStream.listen((req) {
  print('Delivered: ${req.endpoint}');
});

πŸ› οΈ API Reference

OutboxRequest Parameters

Field Type Default Description
id String Required Unique item identifier.
endpoint String Required Target API URL or path.
method String 'POST' HTTP method (GET, POST, PUT, DELETE, PATCH).
payload dynamic null JSON body payload or request data.
headers Map<String, String>? null HTTP headers.
idempotencyKey String? null Key to prevent duplicate requests.
priority OutboxPriority normal critical, high, normal, or low.

πŸ‘¨πŸ’» Author & Maintainer

Developed and maintained by Govind Tank.

Issues, feedback, and pull requests are welcomed on GitHub!


πŸ“„ License

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

Libraries

offline_outbox
Resilient offline-first transactional outbox and retry queue for Dart and Flutter.