AgentSkillsCN

flutter-state-management

使用Riverpod作为主要解决方案的Flutter状态管理模式。涵盖提供者类型、异步状态和本地状态模式。在管理应用状态或实现功能状态逻辑时使用。

SKILL.md
--- frontmatter
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

ScenarioUse
Form input, toggle, animation triggersetState
Single shared value (simple)ValueNotifier or StateProvider
Feature with loading/error/successAsyncNotifierProvider (Riverpod)
Mutable state with business logicNotifierProvider (Riverpod)
Complex event flows, event trackingBLoC (alternative)

Detailed Guides

TopicGuideUse When
Riverpodriverpod.mdPrimary - most features, DI, async state
BLoC Patternbloc.mdAlternative - complex event-driven flows

Riverpod Quick Start

Setup

dart
// main.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

Provider with Code Generation

dart
// 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

dart
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)

dart
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)

dart
// 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

dart
@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

dart
@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

MethodRebuildsUse In
ref.watch()Yesbuild() method
ref.read()NoCallbacks, event handlers
ref.listen()No (triggers callback)Side effects
dart
@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:

ScenarioWhy BLoC
Event logging/analyticsEvents create audit trail
Complex event transformationson<Event> handlers with debounce, throttle
Undo/redo functionalityEvent replay capability
Strict separation of concernsEvents as explicit API

For most features, Riverpod's NotifierProvider or AsyncNotifierProvider is simpler and sufficient.

Provider Types Summary

TypeUse CaseExample
@riverpod functionComputed/derived valuesthemeModeProvider
NotifierProviderMutable sync statecounterProvider
AsyncNotifierProviderMutable async stateauthProvider
FutureProviderRead-only async dataproductsProvider
StreamProviderReal-time datamessagesProvider
Family (parameterized)Per-ID datauserProvider(userId: '123')

Anti-Patterns

AvoidInstead
Business logic in widgetsMove to Notifier classes
ref.read in build()Use ref.watch for reactive updates
Manual loading/error booleansUse AsyncValue from AsyncNotifierProvider
Global mutable stateUse scoped providers with Riverpod
setState after async without mounted checkUse Riverpod (handles lifecycle)
Nested ProviderScope (usually)Single root scope, use overrides for testing