freezed icon indicating copy to clipboard operation
freezed copied to clipboard

Non-constant default values

Open sdebruyn opened this issue 4 years ago • 37 comments

Thank you for the 0.7.0 update, the default values made my code a little bit simpler :)

One thing that I still have to work around are non-constant default values. My type has a String property called id that - if left empty - should have a random UUID value by default. I now work around this by adding a second factory method that redirects to the generated one without the id parameter.

It would be nice if the @Default annotation could also take in a lambda to generate the default value.

sdebruyn avatar Feb 24 '20 10:02 sdebruyn

Dart only supports constant default values.

Are you looking for @late instead?

rrousselGit avatar Feb 24 '20 10:02 rrousselGit

Ah, I see what you're speaking of. But the syntax using @Default would be horrible as we can't use () => something inside decorators.

What about such syntax?

@freezed
abstract class SuperLate with _$SuperLate {
  factory SuperLate([int value]) = _SuperLate;

  @late
  @override
  int get value => super.value ?? Random().nextInt(9999);
}

rrousselGit avatar Feb 24 '20 11:02 rrousselGit

Yes, that would be great! The late feature isn't enough because it doesn't allow the user to supply a custom value.

sdebruyn avatar Feb 24 '20 11:02 sdebruyn

We'll probably need an alternate syntax for unions too:

@freezed
abstract class SuperLate with _$SuperLate {
  factory SuperLate([@MyDefault() int value]) = _SuperLate;
  factory SuperLate.emoty() = _SuperLate;
}

class MyDefault implements Default<T> {
  const MyDefault();

  T get defaultValue => Random().nextInt(9999);
}

Kinda verbose though.

rrousselGit avatar Feb 24 '20 14:02 rrousselGit

It matched Json converter template (in a sense that is good). Depending if this is an instance of Default() or JsonConverter(), will handle generation.

If this gets done, does this mean, we could have default non literals also ?

bounty1342 avatar Apr 18 '20 23:04 bounty1342

Hi @rrousselGit. I write as

Ah, I see what you're speaking of. But the syntax using @Default would be horrible as we can't use () => something inside decorators.

What about such syntax?

@freezed
abstract class SuperLate with _$SuperLate {
  factory SuperLate([int value]) = _SuperLate;

  @late
  @override
  int get value => super.value ?? Random().nextInt(9999);
}

but get error Screen Shot 2020-06-25 at 8 32 59 PM

tbm98 avatar Jun 25 '20 13:06 tbm98

This is not implemented. Just a thought

rrousselGit avatar Jun 25 '20 15:06 rrousselGit

@rrousselGit Could this thought came to reality? I got into the same problem when I want to set empty list to a some field, but I can't get it generified in the @Default , and generator come broken when I tried to implement it writing block body, returning full-signatured constructor call with defaults written in this call. Like this:

factory RepositoryListDataModel({
    @Default(false) bool fetchCompleted,
    String query,
    List<T> data
  }) = {
    return _RepositoryListDataModel<T>(query: query, data: data ?? <T>[]);
  }

Aqluse avatar Sep 26 '20 18:09 Aqluse

Another way to implement this would be to allow a function as Default's argument :

Other _defaultOther() => Other();

@freezed
abstract class Example with _$Example {
  factory Example(@Default(_defaultOther) Other value) = _Example;
}

aloisdeniel avatar Oct 30 '20 16:10 aloisdeniel

The problem is, people will most likely want to initialize some parameters based on other parameters

rrousselGit avatar Oct 30 '20 17:10 rrousselGit

Yeah, probably the most common scenario.

But it may solve issues with json serializable :

For example :

@freezed
abstract class Other with _$Other {
  const factory Other() = _Other;

  factory Example.fromJson(Map<String, dynamic> json) => _$ExampleFromJson(json);
}

@freezed
abstract class Example with _$Example {
  const factory Example(@Default(const Other()) Other value) = _Example;

  factory Example.fromJson(Map<String, dynamic> json) => _$ExampleFromJson(json);
}

That produces this error :

Error with @JsonKey on value. defaultValue is _$_Other, it must be a literal.

I'm clueless on how to achieve this right now.

aloisdeniel avatar Oct 30 '20 18:10 aloisdeniel

Another common use case that doesn't need other properties :

Example({
    DateTime timestamp,
  }) : timestamp = timestamp ?? DateTime.now().add(Duration(days: -30));

The nearest approach I found (though the value isn't initialized at instantiation, but at first read):

  factory Example({
    @JsonKey(name: 'timestamp') DateTime optionalTimestamp,
  }) = _Example;

  @late
  DateTime get timestamp =>
      optionalTimestamp ??
      DateTime.now().add(Duration(days: -30)); 

aloisdeniel avatar Nov 09 '20 09:11 aloisdeniel

@rrousselGit

The problem is, people will most likely want to initialize some parameters based on other parameters

I understand your concern, but I feel like it's a way to eagerly address future problems, since there hasn't been such feature request until now, as far as I could evaluate.

I must add that I cannot really find a good use of such functionality. Default values that depend on different parameters are something other than just default values. I cannot find a good reason to make it easy embracing bad practices.

On the other hand, I cannot see how this would play with json_serializable's default value.

hcbpassos avatar Feb 27 '21 04:02 hcbpassos

@aloisdeniel If we got to that point, why not creating a dummy private constructor and a second one with body, which allows us to define a default value?

const factory Example._(DateTime timestamp) = _Example;

factory Example({
  DateTime timestamp,
}) => Example._(timestamp ?? DateTime.now().add(Duration(days: -30)));

Notice that the parameter is not optional in the dummy constructor. This is a very important detail. By making it optional, freezed would not generate assert(timestamp != null).

hcbpassos avatar Feb 27 '21 04:02 hcbpassos

Notice that the parameter is not optional in the dummy constructor. This is a very important detail. By making it optional, freezed would not generate assert(timestamp != null).

Not if you mark the parameter as required.

rrousselGit avatar Feb 27 '21 22:02 rrousselGit

@hcbpassos Yes absolutely, it is another valid solution, but I wanted to keep the const behaviour. :)

aloisdeniel avatar Apr 12 '21 10:04 aloisdeniel

Any progress on this? I would like to see this functionality because in a truly null safe app I need default values when I read from the database. It would be awesome to have these default constructors.

valle-xyz avatar Apr 16 '21 17:04 valle-xyz

@aloisdeniel

Yes absolutely, it is another valid solution, but I wanted to keep the const behaviour. :)

I'm not sure I understand. Do you want a const constructor with non-const default values?

hcbpassos avatar Apr 16 '21 17:04 hcbpassos

I have kind of the same issue, don't focus on the logic i was just trying to test forms with riverpod state notifier and freeezed,

but I'm not able to continue because DateTime doesn't have a const constructor so I'm thinking is there a possibility to do it or i just need to make a workaround for it?

Im also using Formz package for fields

@freezed
class LoginState with _$LoginState {
  factory LoginState({
    @Default(Email.pure()) Email email,
    @Default(Password.pure()) Password password,
    @Default(DateTime()) Date date,
    @Default(FormzStatus.pure) FormzStatus status,
  }) = _LoginState;
}

elianortega avatar Apr 18 '21 17:04 elianortega

Is there another proposed solution to use another freezed class as a default value? Like a default value for a subclass?

valle-xyz avatar Apr 19 '21 13:04 valle-xyz

Yeah, probably the most common scenario.

But it may solve issues with json serializable :

For example :

@freezed
abstract class Other with _$Other {
  const factory Other() = _Other;

  factory Example.fromJson(Map<String, dynamic> json) => _$ExampleFromJson(json);
}

@freezed
abstract class Example with _$Example {
  const factory Example(@Default(const Other()) Other value) = _Example;

  factory Example.fromJson(Map<String, dynamic> json) => _$ExampleFromJson(json);
}

That produces this error :

Error with @JsonKey on value. defaultValue is _$_Other, it must be a literal.

I'm clueless on how to achieve this right now.

I have the same problem when i want to put an model default value:

@freezed
class User with _$User {
  const factory User(
      {required String id,
      required String email,
      required String username,
      @Default(Profile.empty) Profile profile,}) = _User;

  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
  static const empty = User(
      id: '',
      email: '[email protected]',
      username: 'DummyName',);
}

Where Profile is the model we need:

[SEVERE] json_serializable:json_serializable on lib/features/user/domain/user.entity.dart:

Error with `@JsonKey` on `profile`. `defaultValue` is `_$_Profile`, it must be a literal.
package:thesis_cancer/features/user/domain/user.entity.freezed.dart:297:17
    ╷
297 │   final Profile profile;
    │                 ^^^^^^^
    ╵
[INFO] Running build completed, took 4.6s

[INFO] Caching finalized dependency graph...
[INFO] Caching finalized dependency graph completed, took 80ms

[SEVERE] Failed after 4.7s
pub finished with exit code 1

SalahAdDin avatar May 26 '21 19:05 SalahAdDin

What about cases with default list value? Like this:

class Account with _$Account {
  factory Account({
    String name,
    @Default([]) List<String> photos, //<--- this is not working
  }) = _Account;
}

I just want to init empty list and don't want to use null. How to do that?

rodion-m avatar Jul 05 '21 19:07 rodion-m

@rodion-m you can't use implicit-cast for your default value, instead give your empty list a proper type:

 @Default(<String>[]) List<String> photos,

SunlightBro avatar Jul 05 '21 19:07 SunlightBro

@rodion-m you can't use implicit-cast for your default value, instead give your empty list a proper type:

 @Default(<String>[]) List<String> photos,

Thank you, it works!

rodion-m avatar Jul 05 '21 20:07 rodion-m

@Default(Set<String>.unmodifiable(<String>[])) Set<String> photos;

any idea how i can map the above code on a Set? with this code, i do get The constructor being called isn't a const constructor...

tomquas avatar Aug 05 '21 09:08 tomquas

@Default(Set<String>.unmodifiable(<String>[])) Set<String> photos;

any idea how i can map the above code on a Set? with this code, i do get The constructor being called isn't a const constructor...

Instead do:

@Default(<String>{})

rrousselGit avatar Aug 05 '21 10:08 rrousselGit

@rrousselGit Hey. Do you have plans in the near future to solve this? It's been a few months now

exaby73 avatar Jan 30 '22 13:01 exaby73

No. Feel free to make a pull request

rrousselGit avatar Jan 30 '22 14:01 rrousselGit

@rrousselGit As soon as I am able to wrap my head around the Dart build system, I'd love to. BTW your efforts, and that of the community, is well appreciated. I'd love to donate to this project to keep it alive even if all I can afford is a few bucks :D

exaby73 avatar Jan 31 '22 08:01 exaby73

Thank you for the 0.7.0 update, the default values made my code a little bit simpler :)

One thing that I still have to work around are non-constant default values. My type has a String property called id that - if left empty - should have a random UUID value by default. I now work around this by adding a second factory method that redirects to the generated one without the id parameter.

It would be nice if the @Default annotation could also take in a lambda to generate the default value.

Hey @sdebruyn, could you please provide a code example for your workaround? I have a similar use case where I need a random UUID as default value but didn't find a workaround with freezed yet.

Giuspepe avatar Jul 30 '22 08:07 Giuspepe