store_it 1.0.0
store_it: ^1.0.0 copied to clipboard
A high-performance, SQLite-backed key-value store for Flutter with native TTL and background maintenance.
import 'package:flutter/material.dart';
import 'package:store_it/store_it.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize StoreIT with a 5-second background purge interval for demo purposes.
// In a real app, 30-60 seconds is usually sufficient.
await StoreIT.init(purgeIntervalSeconds: 5);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'StoreIT Advanced Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo, brightness: Brightness.light),
useMaterial3: true,
),
home: const AdvancedStoreITDemo(),
);
}
}
class AdvancedStoreITDemo extends StatefulWidget {
const AdvancedStoreITDemo({super.key});
@override
State<AdvancedStoreITDemo> createState() => _AdvancedStoreITDemoState();
}
class _AdvancedStoreITDemoState extends State<AdvancedStoreITDemo> {
String _lastEvent = 'System Ready';
int _purgeCount = 0;
@override
void initState() {
super.initState();
// Monitor background maintenance
StoreIT.onPurge = (count) {
if (mounted) {
setState(() {
_purgeCount += count;
_lastEvent = 'Evicted $count expired items';
});
}
};
}
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
title: const Text('StoreIT Advanced'),
bottom: const TabBar(
tabs: [
Tab(icon: Icon(Icons.timer), text: 'TTL Cache'),
Tab(icon: Icon(Icons.person), text: 'JSON Profiles'),
Tab(icon: Icon(Icons.list), text: 'Lists/Logs'),
],
),
),
body: const TabBarView(
children: [
TTLCacheView(),
JsonProfileView(),
ActivityLogView(),
],
),
bottomNavigationBar: Container(
color: Theme.of(context).colorScheme.surfaceVariant,
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
child: Row(
children: [
const Icon(Icons.analytics_outlined, size: 16),
const SizedBox(width: 8),
Expanded(
child: Text(
'Event: $_lastEvent | Total Purged: $_purgeCount',
style: Theme.of(context).textTheme.bodySmall,
),
),
TextButton(
onPressed: () => StoreIT.flush().then((_) => setState(() {})),
child: const Text('FLUSH ALL', style: TextStyle(color: Colors.red, fontSize: 10)),
)
],
),
),
),
);
}
}
// ─── 1. TTL CACHE VIEW ───────────────────────────────────────────────────────
class TTLCacheView extends StatefulWidget {
const TTLCacheView({super.key});
@override
State<TTLCacheView> createState() => _TTLCacheViewState();
}
class _TTLCacheViewState extends State<TTLCacheView> {
final _keyCtrl = TextEditingController();
final _valCtrl = TextEditingController();
List<String> _keys = [];
void _refresh() async {
final k = await StoreIT.keys(prefix: 'cache:');
if (mounted) setState(() => _keys = k);
}
@override
void initState() {
super.initState();
_refresh();
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
TextField(controller: _keyCtrl, decoration: const InputDecoration(labelText: 'Key (without prefix)')),
TextField(controller: _valCtrl, decoration: const InputDecoration(labelText: 'Value')),
const SizedBox(height: 10),
Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () async {
if (_keyCtrl.text.isEmpty) return;
await StoreIT.set('cache:${_keyCtrl.text}', _valCtrl.text, ttlSeconds: 1500);
_keyCtrl.clear(); _valCtrl.clear();
_refresh();
},
child: const Text('Set with 1500s TTL'),
),
),
],
),
const Divider(height: 32),
Expanded(
child: ListView.builder(
itemCount: _keys.length,
itemBuilder: (context, i) {
final k = _keys[i];
return FutureBuilder<List<dynamic>>(
future: Future.wait([StoreIT.ttl(k), StoreIT.get(k)]),
builder: (context, snap) {
final ttl = snap.data?[0];
final val = snap.data?[1];
return ListTile(
title: Text(k, style: const TextStyle(fontWeight: FontWeight.bold)),
subtitle: Text('Value: ${val ?? "N/A"}\nTTL: ${ttl ?? "∞"}s'),
isThreeLine: true,
trailing: IconButton(icon: const Icon(Icons.refresh), onPressed: _refresh),
);
},
);
},
),
)
],
),
);
}
}
// ─── 2. JSON PROFILE VIEW (MERGE DEMO) ──────────────────────────────────────
class JsonProfileView extends StatefulWidget {
const JsonProfileView({super.key});
@override
State<JsonProfileView> createState() => _JsonProfileViewState();
}
class _JsonProfileViewState extends State<JsonProfileView> {
final String _userKey = 'user:profile:101';
Map<String, dynamic> _data = {};
void _load() async {
final d = await StoreIT.jsonGet(_userKey);
if (mounted) setState(() => _data = (d as Map?)?.cast<String, dynamic>() ?? {});
}
@override
void initState() {
super.initState();
_load();
}
@override
Widget build(BuildContext context) {
return ListView(
padding: const EdgeInsets.all(16),
children: [
const Text('Atomic JSON Merge Demo', style: TextStyle(fontWeight: FontWeight.bold)),
const Text('Update specific fields without reading the whole object.', style: TextStyle(fontSize: 12)),
const SizedBox(height: 20),
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('User Profile (ID: 101)', style: Theme.of(context).textTheme.titleMedium),
const Divider(),
Text('Name: ${_data['name'] ?? 'N/A'}'),
Text('Role: ${_data['role'] ?? 'N/A'}'),
Text('Status: ${_data['status'] ?? 'N/A'}'),
Text('Theme: ${_data['settings']?['theme'] ?? 'N/A'}'),
],
),
),
),
const SizedBox(height: 20),
Wrap(
spacing: 8,
children: [
ActionChip(
label: const Text('Set Initial'),
onPressed: () async {
await StoreIT.jsonSet(_userKey, {
'name': 'John Doe',
'role': 'Admin',
'status': 'Online',
'settings': {'theme': 'dark'}
});
_load();
},
),
ActionChip(
label: const Text('Promote to Manager'),
onPressed: () async {
await StoreIT.jsonMerge(_userKey, {'role': 'Manager'});
_load();
},
),
ActionChip(
label: const Text('Go Offline'),
onPressed: () async {
await StoreIT.jsonMerge(_userKey, {'status': 'Offline'});
_load();
},
),
],
)
],
);
}
}
// ─── 3. ACTIVITY LOG VIEW (LIST DEMO) ───────────────────────────────────────
class ActivityLogView extends StatefulWidget {
const ActivityLogView({super.key});
@override
State<ActivityLogView> createState() => _ActivityLogViewState();
}
class _ActivityLogViewState extends State<ActivityLogView> {
final String _logKey = 'logs:activity';
List<String> _logs = [];
void _load() async {
final l = await StoreIT.listGet(_logKey);
if (mounted) setState(() => _logs = l ?? []);
}
@override
void initState() {
super.initState();
_load();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: () async {
final timestamp = DateTime.now().toIso8601String().split('T').last.substring(0, 8);
await StoreIT.listAppend(_logKey, ['Action at $timestamp']);
_load();
},
icon: const Icon(Icons.add),
label: const Text('Append Log Entry'),
),
),
const SizedBox(width: 8),
IconButton(
onPressed: () async {
await StoreIT.delete(_logKey);
_load();
},
icon: const Icon(Icons.clear_all, color: Colors.red),
)
],
),
),
const Divider(height: 1),
Expanded(
child: ListView.separated(
itemCount: _logs.length,
separatorBuilder: (_, __) => const Divider(height: 1),
itemBuilder: (context, i) => ListTile(
leading: CircleAvatar(child: Text('${i+1}', style: const TextStyle(fontSize: 10))),
title: Text(_logs[i]),
dense: true,
),
),
)
],
);
}
}