microstate 1.0.1
microstate: ^1.0.1 copied to clipboard
A lightweight, reactive state management solution for Flutter applications with built-in history tracking, validation, debouncing, and throttling capabilities.
microstate #
A lightweight, reactive state management solution for Flutter applications. Microstate provides a simple and intuitive API for managing state with built-in history tracking, validation, debouncing, and throttling capabilities.
Features #
- ๐ฏ Simple API - Easy to use with minimal boilerplate
- ๐ Reactive - Automatic UI updates when state changes
- ๐ History Tracking - Built-in undo/redo functionality
- โ Validation - Optional value validation
- โฑ๏ธ Debouncing & Throttling - Control update frequency
- ๐จ Widget Integration - Seamless integration with Flutter widgets
- ๐งช Type Safe - Full type safety with generics
- ๐ Lightweight - Minimal overhead and dependencies
- ๐ Context-Independent - No BuildContext required, no "called before build" errors
Why Microstate? #
Context-Independent Design #
Unlike many other state management solutions, microstate is completely context-independent. This means:
- โ No BuildContext required - State can be created anywhere
- โ No widget tree dependency - Works independently of widget hierarchy
- โ No initialization order issues - No "state being called before build has executed" errors
- โ Flexible placement - Can be created in constructors, initState, or even as global variables
- โ Simple testing - Easy to test without widget context
// โ
Works perfectly - no context needed
final counter = state(0);
// โ
Safe to call in initState
@override
void initState() {
super.initState();
counter = state(0); // No errors!
}
// โ
Can be created anywhere
class MyService {
final userState = state<User?>(null);
final settingsState = state<Settings>(Settings.defaults());
}
Installation #
Add microstate
to your pubspec.yaml
:
dependencies:
microstate: ^1.0.0
Then run:
flutter pub get
Quick Start #
Basic Usage #
import 'package:flutter/material.dart';
import 'package:microstate/microstate.dart';
class CounterPage extends StatefulWidget {
const CounterPage({super.key});
@override
State<CounterPage> createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
late final ValueState<int> counter;
@override
void initState() {
super.initState();
counter = state(0);
}
@override
void dispose() {
counter.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Reactive counter display
Observer(
state: counter,
builder: (context, value) => Text(
'Count: $value',
style: Theme.of(context).textTheme.headlineMedium,
),
),
const SizedBox(height: 20),
// Action buttons
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () => counter.decrement(),
child: const Text('Decrement'),
),
const SizedBox(width: 16),
ElevatedButton(
onPressed: () => counter.increment(),
child: const Text('Increment'),
),
],
),
],
),
),
);
}
}
API Reference #
Creating State #
Basic State
// Create a simple state
final counter = state(0);
// Access the current value
print(counter.value); // 0
// Update the value
counter.value = 5;
State with History
// Create state with history tracking (default: 10 items)
final counter = stateWithHistory(0, maxHistorySize: 5);
counter.value = 1;
counter.value = 2;
counter.value = 3;
// Undo to previous value
counter.undo(); // Now value is 2
// Check if undo is available
if (counter.canUndo) {
counter.undo();
}
// Get history
print(counter.history); // [0, 1, 2]
State with Validation
// Create state with validation
final age = stateWithValidation(0, validator: (value) => value >= 0 ? value : 0);
age.value = -5; // Will be validated to 0
age.value = 25; // Will be set to 25
Updating State #
Direct Updates
final counter = state(0);
counter.value = 10;
Transform Updates
final counter = state(0);
// Update using a transformation function
counter.update((value) => value + 1);
// Async updates
final user = stateAsync<User?>(null);
await user.updateAsync((current) async => await fetchUser());
Debounced Updates
final searchQuery = state('');
// Only the last update within 300ms will be applied
searchQuery.debouncedUpdate(
(value) => value + 'a',
Duration(milliseconds: 300),
);
Throttled Updates
final scrollPosition = state(0.0);
// Updates are limited to once per 100ms
scrollPosition.throttledUpdate(
(value) => newPosition,
Duration(milliseconds: 100),
);
Widget Integration #
Observer Widget
Observer(
state: counter,
builder: (context, value) => Text('Count: $value'),
)
Manual Listeners
final counter = state(0);
counter.addListener(() {
print('Counter changed to: ${counter.value}');
});
counter.value = 5; // Triggers the listener
Extension Methods #
Numeric Operations
final counter = state(0);
counter.increment(); // value becomes 1
counter.decrement(); // value becomes 0
Boolean Operations
final isEnabled = state(false);
isEnabled.toggle(); // value becomes true
isEnabled.toggle(); // value becomes false
Examples #
Form State Management #
class FormState {
final name = state('');
final email = state('');
final age = stateWithValidation(0, validator: (value) => value >= 0 ? value : 0);
final isSubmitting = state(false);
bool get isValid => name.value.isNotEmpty && email.value.contains('@') && age.value > 0;
Future<void> submit() async {
isSubmitting.value = true;
try {
await submitForm();
} finally {
isSubmitting.value = false;
}
}
}
Search with Debouncing #
class SearchPage extends StatefulWidget {
@override
State<SearchPage> createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage> {
late final ValueState<String> searchQuery;
late final ValueState<List<Item>> searchResults;
@override
void initState() {
super.initState();
searchQuery = state('');
searchResults = state([]);
}
void onSearchChanged(String query) {
searchQuery.debouncedUpdate(
(value) => query,
Duration(milliseconds: 300),
);
}
@override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
onChanged: onSearchChanged,
decoration: InputDecoration(labelText: 'Search'),
),
Observer(
state: searchResults,
builder: (context, results) => ListView.builder(
itemCount: results.length,
itemBuilder: (context, index) => ListTile(
title: Text(results[index].title),
),
),
),
],
);
}
}
Best Practices #
- Always dispose of state when you're done with it to prevent memory leaks
- Use appropriate state types -
state()
for simple values,stateWithHistory()
for undoable state,stateWithValidation()
for validated state - Leverage debouncing for search inputs and other frequent updates
- Use throttling for scroll events and other high-frequency updates
- Combine with Observer widget for automatic UI updates
Contributing #
Contributions are welcome! Please feel free to submit a Pull Request.
License #
This project is licensed under the MIT License - see the LICENSE file for details.