_CORE
AI & Agentic Systems Core Information Systems Cloud & Platform Engineering Data Platform & Integration Security & Compliance QA, Testing & Observability IoT, Automation & Robotics Mobile & Digital Banking & Finance Insurance Public Administration Defense & Security Healthcare Energy & Utilities Telco & Media Manufacturing Logistics & E-commerce Retail & Loyalty
References Technologies Blog Know-how Tools
About Collaboration Careers
CS EN
Let's talk

Riverpod — State Management for Flutter

23. 07. 2024 2 min read intermediate

Riverpod is a modern state management solution for Flutter. Learn providers, code generation, AsyncValue and advanced patterns for scalable applications.

Introduction to Flutter State Management

Riverpod is a modern state management solution for Flutter that addresses many limitations of Provider and InheritedWidget. It offers compile-time safety, better testability, and improved developer experience through code generation and strong typing.

In this article, we’ll explore key concepts, practical implementations, and best practices you need to know for effective use in production projects. Modern mobile development requires deep understanding of state management patterns, and Riverpod provides an excellent foundation.

Architecture and Key Concepts

The foundation of successful Flutter application with Riverpod is understanding its architecture and fundamental concepts. The system is designed with scalability, maintainability, and developer ergonomics in mind.

Key architectural layers include: - Presentation layer - Widgets and UI components - Business logic - Providers and state management - Data layer - Repositories and data sources - Infrastructure layer - Network, storage, and services

// Basic Riverpod provider example
@riverpod
class Counter extends _$Counter {
  @override
  int build() => 0;

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

// Usage in widget
class CounterWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider);

    return Column(
      children: [
        Text('Count: $count'),
        ElevatedButton(
          onPressed: () => ref.read(counterProvider.notifier).increment(),
          child: Text('Increment'),
        ),
      ],
    );
  }
}

Providers and Code Generation

Riverpod supports code generation which provides compile-time safety and reduces boilerplate. Use @riverpod annotation to generate providers automatically:

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

@riverpod
class UserNotifier extends _$UserNotifier {
  @override
  Future<User?> build(String userId) async {
    final repository = ref.watch(userRepositoryProvider);
    return repository.getUser(userId);
  }

  Future<void> updateUser(User user) async {
    state = const AsyncValue.loading();
    state = await AsyncValue.guard(() async {
      final repository = ref.watch(userRepositoryProvider);
      return repository.updateUser(user);
    });
  }
}

AsyncValue and Error Handling

Riverpod provides AsyncValue for handling asynchronous operations with built-in loading, error, and data states:

class UserProfile extends ConsumerWidget {
  final String userId;

  const UserProfile({required this.userId});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final userAsync = ref.watch(userNotifierProvider(userId));

    return userAsync.when(
      data: (user) => UserWidget(user: user),
      loading: () => const CircularProgressIndicator(),
      error: (error, stack) => ErrorWidget(error: error),
    );
  }
}

Testing with Riverpod

Riverpod makes testing easy with provider overrides:

void main() {
  testWidgets('Counter increments', (tester) async {
    await tester.pumpWidget(
      ProviderScope(
        overrides: [
          counterProvider.overrideWith(() => Counter()),
        ],
        child: MyApp(),
      ),
    );

    expect(find.text('0'), findsOneWidget);
    await tester.tap(find.byType(ElevatedButton));
    await tester.pump();
    expect(find.text('1'), findsOneWidget);
  });
}

Best Practices

  1. Use code generation - Leverage @riverpod for type safety
  2. Handle AsyncValue properly - Always handle loading and error states
  3. Keep providers focused - One provider per responsibility
  4. Use family for parametrized providers - Pass parameters to providers
  5. Test with overrides - Mock dependencies in tests

Riverpod provides a robust foundation for Flutter state management with excellent developer experience and production-ready patterns.

flutterriverpodstate managementdart
Share:

CORE SYSTEMS tým

Stavíme core systémy a AI agenty, které drží provoz. 15 let zkušeností s enterprise IT.