← Back to list

flutter-state-management
by abhishekbrt
glow state
⭐ 0🍴 0📅 Jan 11, 2026
SKILL.md
name: flutter-state-management description: State management patterns for Flutter with Riverpod as primary solution. Covers provider types, async state, and local state patterns. Use when managing app state or implementing feature state logic.
Flutter State Management
Quick Decision
| Scenario | Use |
|---|---|
| Form input, toggle, animation trigger | setState |
| Single shared value (simple) | ValueNotifier or StateProvider |
| Feature with loading/error/success | AsyncNotifierProvider (Riverpod) |
| Mutable state with business logic | NotifierProvider (Riverpod) |
| Complex event flows, event tracking | BLoC (alternative) |
Detailed Guides
| Topic | Guide | Use When |
|---|---|---|
| Riverpod | riverpod.md | Primary - most features, DI, async state |
| BLoC Pattern | bloc.md | Alternative - complex event-driven flows |
Riverpod Quick Start
Setup
// main.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
runApp(const ProviderScope(child: MyApp()));
}
Provider with Code Generation
// features/counter/presentation/providers/counter_provider.dart
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'counter_provider.g.dart';
@riverpod
class Counter extends _$Counter {
@override
int build() => 0;
void increment() => state++;
void decrement() => state--;
}
Consuming in Widget
class CounterScreen extends ConsumerWidget {
const CounterScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Scaffold(
body: Center(child: Text('Count: $count')),
floatingActionButton: FloatingActionButton(
onPressed: () => ref.read(counterProvider.notifier).increment(),
child: const Icon(Icons.add),
),
);
}
}
Local State
setState (Widget-Local)
class CounterWidget extends StatefulWidget {
const CounterWidget({super.key});
@override
State<CounterWidget> createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int _count = 0;
void _increment() {
setState(() => _count++);
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Count: $_count'),
ElevatedButton(onPressed: _increment, child: const Text('Add')),
],
);
}
}
ValueNotifier (Shared Single Value)
// Create notifier
final counterNotifier = ValueNotifier<int>(0);
// Listen in widget
ValueListenableBuilder<int>(
valueListenable: counterNotifier,
builder: (context, value, child) {
return Text('Count: $value');
},
)
// Update
counterNotifier.value++;
// Dispose when done
counterNotifier.dispose();
Async State Pattern
AsyncNotifier for Feature State
@riverpod
class UserProfile extends _$UserProfile {
@override
FutureOr<User?> build() async {
final repo = ref.watch(userRepositoryProvider);
return repo.getCurrentUser();
}
Future<void> updateName(String name) async {
final current = state.valueOrNull;
if (current == null) return;
state = const AsyncLoading();
state = await AsyncValue.guard(() async {
final repo = ref.read(userRepositoryProvider);
return repo.updateUser(current.copyWith(name: name));
});
}
}
Consuming AsyncValue
@override
Widget build(BuildContext context, WidgetRef ref) {
final userAsync = ref.watch(userProfileProvider);
return userAsync.when(
data: (user) => user != null
? ProfileView(user: user)
: const LoginPrompt(),
loading: () => const ProfileSkeleton(),
error: (error, _) => ErrorView(
message: error.toString(),
onRetry: () => ref.invalidate(userProfileProvider),
),
);
}
ref.watch vs ref.read
| Method | Rebuilds | Use In |
|---|---|---|
ref.watch() | Yes | build() method |
ref.read() | No | Callbacks, event handlers |
ref.listen() | No (triggers callback) | Side effects |
@override
Widget build(BuildContext context, WidgetRef ref) {
// Side effects (snackbar, navigation)
ref.listen(authProvider, (prev, next) {
if (next is AsyncError) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(next.error.toString())),
);
}
});
// Reactive rebuild
final auth = ref.watch(authProvider);
return ElevatedButton(
// Non-reactive callback
onPressed: () => ref.read(authProvider.notifier).logout(),
child: const Text('Logout'),
);
}
When to Use BLoC
Use BLoC (see bloc.md) when you need:
| Scenario | Why BLoC |
|---|---|
| Event logging/analytics | Events create audit trail |
| Complex event transformations | on<Event> handlers with debounce, throttle |
| Undo/redo functionality | Event replay capability |
| Strict separation of concerns | Events as explicit API |
For most features, Riverpod's NotifierProvider or AsyncNotifierProvider is simpler and sufficient.
Provider Types Summary
| Type | Use Case | Example |
|---|---|---|
@riverpod function | Computed/derived values | themeModeProvider |
NotifierProvider | Mutable sync state | counterProvider |
AsyncNotifierProvider | Mutable async state | authProvider |
FutureProvider | Read-only async data | productsProvider |
StreamProvider | Real-time data | messagesProvider |
| Family (parameterized) | Per-ID data | userProvider(userId: '123') |
Anti-Patterns
| Avoid | Instead |
|---|---|
| Business logic in widgets | Move to Notifier classes |
ref.read in build() | Use ref.watch for reactive updates |
| Manual loading/error booleans | Use AsyncValue from AsyncNotifierProvider |
| Global mutable state | Use scoped providers with Riverpod |
setState after async without mounted check | Use Riverpod (handles lifecycle) |
| Nested ProviderScope (usually) | Single root scope, use overrides for testing |
Score
Total Score
50/100
Based on repository quality metrics
✓SKILL.md
SKILL.mdファイルが含まれている
+20
○LICENSE
ライセンスが設定されている
0/10
○説明文
100文字以上の説明がある
0/10
○人気
GitHub Stars 100以上
0/15
○最近の活動
3ヶ月以内に更新がある
0/10
○フォーク
10回以上フォークされている
0/5
✓Issue管理
オープンIssueが50未満
+5
✓言語
プログラミング言語が設定されている
+5
○タグ
1つ以上のタグが設定されている
0/5
Reviews
💬
Reviews coming soon