-1
class Foo {
  final int? i;
  Foo({this.i});

  Foo copyWith({int? x}) {
    return Foo(i: x ?? i);
  }
}

void main() {
  final foo = Foo(i: 0);

  foo.copyWith(x: null);
  print(foo.i); // prints `0` but should print `null`.
}

How can I actually pass null value to the method? In earlier Dart version copyWith() and copyWith(x: null) were two different things.


Note: I'm not looking for workarounds like making a new variable, like isNull and then deciding whether to pass null or not based on its value.

iDecode
  • 22,623
  • 19
  • 99
  • 186
  • `return Foo(i: x ?? i);` This line means: if x is not null: take the value of x. and if it is null, take the value of i.So, i of the returned object is never updated if you pass null. also, maybe you need to say `foo=foo.copyWith(x:null);` because you return a new object but never use it. – Mohamed Akram Apr 02 '22 at 23:31
  • @MohamedAkram I knew what `x ?? i` means but the question is how do I pass `null` in such situations? Second, I used the conventional `ClassName copyWith` approach, but for the sake of this sample question, I didn't make use of returned `Foo` object. – iDecode Apr 02 '22 at 23:33
  • Why don't you say `return Foo(i:x)`? – Mohamed Akram Apr 02 '22 at 23:34
  • `Foo(i: x)` defeats the purpose of `copyWith` method. What if I didn't pass it any value and pass other variables some value? – iDecode Apr 02 '22 at 23:37
  • `return Foo(i:x)` will return a new object with whatever value you pass, so it will update `i` to be null. – Mohamed Akram Apr 02 '22 at 23:37
  • @MohamedAkram I am not sure how familiar you are with `Class copyWith` approach. You're simply defeating its purpose. I want to make both `copyWith()` and `copyWith(x: null)` to work differently. – iDecode Apr 02 '22 at 23:38
  • 1
    You *did* successfully pass `null` to the `copyWith` method. What `copyWith` chooses to do with the `null` argument is up to the `copyWith` implementation. You probably want to see [Dart: Custom "copyWith" method with nullable properties](https://stackoverflow.com/q/68009392/). – jamesdlin Apr 02 '22 at 23:55
  • "In earlier Dart version `copyWith()` and `copyWith(x: null)` were two different things." That is still true, nothing has changed in that regard (yet!). What was the code that you had that worked in earlier Dart versions? – lrn Apr 04 '22 at 06:55
  • @lrn If they are still two different things then why did my code not work? This wasn't the code I used in earlier Dart version but I remember something like of sort worked. – iDecode Apr 05 '22 at 13:31
  • My bad. They are still *not* different things, they're still the same, and always have been. (At least since long before Dart 1.0.) – lrn Apr 07 '22 at 08:05

1 Answers1

1

With simple copyWithwhit Dart null-safety you can't override value by null because if id is null return this.id. You need to override the value by null but not return with another value. It can solve in a few ways but I will give you the best example.

void main() {
  final user = User(name: 'Dave', id: 110);

  User copy = user.copyWith(id: null);
  print(copy.toString()); // prints User(name: Dave, id: null).
}

class User {
  User({required this.name, this.id});

  final String name;
  final int? id;

  UserCopyWith get copyWith => _UserCopyWith(this);
  
  @override
  String toString() => 'User(name: $name, id: $id)';
}

abstract class UserCopyWith {
  User call({
    String name,
    int? id,
  });
}

class _UserCopyWith implements UserCopyWith {
  _UserCopyWith(this.value);

  final User value;
  static const _undefined = Object();

  @override
  User call({
    Object name = _undefined,
    Object? id = _undefined,
  }) {
    return User(
      name: name == _undefined ? value.name : name as String,
      id: id == _undefined ? value.id : id as int?,
    );
  }
}
Arnas
  • 802
  • 1
  • 6
  • 14
  • Thanks for your answer but this approach has a lot of boilerplate code. What if I have 20 such fields, the code will become way too long. But definitely a +1 for your efforts. – iDecode Apr 04 '22 at 05:31
  • Every boilerplate code have a reason. Like this example you can override nullable value with null. I working with extension for vscode which will generate it with one click good alternative for them who don't like generators like Freezed :) – Arnas Apr 04 '22 at 05:49
  • Check out [this way](https://stackoverflow.com/a/71732563/12483095) of doing it. Much shorter – iDecode Apr 04 '22 at 05:51
  • Yes I know this. I thought you want use pure copy with method. This shorter way depend on `Optional` type. My boilerplate code provide pure `copyWith(id: null);` But of course you can use `Optional` type. – Arnas Apr 04 '22 at 06:28
  • There is no need to make `copyWith` a function object. Also `const Object()` is not unique, anyone can create that. Consider an alternative like: https://gist.github.com/lrhn/c964ec4030ca7f9aefcd14162321f1ee It even works if the type of the field is `Object?` to begin with, as long as as the sentinel isn't leaked from the library. (Against `dart:mirrors`, no sentinel is safe). – lrn Apr 04 '22 at 08:08
  • I personally prefer clean code and easier readable. CopyWith call function like a guard does not allow overriding wrong type that, `const Object()` it safe to use here. And my code compared to yours is a few lines longer but not required abstract class. But anyway, if you feel comfortable with your code and without trouble that is ok :) I tried many different ways this one is the best one for me. – Arnas Apr 04 '22 at 08:42
  • The `const Object` is safe to use here, as long as no-one casts the function to a sub-type accepting `Object?` (which the they only would if they *wanted* to circumvent the design, and then it's fine that things get odd). However, if your original class had a field with type `Object?`, you do need a sentinel in `copyWith` that users cannot create themselves by accident, and `const Object()` is not. On which design is cleaner or more readable, we'll have to agree to disagree :) – lrn Apr 07 '22 at 08:02