app_checkpoint 0.1.0 copy "app_checkpoint: ^0.1.0" to clipboard
app_checkpoint: ^0.1.0 copied to clipboard

A Flutter package for checkpointing and restoring app state.

Checkpoint #

Flutter State Snapshot & Restore SDK A developer-only Flutter package for capturing, serializing, and restoring logical app state to reproduce bugs and debug state-related issues.

Goal #

Provide a dev-only Flutter package that can:

  • Capture a snapshot of logical app state
  • Serialize it deterministically
  • Restore it later to reproduce bugs
  • Persist snapshots for later use

This is not UI screenshots, not widget trees, not production persistence.

Design Philosophy #

State is opt-in, explicit, and owned by the developer.

Checkpoint does NOT crawl memory.
Checkpoint does NOT guess state.
Checkpoint exposes an API that developers consciously wire.

This keeps it:

  • Predictable - You know exactly what's being captured
  • Testable - Explicit contracts make testing straightforward
  • Safe - No magic means no surprises
  • Acceptable to real teams - Transparent and maintainable

Scope (V1) #

What V1 Supports #

  • ✅ App-level state (logical, serializable)
  • ✅ Explicit opt-in state registration
  • ✅ Deterministic snapshot creation
  • ✅ JSON export/import
  • ✅ Programmatic restore
  • ✅ State validation
  • ✅ Persistence utilities
  • ✅ Restore lifecycle hooks
  • ✅ Dev / debug only

Getting Started #

Note: This package is currently in early development. API is subject to change.

Prerequisites #

  • Flutter SDK >= 3.10.7
  • Dart SDK >= 3.10.7

Installation #

Add app_checkpoint to your pubspec.yaml:

dependencies:
  app_checkpoint:
    git:
      url: https://github.com/vsevex/checkpoint.git

Or when published to pub.dev:

dependencies:
  app_checkpoint: ^0.1.0

Quick Start #

Enable checkpoint (must be done in an assert block):

void main() {
  assert(() {
    StateSnapshot.enable();
    return true;
  }());

  runApp(MyApp());
}

Register state contributors:

class UserState {
  String name = '';
  int age = 0;

  Map<String, dynamic> toJson() => {
    'name': name,
    'age': age,
  };

  void restore(Map<String, dynamic> json) {
    name = json['name'] as String? ?? '';
    age = json['age'] as int? ?? 0;
  }
}

final userState = UserState();

// Register the state
StateSnapshot.register<UserState>(
  key: 'user_state',
  exporter: () => userState.toJson(),
  importer: (json) => userState.restore(json),
);

Capture a snapshot:

final snapshot = await StateSnapshot.capture(appVersion: '1.0.0');
final jsonString = snapshot.toJsonString(pretty: true);
print(jsonString);

Restore from snapshot:

await StateSnapshot.restoreFromJson(jsonString);

Public API #

Enabling Checkpoint #

Checkpoint must be enabled in an assert block to ensure zero production overhead:

assert(() {
  StateSnapshot.enable();
  return true;
}());

In release builds, assert statements are removed, ensuring zero overhead.

Registering State #

Register state contributors that define how to export and import state:

StateSnapshot.register<T>(
  key: 'unique_key',
  exporter: () => Map<String, dynamic>,
  importer: (Map<String, dynamic> json) => void,
);

Requirements:

  • Keys must be unique
  • Exporter must be pure (no side effects, deterministic)
  • Importer must be idempotent (safe to call multiple times)
  • Both functions must work with JSON-serializable data only

Capturing Snapshots #

final snapshot = await StateSnapshot.capture(appVersion: '1.0.0');

The snapshot contains:

  • timestamp - UTC timestamp when captured
  • appVersion - Version string of the app
  • schemaVersion - Schema version (currently 1)
  • states - Map of all registered state data

Deterministic Behavior: State is collected in alphabetical order of keys, ensuring deterministic snapshot creation regardless of registration order.

Serializing Snapshots #

// Get JSON map
final json = snapshot.toJson();

// Get JSON string (compact)
final jsonString = snapshot.toJsonString();

// Get JSON string (pretty-printed)
final prettyJson = snapshot.toJsonString(pretty: true);

Restoring State #

// From JSON string
await StateSnapshot.restoreFromJson(jsonString);

// From JSON map
await StateSnapshot.restoreFromJsonMap(jsonMap);

Restore Behavior:

  • State is restored in alphabetical order of keys (deterministic)
  • All restore operations are logged
  • Runs on main isolate
  • Blocking/awaitable operation

Restore Lifecycle Hooks #

Add callbacks to be notified before and after restore:

// Before restore starts
StateSnapshot.onBeforeRestore.add(() {
  print('About to restore state');
});

// After restore completes
StateSnapshot.onAfterRestore.add(() {
  print('State restored successfully');
});

Multiple listeners are supported, and errors in hooks don't prevent restore.

Persistence #

Snapshots can be persisted using storage utilities:

In-Memory Storage (for testing)

final storage = MemorySnapshotStorage();

await storage.save(snapshot, 'my_snapshot');
final loaded = await storage.load('my_snapshot');
final keys = await storage.listKeys();
await storage.delete('my_snapshot');
await storage.clear();

File-Based Storage (for real persistence)

import 'package:path_provider/path_provider.dart';

final dir = await getApplicationDocumentsDirectory();
final storage = FileSnapshotStorage(
  directory: Directory('${dir.path}/checkpoints'),
);

// Save a snapshot
await storage.save(snapshot, 'my_snapshot');

// Load a snapshot
final loaded = await storage.load('my_snapshot');
if (loaded != null) {
  await StateSnapshot.restoreFromJson(loaded.toJsonString());
}

// List all saved snapshots
final keys = await storage.listKeys();

// Delete a snapshot
await storage.delete('my_snapshot');

// Clear all snapshots
await storage.clear();

File Storage Features:

  • Automatically creates directory if it doesn't exist
  • Saves snapshots as JSON files
  • Filters out non-JSON files when listing
  • Handles file operations with proper error handling

Snapshot Data Model #

{
  "schemaVersion": 1,
  "appVersion": "1.2.3",
  "timestamp": "2026-01-26T12:00:00.000Z",
  "states": {
    "user_state": {
      "name": "Vsevolod",
      "age": 23
    },
    "settings_state": {
      "theme": "dark",
      "language": "en"
    }
  }
}

Design principles:

  • Simple, flat structure
  • No circular references
  • JSON-serializable only
  • Versioned schema for future compatibility

State Safety Rules #

The package automatically validates exported state to ensure it's JSON-serializable.

✅ Allowed Types #

  • Map<String, dynamic>
  • List
  • String
  • num (int, double)
  • bool
  • null

❌ Restricted Types #

  • BuildContext or widget references
  • Streams, Controllers, Futures
  • Platform handles (File, Socket, Database, etc.)
  • Closures or functions
  • Circular references

Validation: State is automatically validated during capture. If validation fails, a StateError is thrown with a clear error message indicating the problematic path and type.

Example of invalid state:

// ❌ BAD - Contains BuildContext
exporter: () => {'context': context}

// ❌ BAD - Contains Stream
exporter: () => {'stream': myStream}

// ✅ GOOD - Only JSON-serializable data
exporter: () => {'name': 'John', 'age': 30}

Core Architecture #

Components #

StateSnapshot

Main entry point providing static methods for enabling, registering, capturing, and restoring.

SnapshotRegistry

Central registry mapping string keys to state handlers (exporter/importer pairs). Enforces unique keys and provides thread-safe operations.

SnapshotManager

Orchestrates snapshot capture and restore logic. Coordinates with SnapshotRegistry to collect state in deterministic order.

SnapshotSerializer

Converts snapshots to/from JSON with versioned schema support. Handles schema validation and future migration support.

StateValidator

Validates that exported state is JSON-serializable and safe. Detects non-serializable types and circular references.

SnapshotStorage

Abstract interface for persistence. Implementations include:

  • MemorySnapshotStorage - In-memory storage for testing
  • FileSnapshotStorage - File-based persistence

Restore Semantics #

Restore operations:

  • Execute on main isolate
  • Are blocking/awaitable
  • Restore state in deterministic order (alphabetical by key)
  • Log all operations for debugging
  • Support lifecycle hooks

Dev-Only Guardrails #

Hard guard to ensure zero overhead in release and no accidental prod usage:

assert(() {
  StateSnapshot.enable();
  return true;
}());

This ensures:

  • Zero overhead in release builds (assert statements are removed)
  • No accidental production usage
  • Explicit opt-in design

Testing #

The package includes comprehensive tests covering:

  • State registration and validation
  • Snapshot capture and serialization
  • State restoration and hooks
  • Storage persistence (memory and file)
  • Error handling and edge cases

Run tests:

flutter test

Example App #

See the example/ directory for a complete example app demonstrating:

  • Multiple state contributors
  • Capture and restore functionality
  • Persistence with file storage
  • Restore hooks
  • Interactive UI for testing

Why This Exists #

Debugging state-related bugs in Flutter applications is notoriously difficult. When a user reports a bug, reproducing the exact state that led to the issue is often impossible. Checkpoint solves this by providing a deterministic way to capture and restore application state.

Key benefits:

  • Reproduce bugs with exact state snapshots
  • Debug state transitions in isolation
  • Test state restoration scenarios
  • Share bug reproduction cases with team members
  • Persist snapshots for later analysis

What makes Checkpoint different:

  • Explicit, opt-in design (no magic)
  • Developer-controlled state registration
  • Deterministic serialization
  • Automatic state validation
  • Dev-only, zero production overhead
  • Flexible persistence options

Limitations #

What Checkpoint Cannot Do #

  • ❌ Capture widget tree state automatically
  • ❌ Automatically discover state
  • ❌ Handle non-serializable objects (Streams, Controllers, BuildContext, etc.)
  • ❌ Work in production builds (dev-only by design)
  • ❌ Provide UI for state inspection (use example app or build your own)
  • ❌ Handle circular references or complex object graphs

Developer Responsibility #

Checkpoint is a tool, not a solution. Developers must:

  • Explicitly register state they want to capture
  • Ensure state is JSON-serializable
  • Handle state restoration in their application logic
  • Test restore scenarios thoroughly
  • Choose appropriate persistence strategy

The package follows an explicit, opt-in model where developers register state contributors that define how to export and import their application state.

Development Status #

This package is in active development. The V1 scope is focused on providing a solid foundation for state snapshot and restore functionality.

Contributing #

Contributions are welcome! Please ensure any changes align with the core design philosophy of explicit, opt-in state management.

License #

See LICENSE file for details.

1
likes
0
points
29
downloads

Publisher

verified publishervsevex.me

Weekly Downloads

A Flutter package for checkpointing and restoring app state.

Repository (GitHub)
View/report issues

Topics

#state #snapshot #restore #debugging

License

unknown (license)

Dependencies

flutter, meta

More

Packages that depend on app_checkpoint