Simple state management for Flutter projects.
classFooStateextendsSimpleState { ... }@overrideWidgetbuild(BuildContext context) {
returnSimpleStateWidget(
FooState(), // the initial state
initFunction: ..., // executed on first build
builder: (BuildContext context) => ..., // widgets that will get rebuilt on changes
);
}FooState state =SimpleState.get<FooState>();state.copyWith(...).update();or
FooState(...).update();classFooStateextendsSimpleState {
@overrideTypeget type =>FooState;
}
classFooLoadingStateextendsFooState { ... }
classFooLoadedStateextendsFooState { ... }
classFooErrorStateextendsFooState { ... }import'package:flutter/material.dart';
import'package:simple_state/simple_state.dart';
classCounterStateextendsSimpleState {
finalint counter;
CounterState({this.counter =0});
}
voidmain() {
runApp(constCounterDemo());
}
classCounterDemoextendsStatelessWidget {
constCounterDemo({super.key});
void_incrementCounter() {
finalCounterState currentState =SimpleState.get<CounterState>();
CounterState(counter: currentState.counter +1).update();
}
@overrideWidgetbuild(BuildContext context) {
returnMaterialApp(
title:'Counter Demo',
theme:ThemeData(colorScheme: .fromSeed(seedColor:Colors.deepPurple)),
home:Scaffold(
appBar:AppBar(
backgroundColor:Theme.of(context).colorScheme.inversePrimary,
title:Text('Counter Demo'),
),
body:SimpleStateWidget(
CounterState(),
builder: (context) {
returnCenter(
child:Column(
mainAxisAlignment: .center,
children: [
constText('You have pushed the button this many times:'),
Text(
'${SimpleState.get<CounterState>().counter}',
style:Theme.of(context).textTheme.headlineMedium,
),
],
),
);
},
),
floatingActionButton:FloatingActionButton(
onPressed: _incrementCounter,
child:constIcon(Icons.add),
),
),
);
}
}import'package:flutter/material.dart';
import'package:simple_state/simple_state.dart';
classAddressStateextendsSimpleState {
finalList<Address> addresses;
finalbool isLoading;
finalString? error;
AddressState({this.addresses =const [], this.isLoading =false, this.error});
AddressStatecopyWith({
List<Address>? addresses,
bool? isLoading,
String? error,
}) {
returnAddressState(
addresses: addresses ??this.addresses,
isLoading: isLoading ??this.isLoading,
error: error ??this.error,
);
}
}
classAddressDemoextendsStatelessWidget {
constAddressDemo({super.key});
Future<void> _loadAddresses() async {
AddressState(isLoading:true).update();
try {
finalList<Address> addresses =await_generateAddresses();
AddressState(addresses: addresses, isLoading:false).update();
} catch (e) {
AddressState(
isLoading:false,
error:'Failed to load addresses: $e',
).update();
}
}
void_addAddress() {
finalAddressState state =SimpleState.get<AddressState>();
state.copyWith(addresses: [...state.addresses, Address.random()]).update();
}
Widget_buildContent(BuildContext context) {
AddressState state =SimpleState.get<AddressState>();
if (state.isLoading) {
returnconstCenter(child:CircularProgressIndicator());
}
if (state.error !=null) {
returnCenter(
child:Text(state.error!, style:constTextStyle(color:Colors.red)),
);
}
if (state.addresses.isEmpty) {
returnconstCenter(child:Text('No addresses loaded'));
}
returnListView.builder(
itemCount: state.addresses.length,
itemBuilder: (context, index) {
final address = state.addresses[index];
returnCard(
margin:constEdgeInsets.symmetric(horizontal:8, vertical:4),
child:ListTile(
title:Text(address.street),
subtitle:Text(
'${address.city}, ${address.state} ${address.zipCode}',
),
trailing:Text(address.country),
leading:CircleAvatar(child:Text('${index + 1}')),
),
);
},
);
}
@overrideWidgetbuild(BuildContext context) {
returnMaterialApp(
title:'Address Demo',
theme:ThemeData(colorScheme: .fromSeed(seedColor:Colors.deepPurple)),
home:Scaffold(
appBar:AppBar(
backgroundColor:Theme.of(context).colorScheme.inversePrimary,
title:Text('Address List'),
),
floatingActionButton:FloatingActionButton(
onPressed: _addAddress,
child:constIcon(Icons.add),
),
body:SimpleStateWidget(
AddressState(),
initFunction: _loadAddresses,
builder: (BuildContext context) =>_buildContent(context),
),
),
);
}
}
voidmain() {
runApp(constAddressDemo());
}
Future<List<Address>> _generateAddresses() async {
awaitFuture.delayed(constDuration(seconds:3));
return<Address>[for (int i =0; i <6; i++) Address.random()];
}
classAddress {
finalString street;
finalString city;
finalString state;
finalString zipCode;
finalString country;
Address({
requiredthis.street,
requiredthis.city,
requiredthis.state,
requiredthis.zipCode,
requiredthis.country,
});
Address.random()
: street =<String>[
'123 Fake Street',
'456 Random Street',
'789 Real Street',
].random,
city =<String>['Fake City', 'Random City', 'Real City'].random,
state =<String>['Fake State', 'Random State', 'Real State'].random,
zipCode =<String>['12345', '54321', '13579'].random,
country =<String>['Fake County', 'Random County', 'Real County'].random;
}
extensionRandomExt<T> onList<T> {
Tget random {
shuffle();
return first;
}
}