auto_save_controller 1.0.0
auto_save_controller: ^1.0.0 copied to clipboard
A Flutter TextEditingController that automatically saves and loads local text drafts with built-in debounce, TTL expiry, and AppLifecycle awareness.
auto_save_controller #
A headless, storage-agnostic Flutter package that extends
TextEditingController to automatically save, load, and manage local text
drafts — with built-in debounce, TTL (Time-To-Live), and AppLifecycle
awareness.
Features #
- Zero third-party dependencies — only the Flutter SDK.
- Storage-agnostic — bring your own backend via the
AutoSaveStorageinterface (SharedPreferences,Hive,Isar, SQLite, in-memory, …). - Debounced saves — configurable delay prevents excessive writes during active typing.
- TTL expiry — drafts older than a configurable threshold are automatically discarded on load.
- AppLifecycle aware — pending saves are flushed immediately when the app goes inactive, is paused, or detached.
- Race-condition safe — a draft is never injected if the user typed something during the async load.
AutoSaveControllertypedef — a concise alias forAutoSaveTextEditingController.- Drop-in replacement — extends
TextEditingControllerdirectly; all existing APIs continue to work.
Installation #
Add to your pubspec.yaml:
dependencies:
auto_save_controller: ^1.0.0
Then run:
flutter pub get
Getting Started #
Step 1 — Implement AutoSaveStorage #
The package ships with no storage implementation so it stays dependency-free.
Create your own adapter by implementing the AutoSaveStorage interface:
class InMemoryStorage implements AutoSaveStorage {
final Map<String, String> _store = {};
@override
Future<String?> read(String key) async => _store[key];
@override
Future<void> write(String key, String value) async {
_store[key] = value;
}
@override
Future<void> delete(String key) async => _store.remove(key);
}
See the example app for full implementations using
SharedPreferences, Hive, and Isar.
Step 2 — Register Global Storage (optional) #
Register a single default backend once at application startup:
void main() {
AutoSave.init(storage: InMemoryStorage());
runApp(const MyApp());
}
All controllers that don't receive an explicit storage argument will fall
back to this global instance.
Step 3 — Use the Controller #
class _MyFormState extends State<MyForm> {
late final AutoSaveController _controller;
@override
void initState() {
super.initState();
_controller = AutoSaveController(cacheKey: 'my_form_draft');
}
@override
void dispose() {
_controller.dispose(); // flushes any pending debounced save
super.dispose();
}
@override
Widget build(BuildContext context) {
return TextField(controller: _controller);
}
}
AutoSaveControlleris a typedef alias forAutoSaveTextEditingController. Both names refer to the same class.
Per-Controller Storage Injection #
Pass a storage argument to the constructor to bypass the global instance
for a specific controller. This is useful when different form fields should
persist to different backends, or when you want to avoid calling
AutoSave.init() entirely:
final controller = AutoSaveController(
cacheKey: 'account_bio',
storage: HiveStorage(), // overrides AutoSave.storage
debounceDuration: Duration(seconds: 1),
maxDraftAge: Duration(days: 30),
);
API Reference #
AutoSaveController (typedef) #
Alias for AutoSaveTextEditingController. Prefer this for brevity.
AutoSaveTextEditingController #
Extends TextEditingController.
| Parameter | Type | Default | Description |
|---|---|---|---|
cacheKey |
String |
required | Unique storage key for the draft. |
text |
String? |
null |
Optional initial text. |
storage |
AutoSaveStorage? |
AutoSave.storage |
Per-controller storage override. |
debounceDuration |
Duration |
500 ms |
Delay before a save is committed. |
maxDraftAge |
Duration? |
null |
TTL — expired drafts are discarded. |
maxDraftLength |
int |
10 000 |
Max chars eligible for saving. |
prioritizeDraftOverInitial |
bool |
true |
Draft vs. initial text priority. |
Methods
| Method | Description |
|---|---|
clearDraft() |
Deletes the stored draft and clears the text field. |
dispose() |
Flushes pending saves, disposes lifecycle listener, then calls super.dispose(). |
AutoSave #
Global configuration singleton.
| Member | Description |
|---|---|
AutoSave.init(storage:) |
Registers the global AutoSaveStorage backend. |
AutoSave.storage |
Returns the registered backend; throws StateError if unset. |
AutoSaveStorage #
Abstract interface for storage adapters.
abstract class AutoSaveStorage {
Future<String?> read(String key);
Future<void> write(String key, String value);
Future<void> delete(String key);
}
DraftData #
Internal model used for serialization. Exposed publicly for custom adapters that need to inspect or migrate stored data.
| Property | Type | Description |
|---|---|---|
text |
String |
The draft text content. |
timestamp |
DateTime |
UTC time the draft was last saved. |
Edge Cases #
| Scenario | Behaviour |
|---|---|
| Storage not initialized | AutoSave.storage throws a descriptive StateError. |
Draft is null in storage |
Controller starts empty; no error. |
| Draft is malformed JSON | Silently discarded; debugPrint warning logged. |
| Draft is expired (TTL) | Deleted from storage; controller starts empty. |
| User types during async load | Race condition detected; draft injection aborted. |
Initial text provided, prioritizeDraftOverInitial: false |
Draft ignored; initial text preserved. |
Text exceeds maxDraftLength |
Save skipped; existing draft preserved; warning logged. |
| Text cleared to empty | storage.delete(key) called; storage is cleaned up. |
| App backgrounded mid-debounce | _forceSave() cancels timer and saves immediately. |
dispose() with pending timer |
Timer cancelled; best-effort save fired before teardown. |
Example App #
The example/ directory contains a full Flutter application demonstrating all three storage adapters (SharedPreferences, Hive, Isar). Each backend has its own page — navigate away and return to see the draft restored from storage.
cd example
flutter pub get
# For Isar — generate the collection schema:
flutter pub run build_runner build --delete-conflicting-outputs
flutter run
Contributing #
Contributions, bug reports, and feature requests are welcome! Please open an issue or pull request on GitHub.
License #
MIT © auto_save_controller contributors