DEV Community

Cover image for Deep Dive into Popular Flutter Packages: Riverpod
مصعب يوسف
مصعب يوسف

Posted on Originally published at blog.stackademic.com on

Deep Dive into Popular Flutter Packages: Riverpod

Deep Dive into Popular Flutter Packages: Riverpod

Flutter is a popular open-source UI toolkit that simplifies building natively compiled applications for mobile, web, and desktop platforms. One of Flutter’s key strengths is its rich ecosystem of third-party packages, particularly those designed to manage state effectively. In this article, we will take a deep dive into one of the most popular state management solutions in Flutter: Riverpod. We will also provide an advanced example to illustrate its capabilities in state management, along with best practices and deeper insights into advanced features that make Riverpod an essential tool for developers.

What is Riverpod?

Riverpod is a modern, flexible, and type-safe state management solution for Flutter and Dart applications. Developed by Remi Rousselet, the creator of the Provider package, Riverpod addresses some of the limitations of Provider while providing a better developer experience, improved performance, and increased flexibility.

Riverpod is built with three main objectives in mind:

  1. Compile-time safety: Leveraging Dart’s type system and IDE capabilities, Riverpod catches common errors at compile time, reducing runtime issues. This means developers can identify problems earlier in the development cycle, leading to fewer bugs and a more reliable application.
  2. Scalability: Riverpod’s design scales seamlessly with application complexity, making it suitable for both small and large applications. Whether you’re working on a simple app or a multi-featured, enterprise-level application, Riverpod’s scalability ensures smooth performance and state management.
  3. Testability: Riverpod makes unit testing easy by enabling dependency injection without excessive boilerplate. Its structured approach allows developers to easily create mock implementations and isolate state logic, ensuring robust and maintainable tests.

Why Use Riverpod?

State management is one of the most challenging parts of developing a Flutter application, especially as it grows in complexity. Riverpod provides a modern approach to state management that addresses common challenges, such as:

  • Global state sharing: Manage and share state easily between different parts of your application without relying on cumbersome navigation strategies. This global state management approach is especially beneficial when you need to share data across multiple screens or features.
  • Better reactivity: Riverpod provides reactive data-binding capabilities, ensuring that UI updates are optimized for performance. This means that only the relevant parts of your UI will re-render when changes occur, reducing unnecessary updates and improving the user experience.
  • Type safety: Riverpod’s type-safe approach ensures that errors are caught at compile-time, reducing runtime crashes and improving code maintainability. Type safety is crucial for preventing unexpected behaviors and ensuring that your code adheres to expected data structures.

Key Features of Riverpod

Riverpod is a versatile and powerful package that provides several key features to improve state management in Flutter:

1. Provider Simplification

Riverpod is an improved version of the Provider package that builds on its best parts. The name Riverpod is actually an anagram of “Provider”, symbolizing its relationship while offering new features.

  • No BuildContext required: Unlike the traditional Provider, Riverpod allows you to access providers without relying on BuildContext. This makes it easier to work with state outside of the widget tree and improves modularity. For example, accessing state during asynchronous operations or background tasks becomes much simpler.
  • Improved disposal: Riverpod automatically manages resource disposal, preventing memory leaks and improving performance. By handling resource disposal internally, developers can focus more on the business logic rather than worrying about cleaning up unused resources.

2. AutoDispose and Scoped Providers

  • AutoDispose: Riverpod offers AutoDispose modifiers for providers, automatically disposing of unused state to manage resources efficiently. This is especially helpful for temporary states that are only needed for short durations, such as a form field or a search result.
  • Scoped Providers: Scoped providers let you define providers within a specific scope, which is useful when different parts of your application need unique instances of the same provider. Scoped providers give you better control over resource allocation and make it easier to manage complex dependencies.

3. Support for Asynchronous Operations

Flutter applications often need to fetch data from remote sources or perform other asynchronous tasks. Riverpod simplifies managing async tasks by offering FutureProvider and StreamProvider classes, allowing you to handle loading and error states gracefully.

  • FutureProvider: Handles future-based asynchronous operations and allows the UI to react to data changes. For example, fetching user details from an API can be managed using FutureProvider with proper loading and error handling mechanisms.
  • StreamProvider: Handles stream-based data, ideal for listening to real-time data changes, such as a live chat or stock price updates. By utilizing StreamProvider, developers can easily implement reactive, event-driven user interfaces.

4. Provider Overriding

Riverpod allows you to override providers at runtime, which is especially useful for testing. With this feature, you can inject mock implementations or stub out providers without modifying the original provider. This makes unit testing and integration testing more convenient, as you can simulate different states and behaviors without modifying your core logic.

Setting Up Riverpod

Getting started with Riverpod is straightforward. Here are the steps to set it up in your Flutter project.

Step 1: Add Dependencies

Add the required Riverpod dependencies to your pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  flutter_riverpod: ^2.0.0
Enter fullscreen mode Exit fullscreen mode

Run flutter pub get to install the dependencies.

Step 2: Wrap Your App with ProviderScope

Riverpod requires wrapping your app with a ProviderScope widget, which stores the state of all providers.

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

class HomeScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final counter = ref.watch(counterProvider);
    return Scaffold(
      appBar: AppBar(title: Text('Riverpod Example')),
      body: Center(
        child: Text('Counter value: $counter'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => ref.read(counterProvider.notifier).state++,
        child: Icon(Icons.add),
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Create a Provider

Providers are the core of Riverpod. They store the state and notify listeners when the state changes. Here, we’ll create a simple Provider that stores an integer counter.

final counterProvider = StateProvider<int>((ref) => 0);

class HomeScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final counter = ref.watch(counterProvider);
    return Scaffold(
      appBar: AppBar(title: Text('Riverpod Example')),
      body: Center(
        child: Text('Counter value: $counter'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => ref.read(counterProvider.notifier).state++,
        child: Icon(Icons.add),
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

In the above code:

  • We define a counterProvider using StateProvider to hold an integer state.
  • In HomeScreen, we use the ref.watch(counterProvider) method to listen to state changes.
  • The FloatingActionButton increments the counter value by accessing the provider’s notifier (ref.read(counterProvider.notifier).state++).

Advanced Example: Creating a Todo App with Riverpod

Now that we have covered the basics, let’s create an advanced example to showcase Riverpod’s capabilities. We will build a simple Todo App where users can add, mark as complete, and remove tasks.

Step 1: Create Providers for Todo Management

First, let’s create a StateNotifier and a StateNotifierProvider to manage the state of the list of todos.

import 'package:flutter_riverpod/flutter_riverpod.dart';

class Todo {
  final String description;
  final bool isCompleted;
  Todo({required this.description, this.isCompleted = false});
  Todo copyWith({String? description, bool? isCompleted}) {
    return Todo(
      description: description ?? this.description,
      isCompleted: isCompleted ?? this.isCompleted,
    );
  }
}
class TodoListNotifier extends StateNotifier<List<Todo>> {
  TodoListNotifier() : super([]);
  void add(String description) {
    state = [...state, Todo(description: description)];
  }
  void toggleComplete(int index) {
    state = [
      for (int i = 0; i < state.length; i++)
        if (i == index)
          state[i].copyWith(isCompleted: !state[i].isCompleted)
        else
          state[i],
    ];
  }
  void remove(int index) {
    state = [
      for (int i = 0; i < state.length; i++)
        if (i != index) state[i],
    ];
  }
}
final todoListProvider = StateNotifierProvider<TodoListNotifier, List<Todo>>((ref) {
  return TodoListNotifier();
});
Enter fullscreen mode Exit fullscreen mode

In this code:

  • We define a Todo class with description and isCompleted fields.
  • The TodoListNotifier manages a list of todos and includes methods to add, toggle completion, and remove todos.
  • We use a StateNotifierProvider to expose the TodoListNotifier to the rest of the app.

Step 2: Build the Todo App UI

Now, let’s create the UI for the Todo app.

class TodoApp extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final todos = ref.watch(todoListProvider);
    final todoNotifier = ref.read(todoListProvider.notifier);


return Scaffold(
      appBar: AppBar(title: Text('Riverpod Todo App')),
      body: ListView.builder(
        itemCount: todos.length,
        itemBuilder: (context, index) {
          final todo = todos[index];
          return ListTile(
            title: Text(
              todo.description,
              style: TextStyle(
                decoration: todo.isCompleted ? TextDecoration.lineThrough : null,
              ),
            ),
            leading: Checkbox(
              value: todo.isCompleted,
              onChanged: (_) => todoNotifier.toggleComplete(index),
            ),
            trailing: IconButton(
              icon: Icon(Icons.delete),
              onPressed: () => todoNotifier.remove(index),
            ),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => _showAddTodoDialog(context, todoNotifier),
        child: Icon(Icons.add),
      ),
    );
  }
  void _showAddTodoDialog(BuildContext context, TodoListNotifier todoNotifier) {
    final controller = TextEditingController();
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: Text('Add Todo'),
        content: TextField(
          controller: controller,
          decoration: InputDecoration(hintText: 'Enter todo description'),
        ),
        actions: [
          TextButton(
            onPressed: () {
              todoNotifier.add(controller.text);
              Navigator.of(context).pop();
            },
            child: Text('Add'),
          ),
        ],
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Running the Todo App

Finally, update the main.dart file to use the TodoApp widget:

void main() {
  runApp(
    ProviderScope(
      child: MaterialApp(
        home: TodoApp(),
      ),
    ),
  );
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

Riverpod is a powerful and flexible state management solution for Flutter applications. It improves upon the limitations of the traditional Provider package, offering better performance, compile-time safety, and simplified testing. With features like auto-dispose, scoped providers, support for asynchronous operations, and runtime provider overrides, Riverpod is well-suited for both small and large projects.

In this advanced example, we built a Todo app using Riverpod’s StateNotifier and StateNotifierProvider. This example demonstrates how Riverpod can handle complex state changes while maintaining testability, scalability, and ease of use.

If you’re looking for a modern, type-safe, and testable state management solution, Riverpod is definitely worth considering. Its intuitive API and versatility make it a popular choice among Flutter developers, ensuring your apps are scalable and easy to maintain.

Stackademic 🎓

Thank you for reading until the end. Before you go:

Top comments (0)