正在加载,请稍候…

Flutter Advanced State Management: Riverpod 2.0 Deep Dive

Master Flutter state management with Riverpod 2.0, including code generation, async providers, migration from Provider, and real-world architecture patterns.

Flutter Advanced State Management: Riverpod 2.0 Deep Dive

State management remains one of the most discussed topics in Flutter development. Riverpod 2.0 has emerged as the leading solution for complex applications, offering compile-time safety, dependency injection, and powerful async state handling.

Why Riverpod Over Provider?

Riverpod solves several fundamental limitations of Provider:

  • No BuildContext required: Providers are declared globally and accessed anywhere
  • Compile-time safety: Invalid provider combinations fail at compile time
  • Multiple providers of same type: No Provider.of conflicts
  • Testability: Easy to override providers in tests
  • DevTools integration: Built-in state inspection

Setting Up Riverpod 2.0 with Code Generation

# pubspec.yaml
dependencies:
  flutter_riverpod: ^2.5.0
  riverpod_annotation: ^2.3.0

dev_dependencies:
  riverpod_generator: ^2.4.0
  build_runner: ^2.4.0
  riverpod_lint: ^2.3.0

Run code generation:

dart run build_runner watch --delete-conflicting-outputs

Core Riverpod 2.0 Concepts

Riverpod 2.0 with codegen uses a single @riverpod annotation:

import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'providers.g.dart';

// Simple synchronous provider
@riverpod
String appVersion(AppVersionRef ref) => '2.0.0';

// Future provider (async)
@riverpod
Future<List<User>> users(UsersRef ref) async {
  final repository = ref.watch(userRepositoryProvider);
  return repository.fetchAll();
}

// Stream provider
@riverpod
Stream<List<Message>> messages(MessagesRef ref, {required String roomId}) {
  final repository = ref.watch(messageRepositoryProvider);
  return repository.streamMessages(roomId);
}

// Notifier (replaces ChangeNotifier)
@riverpod
class Counter extends _$Counter {
  @override
  int build() => 0;

  void increment() => state++;
  void decrement() => state--;
  void reset() => state = 0;
}

AsyncNotifier for Complex Async State

@riverpod
class UserProfile extends _$UserProfile {
  @override
  Future<UserData> build(String userId) async {
    final repository = ref.watch(userRepositoryProvider);
    return repository.fetchUser(userId);
  }

  Future<void> updateName(String newName) async {
    state = AsyncData(state.requireValue.copyWith(name: newName));
    try {
      await ref.read(userRepositoryProvider).updateName(
        userId: state.requireValue.id,
        name: newName,
      );
    } catch (e, stack) {
      state = AsyncError(e, stack);
      ref.invalidateSelf();
    }
  }
}

Advanced Provider Patterns

Family Providers

@riverpod
Future<Product> product(ProductRef ref, int productId) async {
  final repository = ref.watch(productRepositoryProvider);
  return repository.fetchProduct(productId);
}

// Usage in widget
class ProductCard extends ConsumerWidget {
  final int productId;
  const ProductCard({required this.productId, super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final productAsync = ref.watch(productProvider(productId));
    return productAsync.when(
      data: (product) => ProductView(product: product),
      loading: () => const ProductSkeleton(),
      error: (e, _) => ErrorCard(message: e.toString()),
    );
  }
}

Provider Dependencies and Auto-Dispose

@Riverpod(keepAlive: false)
Future<SearchResults> searchResults(
  SearchResultsRef ref,
  String query,
) async {
  final cancelToken = CancelToken();
  ref.onDispose(cancelToken.cancel);

  return ref.read(searchRepositoryProvider).search(
    query: query,
    cancelToken: cancelToken,
  );
}

Migration from Provider

Before (Provider):

class UserNotifier extends ChangeNotifier {
  UserData? _user;
  bool _loading = false;

  Future<void> loadUser(String id) async {
    _loading = true;
    notifyListeners();
    _user = await UserRepository().fetchUser(id);
    _loading = false;
    notifyListeners();
  }
}

After (Riverpod 2.0):

@riverpod
Future<UserData> user(UserRef ref, String id) async {
  final repository = ref.watch(userRepositoryProvider);
  return repository.fetchUser(id);
}

class UserScreen extends ConsumerWidget {
  final String userId;
  const UserScreen({required this.userId, super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return ref.watch(userProvider(userId)).when(
      data: (user) => UserView(user: user),
      loading: () => const CircularProgressIndicator(),
      error: (e, _) => Text(e.toString()),
    );
  }
}

Async State Handling: Refresh and Retry

class UserList extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final usersAsync = ref.watch(usersProvider);

    return RefreshIndicator(
      onRefresh: () => ref.refresh(usersProvider.future),
      child: usersAsync.when(
        skipLoadingOnReload: true,
        data: (users) => ListView.builder(
          itemCount: users.length,
          itemBuilder: (_, i) => UserTile(user: users[i]),
        ),
        loading: () => const ShimmerList(),
        error: (e, _) => ErrorView(
          message: e.toString(),
          onRetry: () => ref.invalidate(usersProvider),
        ),
      ),
    );
  }
}

Testing with Riverpod

void main() {
  test('loads user successfully', () async {
    final container = ProviderContainer(
      overrides: [
        userRepositoryProvider.overrideWith(
          () => MockUserRepository(user: fakeUser),
        ),
      ],
    );
    addTearDown(container.dispose);

    await container.read(userProfileProvider('user-1').future);

    expect(
      container.read(userProfileProvider('user-1')),
      isA<AsyncData<UserData>>()
        .having((d) => d.value.name, 'name', 'Jane Doe'),
    );
  });
}

Performance: select() to Prevent Rebuilds

// Only rebuilds when userName changes, not other user fields
final userName = ref.watch(
  userProfileProvider(userId).select(
    (userAsync) => userAsync.whenData((u) => u.name),
  ),
);

Architecture: Feature-First with Riverpod

lib/
  features/
    auth/
      data/auth_repository.dart
      domain/user.dart
      presentation/
        providers/auth_providers.dart
        screens/login_screen.dart
    products/...
  shared/
    providers/
      dio_provider.dart
      hive_provider.dart

Conclusion

Riverpod 2.0 with code generation represents a major leap forward in Flutter state management. Its compile-time safety, powerful async handling, and clean dependency injection make it the right choice for production Flutter applications. The migration path from Provider is incremental and well-documented, scaling from simple counters to complex multi-feature applications.