hivehook 0.1.6
hivehook: ^0.1.6 copied to clipboard
A powerful plugin system for Hive that adds hooks, lifecycle management, and middleware capabilities with built-in TTL, LRU caching, validation, and custom transformations.
import 'package:flutter/material.dart';
import 'package:hivehook/hivehook.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'HiveHook Web Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const DemoPage(),
);
}
}
class DemoPage extends StatefulWidget {
const DemoPage({super.key});
@override
State<DemoPage> createState() => _DemoPageState();
}
class _DemoPageState extends State<DemoPage> {
late HHive ttlHive;
late HHive lruHive;
late HHive customHive;
late HHive comboHive;
final List<String> auditLog = [];
final List<String> ttlOutput = [];
final List<String> lruOutput = [];
final List<String> customOutput = [];
final List<String> comboOutput = [];
final ttlKeyController = TextEditingController(text: 'session');
final ttlValueController = TextEditingController(text: 'user123');
final ttlSecondsController = TextEditingController(text: '5');
final lruKeyController = TextEditingController(text: 'item1');
final lruValueController = TextEditingController(text: 'data1');
final customKeyController = TextEditingController(text: 'user');
final customValueController = TextEditingController(text: 'John Doe');
final comboKeyController = TextEditingController(text: 'combo1');
final comboValueController = TextEditingController(text: 'test data');
@override
void initState() {
super.initState();
_setupDemos();
}
Future<void> _setupDemos() async {
// Step 1: Define all configs FIRST (before initialize)
// TTL Demo
final ttlPlugin = createTTLPlugin(defaultTTLSeconds: 5);
final ttlConfig = HHConfig(env: 'ttl_demo', usesMeta: true);
ttlConfig.installPlugin(ttlPlugin);
final finalizedTtlConfig = ttlConfig.finalize();
// LRU Demo
final lruPlugin = createLRUPlugin(maxSize: 3);
final lruConfig = HHConfig(env: 'lru_demo', usesMeta: true);
lruConfig.installPlugin(lruPlugin);
final finalizedLruConfig = lruConfig.finalize();
// Custom Hooks Demo
final customConfig = HHConfig(
env: 'custom_demo',
usesMeta: true,
actionHooks: [
HActionHook(
latches: [HHLatch.pre(triggerType: TriggerType.valueWrite)],
action: (ctx) async {
if (ctx.payload.value == null ||
ctx.payload.value.toString().isEmpty) {
throw ArgumentError('Value cannot be empty');
}
},
),
HActionHook(
latches: [
HHLatch(triggerType: TriggerType.valueWrite, isPost: true),
HHLatch(triggerType: TriggerType.valueRead, isPost: true),
],
action: (ctx) async {
final timestamp = DateTime.now().toIso8601String();
final operation = ctx.payload.key != null
? 'Operation on key: ${ctx.payload.key}'
: 'Operation';
auditLog.add('[$timestamp] $operation');
},
),
],
);
final finalizedCustomConfig = customConfig.finalize();
// Combined Demo
final ttlPlugin2 = createTTLPlugin(defaultTTLSeconds: 10);
final lruPlugin2 = createLRUPlugin(maxSize: 5);
final comboConfig = HHConfig(
env: 'combo_demo',
usesMeta: true,
actionHooks: [
HActionHook(
latches: [HHLatch.pre(triggerType: TriggerType.valueWrite)],
action: (ctx) async {
if (ctx.payload.value == null ||
ctx.payload.value.toString().isEmpty) {
throw ArgumentError('Value cannot be empty');
}
},
),
],
);
comboConfig.installPlugin(ttlPlugin2);
comboConfig.installPlugin(lruPlugin2);
final finalizedComboConfig = comboConfig.finalize();
// Step 2: Initialize HHiveCore (now it knows about all boxes)
await HHiveCore.initialize();
// Step 3: Create HHive instances
ttlHive = HHive(config: finalizedTtlConfig);
lruHive = HHive(config: finalizedLruConfig);
customHive = HHive(config: finalizedCustomConfig);
comboHive = HHive(config: finalizedComboConfig);
}
void _addOutput(List<String> list, String message) {
setState(() {
list.add(message);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('π HiveHook Web Demo'),
actions: [
IconButton(
icon: const Icon(Icons.play_arrow),
tooltip: 'Run All Scenarios',
onPressed: _runAllScenarios,
),
],
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
children: [
_buildScenarioButtons(),
const SizedBox(height: 20),
_buildTTLSection(),
const SizedBox(height: 20),
_buildLRUSection(),
const SizedBox(height: 20),
_buildCustomSection(),
const SizedBox(height: 20),
_buildComboSection(),
],
),
),
);
}
Widget _buildScenarioButtons() {
return Card(
color: Colors.blue.shade50,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'π¬ Demo Scenarios',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
const Text(
'Click any scenario to see it in action!',
style: TextStyle(color: Colors.grey),
),
const SizedBox(height: 16),
Wrap(
spacing: 8,
runSpacing: 8,
children: [
ElevatedButton.icon(
onPressed: _runTTLScenario,
icon: const Icon(Icons.timer),
label: const Text('TTL Expiration'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.orange,
foregroundColor: Colors.white,
),
),
ElevatedButton.icon(
onPressed: _runLRUScenario,
icon: const Icon(Icons.storage),
label: const Text('LRU Eviction'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.teal,
foregroundColor: Colors.white,
),
),
ElevatedButton.icon(
onPressed: _runValidationScenario,
icon: const Icon(Icons.check_circle),
label: const Text('Validation'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.purple,
foregroundColor: Colors.white,
),
),
ElevatedButton.icon(
onPressed: _runComboScenario,
icon: const Icon(Icons.layers),
label: const Text('Combined'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
foregroundColor: Colors.white,
),
),
ElevatedButton.icon(
onPressed: _clearAll,
icon: const Icon(Icons.clear_all),
label: const Text('Clear All'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
),
),
],
),
],
),
),
);
}
Future<void> _runAllScenarios() async {
_clearAll();
await Future.delayed(const Duration(milliseconds: 500));
await _runTTLScenario();
await Future.delayed(const Duration(milliseconds: 1000));
await _runLRUScenario();
await Future.delayed(const Duration(milliseconds: 1000));
await _runValidationScenario();
await Future.delayed(const Duration(milliseconds: 1000));
await _runComboScenario();
}
Future<void> _runTTLScenario() async {
setState(() => ttlOutput.clear());
_addOutput(ttlOutput, 'π¬ Starting TTL Scenario...');
// Store data with 3 second TTL
await ttlHive.put('demo_key', 'demo_value', meta: {'ttl': '3'});
_addOutput(ttlOutput, 'β
Stored: demo_key = demo_value (TTL: 3s)');
await Future.delayed(const Duration(milliseconds: 500));
// Read immediately - should succeed
var value = await ttlHive.get('demo_key');
_addOutput(ttlOutput, 'β
Read immediately: $value');
await Future.delayed(const Duration(milliseconds: 500));
// Wait and read again - should succeed
_addOutput(ttlOutput, 'β³ Waiting 1 second...');
await Future.delayed(const Duration(seconds: 1));
value = await ttlHive.get('demo_key');
_addOutput(ttlOutput, 'β
After 1s: $value (still alive)');
// Wait for expiration
_addOutput(ttlOutput, 'β³ Waiting 3 more seconds for expiration...');
await Future.delayed(const Duration(seconds: 3));
// Try to read - should be expired
value = await ttlHive.get('demo_key');
if (value == null) {
_addOutput(ttlOutput, 'β° After 4s total: EXPIRED (as expected!)');
} else {
_addOutput(ttlOutput, 'β Still exists: $value');
}
_addOutput(ttlOutput, 'β¨ Scenario complete!');
}
Future<void> _runLRUScenario() async {
setState(() => lruOutput.clear());
_addOutput(lruOutput, 'π¬ Starting LRU Scenario (max 3 items)...');
// Add 3 items (fill cache)
await lruHive.put('item1', 'data1');
_addOutput(lruOutput, 'β
Added: item1 = data1');
await Future.delayed(const Duration(milliseconds: 300));
await lruHive.put('item2', 'data2');
_addOutput(lruOutput, 'β
Added: item2 = data2');
await Future.delayed(const Duration(milliseconds: 300));
await lruHive.put('item3', 'data3');
_addOutput(lruOutput, 'β
Added: item3 = data3');
_addOutput(lruOutput, 'π Cache is now FULL (3/3)');
await Future.delayed(const Duration(milliseconds: 500));
// Access item2 to make it recently used
await lruHive.get('item2');
_addOutput(lruOutput, 'π Accessed item2 (making it recently used)');
await Future.delayed(const Duration(milliseconds: 500));
// Add 4th item - should evict item1 (least recently used)
await lruHive.put('item4', 'data4');
_addOutput(lruOutput, 'β Added: item4 = data4');
_addOutput(lruOutput, 'ποΈ item1 should be evicted (LRU)');
await Future.delayed(const Duration(milliseconds: 500));
// Verify eviction
final item1 = await lruHive.get('item1');
final item2 = await lruHive.get('item2');
final item3 = await lruHive.get('item3');
final item4 = await lruHive.get('item4');
_addOutput(lruOutput, '');
_addOutput(lruOutput, 'π Final state:');
_addOutput(
lruOutput,
item1 == null ? ' β item1: EVICTED β' : ' β item1: Still exists',
);
_addOutput(
lruOutput,
item2 != null ? ' β
item2: $item2 β' : ' β item2: Missing',
);
_addOutput(
lruOutput,
item3 != null ? ' β
item3: $item3 β' : ' β item3: Missing',
);
_addOutput(
lruOutput,
item4 != null ? ' β
item4: $item4 β' : ' β item4: Missing',
);
_addOutput(lruOutput, 'β¨ Scenario complete!');
}
Future<void> _runValidationScenario() async {
setState(() => customOutput.clear());
_addOutput(customOutput, 'π¬ Starting Validation Scenario...');
// Test 1: Valid data
try {
await customHive.put('user1', 'Alice');
_addOutput(customOutput, 'β
Valid data accepted: user1 = Alice');
if (auditLog.isNotEmpty) {
_addOutput(customOutput, 'π ${auditLog.last}');
}
} catch (e) {
_addOutput(customOutput, 'β Unexpected error: $e');
}
await Future.delayed(const Duration(milliseconds: 500));
// Test 2: Empty value - should fail
try {
await customHive.put('user2', '');
_addOutput(customOutput, 'β Empty value should have been rejected!');
} catch (e) {
_addOutput(customOutput, 'β
Empty value rejected (as expected): $e');
}
await Future.delayed(const Duration(milliseconds: 500));
// Test 3: Read and log
final value = await customHive.get('user1');
_addOutput(customOutput, 'β
Read: user1 = $value');
if (auditLog.isNotEmpty) {
_addOutput(customOutput, 'π ${auditLog.last}');
}
_addOutput(customOutput, '');
_addOutput(customOutput, 'π Audit log has ${auditLog.length} entries');
_addOutput(customOutput, 'β¨ Scenario complete!');
}
Future<void> _runComboScenario() async {
setState(() => comboOutput.clear());
_addOutput(comboOutput, 'π¬ Starting Combined Scenario...');
_addOutput(comboOutput, '(TTL: 10s, LRU: max 5, Validation)');
_addOutput(comboOutput, '');
// Add items quickly
for (int i = 1; i <= 6; i++) {
try {
await comboHive.put('item$i', 'data$i');
_addOutput(comboOutput, 'β
Added: item$i = data$i');
await Future.delayed(const Duration(milliseconds: 200));
} catch (e) {
_addOutput(comboOutput, 'β Error: $e');
}
}
_addOutput(comboOutput, '');
_addOutput(comboOutput, 'π Result: 6 items added, cache max is 5');
_addOutput(comboOutput, 'ποΈ item1 should be evicted (LRU)');
await Future.delayed(const Duration(milliseconds: 500));
// Verify
final item1 = await comboHive.get('item1');
final item6 = await comboHive.get('item6');
_addOutput(comboOutput, '');
_addOutput(
comboOutput,
'β item1: ${item1 == null ? "EVICTED" : "Still exists"}',
);
_addOutput(comboOutput, 'β item6: ${item6 != null ? "EXISTS" : "Missing"}');
_addOutput(comboOutput, '');
_addOutput(comboOutput, 'β° All items will expire in 10 seconds');
_addOutput(comboOutput, 'β¨ Scenario complete!');
}
void _clearAll() {
setState(() {
ttlOutput.clear();
lruOutput.clear();
customOutput.clear();
comboOutput.clear();
auditLog.clear();
});
}
Widget _buildTTLSection() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'β° TTL Plugin - Auto Expiration',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
const Text(
'Data automatically expires after the TTL period. Default is 5 seconds.',
style: TextStyle(color: Colors.grey),
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: TextField(
controller: ttlKeyController,
decoration: const InputDecoration(
labelText: 'Key',
border: OutlineInputBorder(),
),
),
),
const SizedBox(width: 8),
Expanded(
child: TextField(
controller: ttlValueController,
decoration: const InputDecoration(
labelText: 'Value',
border: OutlineInputBorder(),
),
),
),
const SizedBox(width: 8),
SizedBox(
width: 100,
child: TextField(
controller: ttlSecondsController,
decoration: const InputDecoration(
labelText: 'TTL (s)',
border: OutlineInputBorder(),
),
keyboardType: TextInputType.number,
),
),
],
),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: [
ElevatedButton(
onPressed: () async {
try {
await ttlHive.put(
ttlKeyController.text,
ttlValueController.text,
meta: {'ttl': ttlSecondsController.text},
);
_addOutput(
ttlOutput,
'β
Stored: ${ttlKeyController.text} = ${ttlValueController.text} (expires in ${ttlSecondsController.text}s)',
);
} catch (e) {
_addOutput(ttlOutput, 'β Error: $e');
}
},
child: const Text('Set with TTL'),
),
ElevatedButton(
onPressed: () async {
try {
final value = await ttlHive.get(ttlKeyController.text);
if (value == null) {
_addOutput(
ttlOutput,
'β° Key "${ttlKeyController.text}" expired or not found',
);
} else {
final meta = await ttlHive.getMeta(
ttlKeyController.text,
);
_addOutput(
ttlOutput,
'β
Read: ${ttlKeyController.text} = $value (TTL: ${meta?['ttl']}s)',
);
}
} catch (e) {
_addOutput(ttlOutput, 'β Error: $e');
}
},
child: const Text('Read'),
),
ElevatedButton(
onPressed: () {
setState(() {
ttlOutput.clear();
});
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
),
child: const Text('Clear'),
),
],
),
const SizedBox(height: 8),
Container(
height: 150,
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.circular(4),
),
child: ListView.builder(
itemCount: ttlOutput.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.only(bottom: 4),
child: Text(
ttlOutput[index],
style: const TextStyle(fontSize: 12),
),
);
},
),
),
],
),
),
);
}
Widget _buildLRUSection() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'πΎ LRU Plugin - Cache Management',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
const Text(
'LRU cache with max size of 3 items. Evicts least recently used when full.',
style: TextStyle(color: Colors.grey),
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: TextField(
controller: lruKeyController,
decoration: const InputDecoration(
labelText: 'Key',
border: OutlineInputBorder(),
),
),
),
const SizedBox(width: 8),
Expanded(
child: TextField(
controller: lruValueController,
decoration: const InputDecoration(
labelText: 'Value',
border: OutlineInputBorder(),
),
),
),
],
),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: [
ElevatedButton(
onPressed: () async {
try {
await lruHive.put(
lruKeyController.text,
lruValueController.text,
);
_addOutput(
lruOutput,
'β
Added: ${lruKeyController.text} = ${lruValueController.text}',
);
await Future.delayed(const Duration(milliseconds: 100));
await _lruListAll();
} catch (e) {
_addOutput(lruOutput, 'β Error: $e');
}
},
child: const Text('Add to Cache'),
),
ElevatedButton(
onPressed: () async {
try {
final value = await lruHive.get(lruKeyController.text);
if (value == null) {
_addOutput(
lruOutput,
'β οΈ Key "${lruKeyController.text}" not found (may have been evicted)',
);
} else {
_addOutput(
lruOutput,
'β
Read: ${lruKeyController.text} = $value',
);
}
} catch (e) {
_addOutput(lruOutput, 'β Error: $e');
}
},
child: const Text('Read'),
),
ElevatedButton(
onPressed: _lruListAll,
child: const Text('List All'),
),
ElevatedButton(
onPressed: () {
setState(() {
lruOutput.clear();
});
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
),
child: const Text('Clear'),
),
],
),
const SizedBox(height: 8),
Container(
height: 150,
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.circular(4),
),
child: ListView.builder(
itemCount: lruOutput.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.only(bottom: 4),
child: Text(
lruOutput[index],
style: const TextStyle(fontSize: 12),
),
);
},
),
),
],
),
),
);
}
Future<void> _lruListAll() async {
try {
final cacheIndexMeta = await lruHive.getMeta('_lru_cache_keys');
final keysString = cacheIndexMeta?['keys'] as String?;
if (keysString == null || keysString.isEmpty) {
_addOutput(lruOutput, 'βΉοΈ Cache is empty');
return;
}
final keys = keysString.split(',').where((k) => k.isNotEmpty).toList();
_addOutput(lruOutput, 'π Cache contents (${keys.length}/3 items):');
for (var key in keys) {
final value = await lruHive.get(key);
_addOutput(lruOutput, ' β’ $key = $value');
}
} catch (e) {
_addOutput(lruOutput, 'β Error: $e');
}
}
Widget _buildCustomSection() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'π§ Custom Hooks - Validation & Logging',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
const Text(
'Pre-hook validates data (no empty values), post-hook logs operations.',
style: TextStyle(color: Colors.grey),
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: TextField(
controller: customKeyController,
decoration: const InputDecoration(
labelText: 'Key',
border: OutlineInputBorder(),
),
),
),
const SizedBox(width: 8),
Expanded(
child: TextField(
controller: customValueController,
decoration: const InputDecoration(
labelText: 'Value (try empty!)',
border: OutlineInputBorder(),
),
),
),
],
),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: [
ElevatedButton(
onPressed: () async {
try {
await customHive.put(
customKeyController.text,
customValueController.text,
);
_addOutput(
customOutput,
'β
Validation passed! Stored: ${customKeyController.text} = ${customValueController.text}',
);
if (auditLog.isNotEmpty) {
_addOutput(customOutput, 'π ${auditLog.last}');
}
} catch (e) {
_addOutput(customOutput, 'β Validation failed: $e');
}
},
child: const Text('Write'),
),
ElevatedButton(
onPressed: () async {
try {
final value = await customHive.get(
customKeyController.text,
);
if (value == null) {
_addOutput(
customOutput,
'β οΈ Key "${customKeyController.text}" not found',
);
} else {
_addOutput(
customOutput,
'β
Read: ${customKeyController.text} = $value',
);
}
if (auditLog.isNotEmpty) {
_addOutput(customOutput, 'π ${auditLog.last}');
}
} catch (e) {
_addOutput(customOutput, 'β Error: $e');
}
},
child: const Text('Read'),
),
ElevatedButton(
onPressed: () {
setState(() {
customOutput.clear();
auditLog.clear();
});
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
),
child: const Text('Clear'),
),
],
),
const SizedBox(height: 8),
Container(
height: 150,
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.circular(4),
),
child: ListView.builder(
itemCount: customOutput.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.only(bottom: 4),
child: Text(
customOutput[index],
style: const TextStyle(fontSize: 12),
),
);
},
),
),
],
),
),
);
}
Widget _buildComboSection() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'π Combined Plugins - TTL + LRU + Validation',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
const Text(
'All plugins working together! TTL (10s), LRU (max 5), validation.',
style: TextStyle(color: Colors.grey),
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: TextField(
controller: comboKeyController,
decoration: const InputDecoration(
labelText: 'Key',
border: OutlineInputBorder(),
),
),
),
const SizedBox(width: 8),
Expanded(
child: TextField(
controller: comboValueController,
decoration: const InputDecoration(
labelText: 'Value',
border: OutlineInputBorder(),
),
),
),
],
),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: [
ElevatedButton(
onPressed: () async {
try {
await comboHive.put(
comboKeyController.text,
comboValueController.text,
);
_addOutput(
comboOutput,
'β
Stored: ${comboKeyController.text} = ${comboValueController.text} (TTL: 10s, LRU tracked)',
);
} catch (e) {
_addOutput(comboOutput, 'β Error: $e');
}
},
child: const Text('Add Item'),
),
ElevatedButton(
onPressed: () async {
try {
final value = await comboHive.get(
comboKeyController.text,
);
if (value == null) {
_addOutput(
comboOutput,
'β οΈ Key "${comboKeyController.text}" not found (expired or evicted)',
);
} else {
final meta = await comboHive.getMeta(
comboKeyController.text,
);
_addOutput(
comboOutput,
'β
Read: ${comboKeyController.text} = $value (TTL: ${meta?['ttl']}s)',
);
}
} catch (e) {
_addOutput(comboOutput, 'β Error: $e');
}
},
child: const Text('Read'),
),
ElevatedButton(
onPressed: () {
setState(() {
comboOutput.clear();
});
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
),
child: const Text('Clear'),
),
],
),
const SizedBox(height: 8),
Container(
height: 150,
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.circular(4),
),
child: ListView.builder(
itemCount: comboOutput.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.only(bottom: 4),
child: Text(
comboOutput[index],
style: const TextStyle(fontSize: 12),
),
);
},
),
),
],
),
),
);
}
@override
void dispose() {
ttlKeyController.dispose();
ttlValueController.dispose();
ttlSecondsController.dispose();
lruKeyController.dispose();
lruValueController.dispose();
customKeyController.dispose();
customValueController.dispose();
comboKeyController.dispose();
comboValueController.dispose();
super.dispose();
}
}