Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Non-constant default values #64

Closed
sdebruyn opened this issue Feb 24, 2020 · 38 comments · Fixed by #1156
Closed

Non-constant default values #64

sdebruyn opened this issue Feb 24, 2020 · 38 comments · Fixed by #1156
Assignees
Labels
enhancement New feature or request

Comments

@sdebruyn
Copy link
Contributor

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.

@rrousselGit
Copy link
Owner

Dart only supports constant default values.

Are you looking for @late instead?

@rrousselGit
Copy link
Owner

rrousselGit commented Feb 24, 2020

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 rrousselGit added the enhancement New feature or request label Feb 24, 2020
@sdebruyn
Copy link
Contributor Author

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

@rrousselGit
Copy link
Owner

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.

@bounty1342
Copy link

bounty1342 commented Apr 18, 2020

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 ?

@tbm98
Copy link
Contributor

tbm98 commented Jun 25, 2020

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

@rrousselGit
Copy link
Owner

This is not implemented. Just a thought

@Aqluse
Copy link

Aqluse commented Sep 26, 2020

@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>[]);
  }

@aloisdeniel
Copy link

aloisdeniel commented Oct 30, 2020

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

@rrousselGit
Copy link
Owner

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

@aloisdeniel
Copy link

aloisdeniel commented Oct 30, 2020

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
Copy link

aloisdeniel commented Nov 9, 2020

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

@hcbpassos
Copy link

hcbpassos commented Feb 27, 2021

@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
Copy link

hcbpassos commented Feb 27, 2021

@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).

@rrousselGit
Copy link
Owner

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.

@aloisdeniel
Copy link

aloisdeniel commented Apr 12, 2021

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

@valle-xyz
Copy link

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.

@hcbpassos
Copy link

@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?

@elianortega
Copy link

elianortega commented Apr 18, 2021

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

@valle-xyz
Copy link

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

@SalahAdDin
Copy link

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: 'name@dummy.com',
      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

@rodion-m
Copy link

rodion-m commented Jul 5, 2021

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?

@SunlightBro
Copy link

SunlightBro commented Jul 5, 2021

@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,

@rodion-m
Copy link

rodion-m commented Jul 5, 2021

@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!

@tomquas
Copy link

tomquas commented Aug 5, 2021

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

@rrousselGit
Copy link
Owner

@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>{})

@exaby73
Copy link

exaby73 commented Jan 30, 2022

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

@rrousselGit
Copy link
Owner

No. Feel free to make a pull request

@exaby73
Copy link

exaby73 commented Jan 31, 2022

@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

@Giuspepe
Copy link

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.

@SalahAdDin
Copy link

SalahAdDin commented Jul 30, 2022

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 is 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 the default value but didn't find a workaround with freezed yet.

I'm interested in it too.

@tobytraylor
Copy link

The way that this was solved in build value by David Morgan was to identify a static function with a attribute that would be called to perform the defaulting.

abstract class MyValue { @BuiltValueHook(initializeBuilder: true) static void _setDefaults(MyValueBuilder b) => b ..name = 'defaultName' ..count = 0;

built value issue / discussion

@Shiba-Kar
Copy link

how to assign a default datetime

@freezed
class Client with _$Client {
  factory Client({
    required User user,
    @Default(DateTime.now()) DateTime addedDate, //The constructor being called isn't a const constructor.
Try removing 'const' from the constructor invocation.
  }) = _Client;
@late
@override
  factory Client.fromJson(Map<String, dynamic> json) => _$ClientFromJson(json);
}

@feduke-nukem
Copy link

Does that suppose to work?

got this message is non-nullable but is neither required nor marked with @Default

@paramsinghvc
Copy link

Uhh, I miss Typescript! Too much work for union types!

@RyoheiTomiyama
Copy link

I resolved like this.

typedef UUID = String;

@freezed
class SendData with _$SendData {
  factory SendData.def({
    required String receiver_id,
    required List<FileData> files,
    required UUID uuid,
  }) = _SendData;

  factory SendData({
    required String receiver_id,
    required List<FileData> files,
  }) {
    return _SendData(
      receiver_id: receiver_id,
      files: files,
      uuid: _generateUuid(),
    );
  }

  static UUID _generateUuid() {
    final uuid = Uuid();
    return uuid.v4();
  }
}
final sendData = SendData(receiver_id: receiver_id, files: files);

@normidar
Copy link

Any progress on this?

#64 (comment)
this is diffcalt difficult when I hava lot of field in my class.

@fidelQabit
Copy link

I have problem with this and DateTime, i have to make that fields nullable by default instead of DateTime.now() lol:

DateTime? metricsDateFrom,
DateTime? metricsDateTo,

@rrousselGit rrousselGit linked a pull request Feb 21, 2025 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.