riverpod
riverpod copied to clipboard
Add a page showcasing how to migrate from `AsyncValue.map/when` to Dart 3's switch-case
Now that Dart 3 has pattern matching, we should stop relying on "when"/"map" and instead use the syntax native to the language. At the same time, migrating is non-trivial
We should:
- Add a documentation page to document how to do the migration by hand
- Possibly add a migration tool to do it automagically (related to https://github.com/rrousselGit/freezed/issues/940)
Although there are some common points with Freezed, Rivepod syntax has special cases. Namely the various flags, such as skipLoadingOnRefresh/Reload & skipError variants.
Goals:
- show how to convert any combination of flags to pattern matching
- offer a default recommended syntax for common cases
- Make sure we support
AsyncValue.data(null) - In Riverpod <3, AsyncValue is not "sealed". So migration should support a non-exhaustive switch.
Default "when" syntax (skipLoadingOnReload: false, skipLoadingOnRefresh: true, skipError: false)
Before:
asyncValue.when(
data: (value) => print(value),
error: (error, stack) => print('Error $error'),
loading: () => print('loading'),
);
After
switch (asyncValue) {
case AsyncData(:final value): print(data);
case AsyncError(:final error): print('Error $error');
case _: print('loading');
}
"when" with skipLoadingOnReload: true
Before:
asyncValue.when(
skipLoadingOnReload: true,
data: (value) => print(value),
error: (error, stack) => print('Error $error'),
loading: () => print('loading'),
);
After
switch (asyncValue) {
case AsyncValue(:final error?): print('Error $error'); // Make sure to check errors first
case AsyncValue(:final value, hasData: true): print(data);
case _: print('loading');
}
Alternatively, if the value is non-nullable, we can do:
switch (asyncValue) {
case AsyncValue(:final error?): print('Error $error'); // Make sure to check errors first
case AsyncValue(:final value?): print(data);
case _: print('loading');
}
"when" with skipError: true + skipLoadingOnReload: true
Before:
asyncValue.when(
skipLoadingOnReload: true,
skipError: true,
data: (value) => print(value),
error: (error, stack) => print('Error $error'),
loading: () => print('loading'),
);
After
switch (asyncValue) {
case AsyncValue(:final value, hasData: true): print(data);
case AsyncValue(:final error?): print('Error $error'); // Check errors after data
case _: print('loading');
}
"when" with skipError: true only
Before:
asyncValue.when(
skipError: true,
data: (value) => print(value),
error: (error, stack) => print('Error $error'),
loading: () => print('loading'),
);
After
switch (asyncValue) {
case AsyncValue(:final value, hasData: true, isReloading: false): print(data);
case AsyncValue(:final error?): print('Error $error'); // Check errors after data
case _: print('loading');
}
Is 'case` unnecessary?
It depends on where the switch is used.
You used it in an expression, so no case and => instead of :
var x = switch (value) {
AsyncData(...) => 'Hello',
};
vs:
var x;
switch (value) {
case AsyncData(...):
x = 'Hello';
}
Oh, I have just misread your below code!! Fine, I really appreciate you 🙇
switch (asyncValue) {
case AsyncData(:final value): print(data);
case AsyncError(:final error): print('Error $error');
case _: print('loading');
}
Default "when" syntax (skipLoadingOnReload: false, skipLoadingOnRefresh: true, skipError: false)
Before:
asyncValue.when( data: (value) => print(value), error: (error, stack) => print('Error $error'), loading: () => print('loading'), );After
switch (asyncValue) { case AsyncData(:final value): print(data); case AsyncError(:final error): print('Error $error'); case _: print('loading'); }
I think this one should be as the following to apply the default skipLoadingOnRefresh: true:
switch (asyncValue) {
case AsyncValue(:final error?): print('Error $error');
case AsyncValue(:final value, hasValue: true, isReloading: false): print(data);
case _: print('loading');
}
No need. Checking AsyncData/AsyncError is enough.
Ahh You're right. Refreshing return AsyncData with isLoading set to true unlike Rebuild due to ref.watch which return AsyncLoading with value
@rrousselGit Should we switch to using the sealed class modifier (as opposed to the annotation) in AsyncValue?
That's for Riverpod 3, as this is a breaking change. (It is currently allowed to subclass AsyncValue)
How to define variables in => ?
like this
You can't. Use a normal switch instead, with the case keyword.
@rrousselGit i'm so sorry if this is a stupid question, but since you are talking about docs :)
switch (asyncValue) {
case AsyncData(:final value): print(data);
case AsyncError(:final error): print('Error $error');
case _: print('loading');
}
why does the error part use a variable called "error" to match what's next to the :final but the first line uses data instead of $value?
i'm tempted to wonder if it's a typo but i feel like only a 8-48%.. 12-52% chance of it :) I checked https://dart.dev/language/branches and didn't have a eureka moment
i'm tempted to wonder if it's a typo but i feel like only a 8-48%.. 12-52% chance of it :)
100% a typo. I think this is just a prototype of how the doc page will be though.
I'm wondering were the 8-48%.. 12-52% come from 👀
Haha, thanks! this is the kind of high quality project i'm very reluctant to say anyone made a mistake on :)
On Mon, Oct 16, 2023 at 12:28 PM Ahmed Elsayed @.***> wrote:
i'm tempted to wonder if it's a typo but i feel like only a 8-48%.. 12-52% chance of it :)
100% a typo. I think this is just a prototype of how the doc page will be though.
I'm wondering were the 8-48%.. 12-52% come from 👀
— Reply to this email directly, view it on GitHub https://github.com/rrousselGit/riverpod/issues/2715#issuecomment-1764944662, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAXBGYKZODWLVUSLTA4SYYLX7VVE5AVCNFSM6AAAAAA2GCJKN6VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTONRUHE2DINRWGI . You are receiving this because you are subscribed to this thread.Message ID: @.***>
100% a typo
what advantage do we get from migrating from when? I believe when is a lot more readable than using switch cases