Simple state management for Flutter projects.
class FooState extends SimpleState { ... }@override
Widget build(BuildContext context) {
return SimpleStateWidget(
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();class FooState extends SimpleState {
@override
Type get type => FooState;
}
class FooLoadingState extends FooState { ... }
class FooLoadedState extends FooState { ... }
class FooErrorState extends FooState { ... }import 'package:flutter/material.dart';
import 'package:simple_state/simple_state.dart';
class CounterState extends SimpleState {
final int counter;
CounterState({this.counter = 0});
}
void main() {
runApp(const CounterDemo());
}
class CounterDemo extends StatelessWidget {
const CounterDemo({super.key});
void _incrementCounter() {
final CounterState currentState = SimpleState.get<CounterState>();
CounterState(counter: currentState.counter + 1).update();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
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) {
return Center(
child: Column(
mainAxisAlignment: .center,
children: [
const Text('You have pushed the button this many times:'),
Text(
'${SimpleState.get<CounterState>().counter}',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
child: const Icon(Icons.add),
),
),
);
}
}import 'package:flutter/material.dart';
import 'package:simple_state/simple_state.dart';
class AddressState extends SimpleState {
final List<Address> addresses;
final bool isLoading;
final String? error;
AddressState({this.addresses = const [], this.isLoading = false, this.error});
AddressState copyWith({
List<Address>? addresses,
bool? isLoading,
String? error,
}) {
return AddressState(
addresses: addresses ?? this.addresses,
isLoading: isLoading ?? this.isLoading,
error: error ?? this.error,
);
}
}
class AddressDemo extends StatelessWidget {
const AddressDemo({super.key});
Future<void> _loadAddresses() async {
AddressState(isLoading: true).update();
try {
final List<Address> addresses = await _generateAddresses();
AddressState(addresses: addresses, isLoading: false).update();
} catch (e) {
AddressState(
isLoading: false,
error: 'Failed to load addresses: $e',
).update();
}
}
void _addAddress() {
final AddressState state = SimpleState.get<AddressState>();
state.copyWith(addresses: [...state.addresses, Address.random()]).update();
}
Widget _buildContent(BuildContext context) {
AddressState state = SimpleState.get<AddressState>();
if (state.isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (state.error != null) {
return Center(
child: Text(state.error!, style: const TextStyle(color: Colors.red)),
);
}
if (state.addresses.isEmpty) {
return const Center(child: Text('No addresses loaded'));
}
return ListView.builder(
itemCount: state.addresses.length,
itemBuilder: (context, index) {
final address = state.addresses[index];
return Card(
margin: const EdgeInsets.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}')),
),
);
},
);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
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: const Icon(Icons.add),
),
body: SimpleStateWidget(
AddressState(),
initFunction: _loadAddresses,
builder: (BuildContext context) => _buildContent(context),
),
),
);
}
}
void main() {
runApp(const AddressDemo());
}
Future<List<Address>> _generateAddresses() async {
await Future.delayed(const Duration(seconds: 3));
return <Address>[for (int i = 0; i < 6; i++) Address.random()];
}
class Address {
final String street;
final String city;
final String state;
final String zipCode;
final String country;
Address({
required this.street,
required this.city,
required this.state,
required this.zipCode,
required this.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;
}
extension RandomExt<T> on List<T> {
T get random {
shuffle();
return first;
}
}