mu_state
Minimal Cubit-inspired state management using Flutter's built-in primitives. No external dependencies.
See my article From ValueNotifier to Cubit-inspired state management for more info
Components
MuLogic<S>- Alias forValueNotifierto manage state and business logic. Alternative toCubit.MuBuilder<L>- Alias forValueListenableBuilderto rebuild UI on state changes.MuMultiBuilder- Listen to multipleMuLogicinstances and rebuild when any of them change.MuProvider<L, S>- ProvidesMuLogicinstances down the widget tree usingInheritedWidget.MuMultiProvider- Convenience widget for nesting multipleMuProviderwidgets cleanly.context.logic<L, S>()- Extension method to accessMuLogicinstances from the widget context.MuComparable- Mixin for state classes to enable equality comparisons viapropslist. A lightweight alternative to theEquatablepackage.
Usage
Quick workflow:
- Create logic → Extend
MuLogic<S>with business methods (one per page/feature) - Define state(s) → Create immutable classes with
MuComparableandcopyWith()(can have multiple per page: LoadingState, ErrorState, ReadyState, etc.) - Update state → Use
value = state.copyWith(...)(notemit()) - Provide logic → Wrap app with
MuProvider<Logic, State>orMuMultiProviderto avoid nesting multiple providers. - Build UI → Use
MuBuilderto 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.