| name | custom-plugin-flutter-skill-state |
| description | 2300+ lines of state management mastery - all patterns (Provider, Riverpod, BLoC, GetX), dependency injection, persistence, testing with production-ready code examples. |
| sasmp_version | 1.3.0 |
| bonded_agent | 01-flutter-ui-development |
| bond_type | PRIMARY_BOND |
custom-plugin-flutter: State Management Skill
Quick Start - Provider Pattern
// Define notifier
class CounterNotifier extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
void decrement() {
_count--;
notifyListeners();
}
}
// Create provider
final counterProvider = ChangeNotifierProvider((ref) => CounterNotifier());
// Use in UI
class CounterApp extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final counter = ref.watch(counterProvider);
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Count: ${counter.count}'),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () => counter.decrement(),
child: Text('-'),
),
SizedBox(width: 16),
ElevatedButton(
onPressed: () => counter.increment(),
child: Text('+'),
),
],
),
],
),
),
);
}
}
1. setState Foundation
When setState Suffices:
class SimpleCounter extends StatefulWidget {
@override
State<SimpleCounter> createState() => _SimpleCounterState();
}
class _SimpleCounterState extends State<SimpleCounter> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: [
Text('Count: $_count'),
ElevatedButton(
onPressed: () => setState(() => _count++),
child: Text('Increment'),
),
],
),
),
);
}
}
setState Limitations:
- ❌ Rebuilds entire widget subtree
- ❌ No code generation support
- ❌ Hard to test business logic
- ❌ Doesn't work across widgets
2. Provider Ecosystem
ChangeNotifier Pattern:
class UserService extends ChangeNotifier {
User? _user;
User? get user => _user;
Future<void> fetchUser(String id) async {
try {
_user = await api.getUser(id);
notifyListeners();
} catch (e) {
// Error handling
}
}
}
final userProvider = ChangeNotifierProvider((ref) => UserService());
// In UI
Consumer(
builder: (context, ref, child) {
final user = ref.watch(userProvider).user;
return user != null ? Text(user.name) : Text('Loading...');
},
)
FutureProvider for Async:
final userProvider = FutureProvider<User>((ref) async {
return await api.getUser('123');
});
// Usage
ref.watch(userProvider).when(
loading: () => CircularProgressIndicator(),
data: (user) => Text(user.name),
error: (error, st) => Text('Error: $error'),
)
3. Riverpod Modern Approach
Functional State Management:
// Simple provider
final nameProvider = StateProvider((ref) => 'John');
// FutureProvider for async operations
final userProvider = FutureProvider.family<User, String>((ref, id) async {
return await api.getUser(id);
});
// StateNotifierProvider for complex state
final counterProvider = StateNotifierProvider<CounterNotifier, int>((ref) {
return CounterNotifier();
});
class CounterNotifier extends StateNotifier<int> {
CounterNotifier() : super(0);
void increment() => state++;
void decrement() => state--;
}
// Usage
Consumer(
builder: (context, ref, child) {
final count = ref.watch(counterProvider);
return ElevatedButton(
onPressed: () => ref.read(counterProvider.notifier).increment(),
child: Text('Count: $count'),
);
},
)
4. BLoC Pattern
Complete BLoC Example:
// Events
abstract class UserEvent {}
class GetUserEvent extends UserEvent {
final String id;
GetUserEvent(this.id);
}
// States
abstract class UserState {}
class UserLoading extends UserState {}
class UserLoaded extends UserState {
final User user;
UserLoaded(this.user);
}
class UserError extends UserState {
final String error;
UserError(this.error);
}
// BLoC
class UserBloc extends Bloc<UserEvent, UserState> {
final UserRepository _repository;
UserBloc(this._repository) : super(UserLoading()) {
on<GetUserEvent>(_onGetUser);
}
Future<void> _onGetUser(GetUserEvent event, Emitter<UserState> emit) async {
emit(UserLoading());
try {
final user = await _repository.getUser(event.id);
emit(UserLoaded(user));
} catch (e) {
emit(UserError(e.toString()));
}
}
}
// UI Integration
BlocBuilder<UserBloc, UserState>(
builder: (context, state) {
if (state is UserLoading) return CircularProgressIndicator();
if (state is UserLoaded) return Text(state.user.name);
if (state is UserError) return Text('Error: ${state.error}');
return SizedBox();
},
)
5. Dependency Injection
GetIt Service Locator:
final getIt = GetIt.instance;
// Setup
void setupServiceLocator() {
getIt.registerSingleton<ApiClient>(ApiClient());
getIt.registerSingleton<UserRepository>(
UserRepository(getIt<ApiClient>()),
);
getIt.registerLazySingleton<UserBloc>(
() => UserBloc(getIt<UserRepository>()),
);
}
// Usage
final userBloc = getIt<UserBloc>();
6. State Persistence
Hydration Pattern:
class UserBloc extends Bloc<UserEvent, UserState> {
UserBloc(this._repository) : super(const UserInitial()) {
on<GetUserEvent>(_onGetUser);
}
@override
Future<void> close() {
_savePersistentState();
return super.close();
}
Future<void> _savePersistentState() async {
final box = await Hive.openBox('userBloc');
await box.put('state', state.toJson());
}
Future<void> loadPersistedState() async {
final box = await Hive.openBox('userBloc');
final json = box.get('state');
if (json != null) {
emit(UserState.fromJson(json));
}
}
}
7. Testing State Management
Testing with bloc_test:
void main() {
group('CounterBloc', () {
late CounterBloc counterBloc;
setUp(() {
counterBloc = CounterBloc();
});
tearDown(() {
counterBloc.close();
});
blocTest<CounterBloc, int>(
'emits [1] when CounterIncrementPressed is added',
build: () => counterBloc,
act: (bloc) => bloc.add(CounterIncrementPressed()),
expect: () => [1],
);
});
}
8. Advanced Patterns
Derived State with Selectors:
final filteredUsersProvider = Provider<List<User>>((ref) {
final users = ref.watch(usersProvider);
final filter = ref.watch(filterProvider);
return users.where((u) => u.name.contains(filter)).toList();
});
// Usage
Consumer(
builder: (context, ref, child) {
final filtered = ref.watch(filteredUsersProvider);
return ListView.builder(
itemCount: filtered.length,
itemBuilder: (context, index) => Text(filtered[index].name),
);
},
)
9. Performance Optimization
- ✅ Use
Selectorfor fine-grained rebuilds - ✅ Memoize expensive computations
- ✅ Lazy initialize expensive services
- ✅ Profile with DevTools regularly
- ✅ Use
constwidgets throughout
10. Pattern Selection
| Complexity | Pattern | When |
|---|---|---|
| Trivial | setState | Single widget |
| Simple | Provider | Multiple screens |
| Medium | Riverpod | Type-safe needed |
| Complex | BLoC | Enterprise scale |
Master state management for scalable Flutter applications.