website/docs/migration/from_change_notifier.mdx
import old from "!!raw-loader!./from_change_notifier/old.dart"; import declaration from "./from_change_notifier/declaration"; import initialization from "./from_change_notifier/initialization"; import migrated from "./from_change_notifier/migrated";
import { Link } from "/src/components/Link"; import { AutoSnippet } from "/src/components/CodeSnippet";
Within Riverpod, ChangeNotifierProvider is meant to be used to offer a smooth transition from
pkg:provider.
If you've just started a migration to pkg:riverpod, make sure you read the dedicated guide
(see <Link documentID="from_provider/quickstart" />).
This article is meant for folks that already transitioned to riverpod, but want to move away from
ChangeNotifier.
All in all, migrating from ChangeNotifier to AsyncNotifier requires a
paradigm shift, but it brings great simplification with the resulting migrated
code.
Take this (faulty) example: <AutoSnippet raw={old} />
This implementation shows several weak design choices such as:
isLoading and hasError to handle different asynchronous casestry/catch/finally expressionsnotifyListeners at the right times to make this implementation workNote how this example has been crafted to show how ChangeNotifier can lead to faulty design choices
for newbie developers; also, another takeaway is that mutable state might be way harder than it
initially promises.
Notifier/AsyncNotifier, in combination with immutable state, can lead to better design choices
and less errors.
Let's see how to migrate the above snippet, one step at a time, towards the newest APIs.
First, we should declare the new provider / notifier: this requires some thought process which depends on your unique business logic.
Let's summarize the above requirements:
List<Todo>, which obtained via a network call, with no parametersloading, error and data state:::tip The above thought process boils down to answering the following questions:
y: Use riverpod's class-based APIn: Use riverpod's function-based APIy: Let build return a Future<T>n: Let build simply return Ty: Let build (or your function) accept themn: Let build (or your function) accept no extra parameters
::::::info
If you're using codegen, the above thought process is enough.
There's no need to think about the right class names and their specific APIs.
@riverpod only asks you to write a class with its return type, and you're good to go.
:::
Technically, the best fit here is to define a AsyncNotifier<List<Todo>>,
which meets all the above requirements. Let's write some pseudocode first.
<AutoSnippet language="dart" {...declaration}></AutoSnippet>
:::tip Remember: use snippets in your IDE to get some guidance, or just to speed up your code writing. See <Link documentID="introduction/getting_started" hash="going-further-installing-code-snippets" />. :::
With respect with ChangeNotifier's implementation, we don't need to declare todos anymore;
such variable is state, which is implicitly loaded with build.
Indeed, riverpod's notifiers can expose one entity at a time.
:::tip Riverpod's API is meant to be granular; nonetheless, when migrating, you can still define a custom entity to hold multiple values. Consider using Dart 3's records to smooth out the migration at first. :::
Initializing a notifier is easy: just write initialization logic inside build.
We can now get rid of the old _init function.
<AutoSnippet language="dart" {...initialization}></AutoSnippet>
With respect of the old _init, the new build isn't missing anything: there is no need to
initialize variables such as isLoading or hasError anymore.
Riverpod will automatically translate any asynchronous provider, via exposing an AsyncValue<List<Todo>>
and handles the intricacies of asynchronous state way better than what two simple boolean flags can do.
Indeed, any AsyncNotifier effectively makes writing additional try/catch/finally an anti-pattern
for handling asynchronous state.
Just like initialization, when performing side effects there's no need to manipulate boolean flags
such as hasError, or to write additional try/catch/finally blocks.
Below, we've cut down all the boilerplate and successfully fully migrated the above example: <AutoSnippet language="dart" {...migrated} />
:::tip Syntax and design choices may vary, but in the end we just need to write our request and update state afterwards. See <Link documentID="concepts2/providers" />. :::
Let's review the whole migration process applied above, from a operational point of view.
buildtodos, isLoading and hasError properties: internal state will sufficetry-catch-finally blocks: returning the future is enoughaddTodo)state