Why Use flutter_riverpod?

flutter_riverpod is a modern, testable state‑management package designed for scalable Flutter applications. It offers a clear separation between UI and business logic, works well with clean architecture, and eliminates the need for boilerplate utilities that many developers write from scratch.

Tip: Because Riverpod does not depend on BuildContext for reading providers, you can access state from services, repositories, or even background isolates.

Installation

Add the package to your project with the official Flutter command:

Code
flutter pub add flutter_riverpod

After the command completes, verify the dependency in pubspec.yaml and run flutter pub get if needed.

Core Concepts

  • Provider: The immutable definition of a piece of state or a value.
  • ConsumerWidget: A widget that rebuilds when the providers it watches change.
  • Ref / WidgetRef: The object used inside a ConsumerWidget to read, watch, or listen to providers.
  • ProviderScope: The top‑level widget that stores the state of all providers.

Step‑by‑Step Beginner Example

The following example demonstrates a simple counter using StateProvider and ConsumerWidget. The code is fully runnable in a new Flutter project.

Dart / Flutter
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

// 1️⃣ Define a provider that holds an integer state.
final counterProvider = StateProvider((ref) => 0);

void main() {
  // 2️⃣ Wrap the app with ProviderScope so Riverpod can manage state.
  runApp(const ProviderScope(child: MyApp()));
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Riverpod Counter',
      home: const CounterPage(),
    );
  }
}

// 3️⃣ Use ConsumerWidget to read and react to provider changes.
class CounterPage extends ConsumerWidget {
  const CounterPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // Watch the provider; the widget rebuilds when the value changes.
    final count = ref.watch(counterProvider);

    return Scaffold(
      appBar: AppBar(title: const Text('Riverpod Counter')),
      body: Center(
        child: Text('Count: $count', style: const TextStyle(fontSize: 24)),
      ),
      floatingActionButton: FloatingActionButton(
        // Increment the state using the provider's notifier.
        onPressed: () => ref.read(counterProvider.notifier).state++,
        child: const Icon(Icons.add),
      ),
    );
  }
}

Explanation of the Example

  • The counterProvider is a StateProvider that starts at 0.
  • ProviderScope at the root ensures all descendant widgets can access the provider.
  • ConsumerWidget receives a WidgetRef which lets you watch (rebuild on change) or read (one‑off read) the provider.
  • Calling ref.read(counterProvider.notifier).state++ updates the state, triggering a rebuild of the CounterPage because it watches the provider.

Setup Notes & Best Practices

  • Place Riverpod providers in a dedicated providers/ or state/ folder to keep them isolated from UI code.
  • Prefer immutable providers (e.g., Provider, FutureProvider, StreamProvider) for read‑only data and use StateProvider or StateNotifierProvider for mutable state.
  • When writing unit tests, wrap the widget under test with ProviderScope(overrides: [...]) to inject mock providers.
  • Check the package version compatibility with your Flutter SDK; newer Riverpod releases may introduce breaking changes. Review the changelog on pub.dev.

Common Mistakes to Avoid

  • Forgetting ProviderScope: Without it, providers have no storage and will throw runtime errors.
  • Reading a provider inside initState without ref: Use ref.read inside a ConsumerStatefulWidget or move the logic to a separate service.
  • Mixing Riverpod calls with legacy InheritedWidget patterns: Keep Riverpod usage consistent; avoid calling Provider.of from the same widget tree.
  • Over‑using watch in large widgets: Watching many providers can cause unnecessary rebuilds. Split UI into smaller ConsumerWidgets or use select to watch only needed slices.

FAQ

  • Q: Does flutter_riverpod work with Flutter Web?
    A: Yes. Riverpod is platform‑agnostic and works on mobile, web, and desktop as long as the underlying Flutter SDK supports the target platform.
  • Q: How do I migrate from the older Provider package?
    A: The APIs are similar but not identical. Review the migration guide on the package’s pub.dev page and replace Provider with ProviderScope, then update provider definitions to the Riverpod equivalents.
  • Q: Can I use Riverpod with code generation (e.g., Freezed, JsonSerializable)?
    A: Absolutely. Riverpod does not interfere with code‑gen tools. You can expose generated models via Provider or StateNotifierProvider as needed.
  • Q: Where should I place my provider declarations?
    A: Organise them by feature or domain (e.g., features/auth/providers.dart) and keep them separate from UI widgets to maintain a clean architecture.

For the most up‑to‑date API reference and advanced usage patterns, consult the official documentation at https://pub.dev/packages/flutter_riverpod.

Frequently Asked Questions

Is flutter_riverpod compatible with Flutter Web and Desktop?

Yes. The package is platform‑agnostic and works on mobile, web, and desktop as long as your Flutter SDK supports the target platform.

How can I test Riverpod providers?

Wrap the widget under test with ProviderScope and use the overrides parameter to inject mock providers. This allows you to verify UI behavior without hitting real services.

What is the difference between Provider and ProviderScope?

Provider defines a piece of state or a value, while ProviderScope is a widget that stores the state of all providers in the widget tree. ProviderScope must be placed above any widget that reads a provider.

Can I use Riverpod together with other state‑management solutions?

Technically you can, but mixing patterns often leads to complexity. It is recommended to choose one primary state‑management approach per feature to keep the codebase maintainable.