Skip to main content

Riverpod Code Generation

· 8 min read
Charles Tsang
Flutter Developer
Cover

Riverpod offers a code generation tool that helps to enhance the developer experience. Through code generation, Riverpod simplifies the syntax to define providers and notifiers, reducing the amount of boilerplate code and enhancing the developer experience. It also provides support for multiple family parameters, including named parameters and positional parameters.

In the previous article, we covered the fundamental concepts of Riverpod. In this article, we will dive deeper into the topic of code generation in Riverpod, exploring how it can improve the overall developer experience.

Introduction

Riverpod offers a diverse range of providers and notifiers, along with their modifiers, to cater to various use cases. However, manually defining providers and notifiers can be tedious and error-prone. Code generation can significantly simplify the syntax needed to define providers and notifiers, reducing the amount of boilerplate code and enhancing the overall developer experience.

To use code generation in Riverpod, run the following command:

To use Riverpod with code generation, run the following command:

flutter pub add riverpod_annotation
flutter pub add dev:riverpod_generator
flutter pub add dev:build_runner

build_runner

Similar to other code generation packages in Dart such as json_serializable and freezed, riverpod_generator uses build_runner to generate the files.

You can either perform a one-time generation of the files or set up a watch mode to automatically generate the files when the source files change.

To generate the files manually, run the following command:

dart run build_runner build -d

If you want to enable the watch mode, which continuously monitors your source files and generates the updated files automatically, you can use the following command instead:

dart run build_runner watch -d

To stop the watch mode, simply press Ctrl + C in the terminal.

info

The -d flag is used to skip the user prompt and delete any conflicting files from previous builds.

Explorer File Nesting

If you find that the generated files are cluttering your project structure, you can leverage the Explorer File Nesting feature in Visual Studio Code to keep the generated files organised.

To enable file nesting in Visual Studio Code, add the following configuration to your settings.json file:

settings.json
{
"explorer.fileNesting.enabled": true,
"explorer.fileNesting.expand": false,
"explorer.fileNesting.patterns": {
"*.dart": "$(capture).g.dart,
},
}

This will group all the generated Dart files (those with the .g.dart extension) with their corresponding source files in a single directory within the Visual Studio Code file explorer.

The explorer.fileNesting.expand setting is set to false, which means the nested files will be initially collapsed, reducing the visual clutter in the file explorer. You can then expand the nested files as needed.

Explorer File Nesting

JetBrains IDEs like IntelliJ IDEA and Android Studio also support file nesting. For more information, check out the File Nesting Rules documentation.

By grouping the generated files with their corresponding sources, you can maintain a clean and organised project structure. This can improve the overall navigability and readability of your codebase, making it easier to work with and maintain over time.

tip

You can also group other related files. For example, the pubspec.yaml with pubspec.lock, build.yaml and analysis_options.yaml.

Syntax

To define providers and notifiers with code generation, you need to import the riverpod_annotation package and add the @riverpod annotation to the function or class.

Then add the part directive to specify the generated file that will contain the provider or notifier. The generated file will have the same name as the source file with the suffix .g.dart.

file_name.dart
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'file_name.g.dart';


int number(NumberRef ref) {
return 0;
}

The syntax to define a Provider, FutureProvider and StreamProvider is similar to the vanilla approach with one key difference:

  • No explicit provider type

Instead, Riverpod will automatically infer the provider type from the return type of the function.

  • Provider: If the function returns a type that is not a Future or Stream.
  • FutureProvider: If the function returns a Future.
  • StreamProvider: If the function returns a Stream.

Note that the generated provider will have the same name as the function with the suffix Provider. For example, the generated provider name for the function number will be numberProvider.

Provider Syntax

The syntax to define a Notifier, AsyncNotifier, StreamNotifier and their corresponding providers is similar to the vanilla approach. However, there are a couple of key differences:

  • No explicit notifier type: Riverpod will automatically infer the notifier type from the return type of the build method.
  • No explicit provider declaration: Riverpod will automatically generate the provider for you.

Note that the generated provider will have the same name as the notifier class with the suffix Provider. For instance, the generated provider name for the CounterNotifier class will be counterNotifierProvider.

Notifier Syntax

The below examples demonstrate how to define providers and notifiers with code generation:

Provider

number_provider.dart
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'number_provider.g.dart';


int number(NumberRef ref) {
return 0;
}

FutureProvider

users_provider.dart
import 'dart:convert';

import 'package:http/http.dart' as http;
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'users_provider.g.dart';


Future<List<User>> users(UsersRef ref) async {
final uri = Uri.https('jsonplaceholder.typicode.com', '/users');
final response = await http.get(uri);
final json = jsonDecode(utf8.decode(response.bodyBytes)) as List<dynamic>;
final users = json.map((e) => User.fromJson(e as Map<String, dynamic>)).toList();
return users;
}

StreamProvider

timer_provider.dart
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'timer_provider.g.dart';


Stream<int> timer(TimerRef ref) {
return Stream.periodic(const Duration(seconds: 1), (x) => x);
}

NotifierProvider

Notice that we do not have to declare the NotifierProvider explicitly with code generation. The code generation tool will generate the NotifierProvider for us.

counter_notifier_provider.dart
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'counter_notifier_provider.g.dart';


class CounterNotifier extends _$CounterNotifier {

int build() {
return 0;
}

void increment() {
state++;
}
}

AsyncNotifierProvider

users_provider.dart
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'users_provider.g.dart';


class Users extends _$Users {

Future<List<User>> build() async {
final uri = Uri.https('jsonplaceholder.typicode.com', '/users');
final response = await http.get(uri);
final json = jsonDecode(utf8.decode(response.bodyBytes)) as List<dynamic>;
final users = json.map((e) => User.fromJson(e as Map<String, dynamic>)).toList();
return users;
}

Future<void> add(User user) async {
final uri = Uri.https('jsonplaceholder.typicode.com', '/users');
await http.post(uri, body: jsonEncode(user.toJson()));
}
}

StreamNotifierProvider

users_provider.dart
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'users_provider.g.dart';


class UsersNotifier extends _$UsersNotifier {

Stream<List<User>> build() {
return ...
}

Future<void> add(User user) async {
...
}
}

AutoDispose

riverpod_generator promotes the auto-dispose behavior for providers and therefore providers created with code generation have auto-dispose enabled by default. This means that the provider will automatically be disposed when it is no longer being subscribed to.

The auto-dispose behavior is designed to help optimise your application's memory usage by ensuring that providers are only kept alive as long as they are needed. When a provider is no longer being used, it will be automatically disposed, freeing up resources and preventing potential memory leaks.

However, there may be cases where you want to disable the auto-dispose behavior and keep the provider alive, even if it's not being actively used. To do this, you can use the @Riverpod annotation and set the keepAlive parameter to true:

(keepAlive: true)
int number(NumberRef ref) => 0;

Family

Riverpod's code generation supports defining family providers and notifiers with multiple parameters, including named parameters and positional parameters.

To define a family provider, you can specify the parameters in the function:

Family Provider with Named Parameters

String foo(FooRef ref, int a, {required int b, int? c, int d = 0}) {
return 'I am a family provider with named parameters';
}

To define a family notifier, you can specify the parameters in the build method:

Family Notifier with Positional Parameters:

class Bar extends _$Bar {

String build(int a, [int? b, int c = 0]) {
return 'I am a family notifier with position parameters';
}
}

Generator configuration

riverpod_generator has a default naming convention for the generated providers: the provider name will have the suffix Provider. However, you can customise the prefix and suffix of the provider names to better align with your project's naming conventions.

To configure the naming convention for the generated providers, you can create a build.yaml file in the root of your project and specify prefix and suffix for the provider names.

For example, to change the provider name suffix from Provider to Pod, you can add the following configuration to your build.yaml file:

build.yaml
targets:
$default:
builders:
riverpod_generator:
options:
provider_name_prefix: ""
provider_family_name_prefix: ""
provider_name_suffix: "Pod"
provider_family_name_suffix: "Pod"

With this configuration, the following provider declaration will result in a provider named numberPod instead of the default numberProvider.


int number(NumberRef ref) {
return 0;
}

Flutter Riverpod Snippets

Flutter Riverpod Snippets is a Visual Studio Code extension that provides a set of handy code snippets to create providers and notifiers.

The below GIFs demonstrate how to use the extension to create a Provider and a NotifierProvider:

Provider Snippet Demo Notifier Snippet Demo

The table below summarises the snippets for creating providers and notifiers with code generation:

SnippetProvider/NotifierKeep Alive
riverpodProvider
riverpodKeepAliveProvider
riverpodFutureFutureProvider
riverpodFutureKeepAliveFutureProvider
riverpodStreamStreamProvider
riverpodStreamKeepAliveStreamProvider
riverpodClassNotifier & NotifierProvider
riverpodClassKeepAliveNotifier & NotifierProvider
riverpodAsyncClassAsyncNotifier & AsyncNotifierProvider
riverpodAsyncClassKeepAliveAsyncNotifier & AsyncNotifierProvider
riverpodStreamClassStreamNotifier & StreamNotifierProvider
riverpodStreamClassKeepAliveStreamNotifier & StreamNotifierProvider

Conclusion

Let's summarise the key topic we've covered:

  • Setting up Riverpod with code generation
  • Using build_runner to generate files
  • Organising generated files
  • Syntax for defining providers and notifiers with code generation
  • Configuring the naming convention for generated providers
  • Using the Flutter Riverpod Snippets extension

If you haven't already tried using Riverpod with code generation, I highly recommend giving it a try. The improved developer experience can make a significant difference in your Riverpod-based projects.