A lightweight state management approach for Flutter that provides the benefits of unidirectional data flow without the complexity of BLoC.
Read the full technical documentation
This sample implements a simplified unidirectional data flow pattern that maintains predictable state management while reducing boilerplate code. It combines the best aspects of BLoC (Business Logic Component) architecture with a more straightforward, functional approach.
- Truly Unidirectional Data Flow - State flows in one direction through the app, making the data flow predictable and easier to debug
- Immutable State - All state objects are immutable using Dart records and
typedef
s - Type-safe Error Handling - Uses algebraic data types for predictable error handling
- Simplified Business Logic - Controllers using ValueNotifier replace BLoCs with a more straightforward approach
- Built-in Pagination Support - First-class support for infinite scrolling and pagination
- Minimal Boilerplate - No event classes, mappers, or complex streams required
- Dependency Injection - Simple service location pattern using ioc_container
- Testable - Easy to test due to clear separation of concerns and immutable state
- Controllers - Extend ValueNotifier to handle business logic and state management
- DataState - Algebraic data types representing all possible states (Loading, Loaded, Failed, etc.)
- Models - Immutable data classes using Dart records and
typedef
s - Framework - Core utilities for state management and data flow
- UI - Pure widgets that rebuild based on state changes
- No separate Event/State classes needed
- Uses ValueNotifier instead of Streams for simpler state management
- No need for complex transformers or stream operators
- Direct method calls instead of event dispatch
- Built-in support for common patterns like pagination
- Simpler testing due to fewer moving parts
- State Definition
typedef AppState = ({
DataState<ImmutableList<Post>, Fault> postsData,
int pageCount,
});
- Controller Setup
class AppController extends ValueNotifier<AppState> {
AppController(this.httpClient, this.navigatorKey)
: super(createAppState());
final Client httpClient;
final GlobalKey<NavigatorState> navigatorKey;
}
- UI Connection
ValueListenableBuilder<AppState>(
valueListenable: container<AppController>(),
builder: (context, state, _) => // Build UI based on state
)
- Simplicity: Easier to understand and maintain than full BLoC implementation
- Performance: No unnecessary abstractions or stream transformations
- Type Safety: Full type safety with algebraic data types
- Testing: Straightforward testing due to immutable state and clear data flow
- Scalability: Scales well for both small and large applications
- Maintainability: Clear separation of concerns makes code easier to maintain
- Keep controllers focused and break them up to share across app components
- Use records with
typedef
s for immutable state objects - Handle all error cases explicitly with algebraic data types
- Avoid global state except for dependency injection
- Write widget tests for business logic and user interactions
- Avoid unnecessary layering and mapping
See the sample app in this repository for a complete example of:
- Infinite scrolling list
- Error handling
- Loading states
- Animation integration
- Theme management
- Widget testing with golden files
Contributions are welcome! Please read our contributing guidelines before submitting pull requests.