This project is included in our 4 Part mini-series about state management, and demonstrates how to implement a custom state management solution in Flutter without relying on third-party state management packages. It provides a lightweight, type-safe, and reactive approach to managing application state.
You can read the full series at:
- Flutter State Management: Beyond Packages (Part 1)
- Flutter State Management: Beyond Packages (Part 2)
- Flutter State Management: Beyond Packages (Part 3)
- Flutter State Management: Beyond Packages (Part 4)
- No External State Management Dependencies: Built using only Flutter's core capabilities
- Type-Safe: Fully type-safe state management with generics
- Reactive: Stream-based reactivity for efficient UI updates
- Modular: Separate state containers for different domains
- Optimized Rebuilds: Selectors for granular widget rebuilds
- Testable: Clean separation of state from UI
This example implements a custom state management pattern with three core components:
graph TD
subgraph "Core Components"
StateStore["StateStore<T> - Holds state, provides stream and updates state"]
StateManager["StateManager - Registers stores and retrieves stores by type"]
end
subgraph "Provider Components"
StateProvider["StateProvider - Makes StateManager available - Root widget"]
StateConsumer["StateConsumer<T> - Listens to store changes and rebuilds on any state change"]
StateSelector["StateSelector<T,S> - Optimized rebuilds and selects part of state"]
end
subgraph "State Domains"
AuthState["AuthState - User auth info"]
ProductState["ProductState - Product catalog and filtering"]
CartState["CartState - Shopping cart items and checkout process"]
end
subgraph "UI Layer"
WidgetTree["Flutter Widget Tree"]
end
%% Relationships
StateManager -->|registers| StateStore
StateStore -->|contains| AuthState
StateStore -->|contains| ProductState
StateStore -->|contains| CartState
StateProvider -->|provides| StateManager
StateProvider -->|parent of| WidgetTree
WidgetTree -->|uses| StateConsumer
WidgetTree -->|uses| StateSelector
StateConsumer -->|reads from| StateStore
StateSelector -->|reads specific part of| StateStore
%% Data flow
StateStore -.->|notifies| StateConsumer
StateStore -.->|notifies| StateSelector
WidgetTree -.->|updates| StateStore
A generic container that holds state of any type and notifies listeners when the state changes:
class StateStore<T> {
// Current state
T _state;
final _stateController = StreamController<T>.broadcast();
// Getters
T get state => _state;
Stream<T> get stream => _stateController.stream;
// Update methods
void update(T newState) { ... }
void updateWith(T Function(T currentState) reducer) { ... }
}A centralized registry for all stores in the application:
class StateManager {
final Map<Type, StateStore<dynamic>> _stores = {};
void register<T>(StateStore<T> store) { ... }
StateStore<T> get<T>() { ... }
}Widgets that connect the stores to the Flutter widget tree:
StateProvider: Makes stores available throughout the widget treeStateConsumer: Rebuilds when a store's state changesStateSelector: Rebuilds only when a specific part of the state changes
This example implements a small e-commerce application with the following features:
- Product listing with category filtering
- Product details
- Shopping cart management
- Checkout process
The state is divided into three domains:
- AuthState: User authentication information
- ProductState: Product catalog and filtering
- CartState: Shopping cart items and checkout process
final stateManager = StateManager()
..register<AuthState>(StateStore<AuthState>(AuthState()))
..register<ProductState>(StateStore<ProductState>(ProductState()))
..register<CartState>(StateStore<CartState>(CartState()));StateProvider(
stateManager: stateManager,
child: MaterialApp(
// app configuration
),
)StateConsumer<ProductState>(
builder: (context, state) {
if (state.isLoading) {
return CircularProgressIndicator();
}
return ListView.builder(
itemCount: state.products.length,
itemBuilder: (context, index) => ProductTile(state.products[index]),
);
},
)StateSelector<CartState, int>(
selector: (state) => state.itemCount,
builder: (context, itemCount) {
return Text('Items in cart: $itemCount');
},
)final cartStore = StateProvider.of<CartState>(context);
void addToCart(Product product) {
cartStore.updateWith((state) {
final currentItems = List<CartItem>.from(state.items);
currentItems.add(CartItem(product: product));
return state.copyWith(items: currentItems);
});
}- Simplicity: Easier to understand than many third-party solutions
- Transparency: No "magic" - you can see exactly how state flows through your app
- Control: Full control over how and when state updates occur
- Performance: Optimized rebuilds with selectors
- Maintainability: Clear patterns for state updates
- Clone this repository
- Run
flutter pub get - Run
flutter run
The implementation follows these principles:
- Immutable State: State classes are immutable with
copyWithmethods - Unidirectional Data Flow: State flows down, actions flow up
- Single Source of Truth: Each piece of state has one authoritative source
- Separation of Concerns: UI components are decoupled from state logic
This approach is well-suited for:
- Learning how state management works under the hood
- Projects that need precise control over state updates
- Applications where minimizing dependencies is important
- Teams that prefer explicit patterns over "magical" solutions
For very large or complex applications, you might want to consider established packages like Provider, Riverpod, GetX or Bloc, which offer additional features and optimizations.
Our custom state management solution includes a comprehensive test suite that verifies its functionality, reliability, and performance. The test suite covers all major components:
-
StateStore Tests: Verify that the core
StateStoreclass correctly initializes with initial state, properly updates state through both direct updates and reducer functions, and notifies listeners via its stream. -
StateManager Tests: Ensure that the
StateManagercorrectly registers and retrieves stores by type, handles errors for unregistered stores, properly disposes of all registered stores, and maintains state consistency across different store references. -
Widget Integration Tests: Confirm that our UI components (
StateProvider,StateConsumer, andStateSelector) integrate properly with the Flutter widget tree. These tests verify that widgets rebuild appropriately when state changes and thatStateSelectoroptimizes rebuilds by only triggering them when the selected portion of state changes.
This testing strategy ensures that our state management solution works reliably across different scenarios and use cases. By thoroughly testing both the state containers and their integration with Flutter widgets, we maintain confidence in the solution's robustness as the application evolves.
To run the tests, execute the following command in the terminal:
flutter testCopyright © 2025 Dom Jocubeit
MIT
This project is for educational purposes to demonstrate custom state management in Flutter.

