riverpod icon indicating copy to clipboard operation
riverpod copied to clipboard

Add a page showcasing how to migrate from `AsyncValue.map/when` to Dart 3's switch-case

Open rrousselGit opened this issue 2 years ago • 19 comments
trafficstars

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');
}

rrousselGit avatar Jul 11 '23 14:07 rrousselGit

Is 'case` unnecessary?

スクリーンショット 2023-08-08 22 45 05

dai10512 avatar Aug 08 '23 13:08 dai10512

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';
}

rrousselGit avatar Aug 08 '23 15:08 rrousselGit

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');
}

dai10512 avatar Aug 09 '23 02:08 dai10512

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');
}

AhmedLSayed9 avatar Aug 14 '23 21:08 AhmedLSayed9

No need. Checking AsyncData/AsyncError is enough.

rrousselGit avatar Aug 14 '23 23:08 rrousselGit

Ahh You're right. Refreshing return AsyncData with isLoading set to true unlike Rebuild due to ref.watch which return AsyncLoading with value

AhmedLSayed9 avatar Aug 15 '23 03:08 AhmedLSayed9

@rrousselGit Should we switch to using the sealed class modifier (as opposed to the annotation) in AsyncValue?

navaronbracke avatar Aug 16 '23 16:08 navaronbracke

That's for Riverpod 3, as this is a breaking change. (It is currently allowed to subclass AsyncValue)

rrousselGit avatar Aug 28 '23 08:08 rrousselGit

How to define variables in => ? like this image

Adherentman avatar Sep 30 '23 01:09 Adherentman

You can't. Use a normal switch instead, with the case keyword.

rrousselGit avatar Sep 30 '23 12:09 rrousselGit

@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

neiljaywarner avatar Oct 16 '23 16:10 neiljaywarner

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 👀

AhmedLSayed9 avatar Oct 16 '23 17:10 AhmedLSayed9

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: @.***>

neiljaywarner avatar Oct 16 '23 19:10 neiljaywarner

100% a typo

rrousselGit avatar Oct 16 '23 20:10 rrousselGit

what advantage do we get from migrating from when? I believe when is a lot more readable than using switch cases

walidwalid23 avatar Jul 26 '24 05:07 walidwalid23