mu_state
Minimal Cubit-inspired state management using Flutter's built-in primitives. No external dependencies.
Based on my article Pragmatic state handling in Flutter
Components
MuLogic<S>
- Alias forValueNotifier
to manage state and business logic. Alternative toCubit
.MuBuilder<L>
- Alias forValueListenableBuilder
to rebuild UI on state changes.MuMultiBuilder
- Listen to multipleMuLogic
instances and rebuild when any of them change.MuProvider<L, S>
- ProvidesMuLogic
instances down the widget tree usingInheritedWidget
.MuMultiProvider
- Convenience widget for nesting multipleMuProvider
widgets cleanly.context.logic<L, S>()
- Extension method to accessMuLogic
instances from the widget context.MuComparable
- Mixin for state classes to enable equality comparisons viaprops
list. A lightweight alternative to theEquatable
package.
Usage
Quick workflow:
- Create logic → Extend
MuLogic<S>
with business methods (one per page/feature) - Define state(s) → Create immutable classes with
MuComparable
andcopyWith()
(can have multiple per page: LoadingState, ErrorState, ReadyState, etc.) - Update state → Use
value = state.copyWith(...)
(notemit()
) - Provide logic → Wrap app with
MuProvider<Logic, State>
orMuMultiProvider
to avoid nesting multiple providers. - Build UI → Use
MuBuilder
to listen orcontext.logic<T>()
to access
Example
import 'package:mu_state/mu_state.dart';
// Simple state class
class CounterState with MuComparable {
final int counter;
final bool isLoading;
const CounterState({required this.counter, required this.isLoading});
CounterState copyWith({int? counter, bool? isLoading}) {
return CounterState(
counter: counter ?? this.counter,
isLoading: isLoading ?? this.isLoading,
);
}
@override
List<Object?> get props => [counter, isLoading];
}
// Logic class
class CounterLogic extends MuLogic<CounterState> {
CounterLogic() : super(const CounterState(counter: 0, isLoading: false));
void increment() {
value = value.copyWith(counter: value.counter + 1);
}
Future<void> incrementAsync() async {
value = value.copyWith(isLoading: true);
await Future.delayed(const Duration(seconds: 1));
value = value.copyWith(counter: value.counter + 1, isLoading: false);
}
}
// Usage
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MuProvider<CounterLogic, CounterState>(
logic: CounterLogic(),
child: MaterialApp(
home: CounterPage(),
),
);
}
}
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
MuBuilder<CounterState>(
valueListenable: context.logic<CounterLogic, CounterState>(),
builder: (context, state, child) {
return Column(
children: [
Text('Counter: ${state.counter}'),
if (state.isLoading) CircularProgressIndicator(),
],
);
},
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () => context.logic<CounterLogic, CounterState>().increment(),
child: Text('Increment'),
),
],
),
),
);
}
}
See the example project for a complete implementation with more features.