Sdk: Allow parameter defaults for null values

Created on 19 Jul 2018  路  13Comments  路  Source: dart-lang/sdk

Please save us from having to do this to capture both undefined and null values. 馃槂 I know undefined isn't a thing in Dart but it is treating "unspecified" as being different than "null".

void main() {
  Thing nullThing = Thing(text: null);
  print(nullThing.text); // "default"

  Thing undefinedThing = Thing();
  print(undefinedThing.text); // "default"
}


class Thing{
  String text;

  Thing({
    text: "default"
  }) : this.text = text ?? "default";
}

Ideally:

class Thing{
  String text;

  Thing({
    this.text: "default"
  });
}

Why? Because when being passed Maps from a NoSQL database that may not have complete data, and then using it to create a new class instance, I have not found a good way to pass undefined to the class constructor arguments, when myMap['notHere'] == null

area-language core-m type-enhancement

Most helpful comment

I agree with everyone here. Here's a little proposal; keep the current system but add an operator like this : ??= which would work on undefined and on null values.

class Person {
  String firstName;
  String lastName;

  Person({this.firstName ??= 'John', this.lastname ??= 'Watson'}); 
}

Would be the same as :

class Person {
  String firstName;
  String lastName;

  Person({
    String firstName,
    String lastName,
  })  : this.firstName = firstName ?? 'John',
        this.lastName = lastName ?? 'Watson';
}

All 13 comments

I agree that it would be ideal if explicitly passing null would be treated the same as not passing the parameter at all (which is not undefined, but I can see the resemblance).
That is, foo({x = "default"}) { ...} should implicitly be the same as foo({x}) { x ??= "default"; ...}.

I hope to be able to make that change some time in the future.
If we introduce non-nullable types, it likely becomes much more important. That would also allow for required named arguments (a named argument with a non-nullable type and no default value).

Currently, I personally prefer to use ?? to initialize optional parameters, and only use default values for parameters which I would consider required (where I don't mind passing null being an error, which is typically boolean named arguments only).\

I would also like to extend this request to @required in meta

class Project{
  final int id;
  final String title;
  final String iconUrl;
  final List<String> tags;
  final List<SocialLink> links;
  final int taps;

  Project({
    @required this.id,
    @required this.title,
    @required this.iconUrl,
    @required this.tags,
    @required this.links,
    @required this.taps,
  }) : assert(id != null),
       assert(title != null),
       assert(iconUrl != null),
       assert(tags != null),
       assert(links != null);
}

could be

class Project{
  final int id;
  final String title;
  final String iconUrl;
  final List<String> tags;
  final List<SocialLink> links;
  final int taps;

  Project({
    @required this.id,
    @required this.title,
    @required this.iconUrl,
    @required this.tags,
    @required this.links,
    @required this.taps,
  });
}

I wanted to provide a different example where providing null to parameters results in suppression of the default values.

Example references the FloatingActionButton FAB class constructor from Flutter package:

// from Flutter 
class FloatingActionButton extends StatefulWidget {
  const FloatingActionButton({
      Key key,
      this.child,
      this.tooltip,
      this.foregroundColor,
      this.backgroundColor,
      this.heroTag = const _DefaultHeroTag(),
      this.elevation = 6.0,
      this.highlightElevation = 12.0,
      @required this.onPressed,
      this.mini = false,
      this.shape = const CircleBorder(),
      this.clipBehavior = Clip.none,
      this.materialTapTargetSize,
      this.isExtended = false,
    }) :  assert(elevation != null && elevation >= 0.0),
        assert(highlightElevation != null && highlightElevation >= 0.0),
        assert(mini != null),
        assert(shape != null),
        assert(isExtended != null),
        _sizeConstraints = mini ? _kMiniSizeConstraints : _kSizeConstraints,
        super(key: key);
// remaining class definition...
}

The code above is imported and cannot be modified by end user.

The code below is a user trying to create a class that builds a FAB. Each argument of FAB constructor is constructed by a method. Each method can return a value or null (null would ideally then defers to FABs defaults).

class MyClass /* extends StatelessWidget */ {

  bool getMini(){ /* may return a `bool` or `null` */ }
  bool getExtended(){ /* may return a `bool` or `null */` }
  double getElevation(){ /* may return a `double` or `null`*/ }
  double getHighlightElevation(){ /* may return a `double` or `null`*/ }
  Hero getHero(){ /* may return new `Hero` or `null`*/ }
  Shape getShape(){ /* may return new `Shape` or `null`*/ }
  Clip getClipBehavior(){ /* may return new `Clip` or `null`*/ }

  build(_){
    return FloatingActionButton(
      mini = getMini(),
      isExtended = getExtended(),
      elevation = getElevation(),
      highlightElevation = getHighlightElevation(),
      heroTag = getHero(),
      shape = getShape(),
      clipBehavior = getClipBehavior(),
      // remaining FAB arguments (child, onPressed...)
    );
  }
}

Comaring expected vs actual results

Desired/expected results:

  • If null is passed as argument to constructor, it ignores null for default value

    • example: if getMini() returns null, passes it to the mini argument of FloatingActionButton constructor, the null should be ignored and it's own internal default true should be used.

Actual results:

  • If null is passed as an argument, defaults values are ignored.

    • example: if getMini() returns null, passed to mini argument of FloatingActionButton constructor, mini variable remains null, ignoring default value and an error will be thrown.

Because null can't be passed to constructors, to get desired results one constructor must potentially be created for every iteration.

  • Because the above code doesn't work as expected, it must be rewritten a different way. For simple situations, a ternary can be used for multiple constructors, but quickly gets unwieldy.

    • The above code example, since none of the args are mutually exclusive and all variables are final, must be known at the time of constructor, ...all situations must be written. The 7 arguments would require writing ~127 versions of constructors!
    • Here for only the top 3 (of the 7) variables (mini, isExtended, elevation) are the 8 constructors that would be required to implement code: (code snippet is argument assignment)
      dart build(_){ return FloatingActionButton(

      • none (of first three) with value

        dart // mini = null, // isExtended = null // elevation = null

      • first with value

        dart mini = getMini(), // isExtended = null // elevation = null

      • second with value

        dart // mini = null, isExtended = getExtended(), // elevation = null

      • first two with value

        dart mini = getMini(), isExtended = getExtended(), // elevation = null

      • third with value

        dart // mini = null, // isExtended = null elevation = getElevation(),

      • first and third with value

        dart mini = getMini(), // isExtended = null elevation = getElevation(),

      • second and third with value

        dart // isExtended = null isExtended = getExtended(), elevation = getElevation(),

      • all three with value

        dart mini = getMini(), isExtended = getExtended(), elevation = getElevation(),

        dart // heroTag, shape, clipBehavior... // remaining FAB arguments (child, onPressed...) ); }

  • Although changes to the constructor would allow it to work since it's imported the end user cannot modify:

    • Changing the constructor signature from FloatingActionButton({this.mini = false}); to FloatingActionButton({bool mini}): thi.mini = mini ?? true; can't be made as in another package.
  • The end user should be able to rely on the package's defaults, not having to implement his own or duplicate the package`s default which would be redundant code.

    • Although the end user can see, from Flutter source code, that package default for mini is false and a working implementation would be:

      dart return FloatingActionButton( mini = getMini() ?? false, //... )

      this would require all the defaults needing to be re-implemented by end user, resulting in duplication and possible difficult to detect bugs if the package is changed in future.

note: Although using a Flutter class within example, the issue is not specific to Flutter.

I'm new to dart and this behavior was a big stumbling block for me, the error I was getting didn't suggest that null value was doing anything. It would be a huge benefit for me and many other beginners for this to be resolved!

@MeamonB I think this will become more clear if and when Dart gets non nullable types. I am personally waiting to see if that happens before I worry about this issue more. Just my opinion.

Same issue here, I've spend many hour trying why the default value didn't get set correctly until I realise if we pass null as a parameters it doesn't get his default value... Very annoying !

This would be very helpful when developing packages that interface with other packages and many other scenarios too but I'm just frustrated because I'm trying to make a package and I would really like this to be added.

I agree with everyone here. Here's a little proposal; keep the current system but add an operator like this : ??= which would work on undefined and on null values.

class Person {
  String firstName;
  String lastName;

  Person({this.firstName ??= 'John', this.lastname ??= 'Watson'}); 
}

Would be the same as :

class Person {
  String firstName;
  String lastName;

  Person({
    String firstName,
    String lastName,
  })  : this.firstName = firstName ?? 'John',
        this.lastName = lastName ?? 'Watson';
}

Here's another proposal, just like dart optional values in array:

SnackBar(
      content: Text(message),
      duration: if (retryAction != null) Duration(days: 365),
)

This would really boost productivity instead of having to write the initialization twice

SnackBar(
      content: Text(message),
      duration: if (retryAction != null) Duration(days: 365),
)

Or like in lists or maps :

SnackBar(
      content: Text(message),
      if (retryAction != null) 
           duration: Duration(days: 365),
)

Using if for optional named parameters is definitely possible. My main worry about it is that it doesn't work for optional positional parameters, at least not the way they are currently defined.
That asymmetry would encourage using named parameters over positional parameters even in situations where positional parameters would otherwise be the better design choice.

If we also allow you to omit no-trailing optional positional parameters, so you an write foo(1,,3) to call int foo(int x, [int y = 42, int z]) and get the default value for y, then we can also allow writing foo(1, if (test) 2, 3) to conditionally omit the parameter.

I think this may be fixed after Dart introduced non-nullable types?

This is sooooo hacky and makes me feel like I'm missing something critical, but it works.

ThemedSwitch({this.key,
    @required value,
    @required this.onChanged,
    this.activeColor,
    this.activeTrackColor,
    this.inactiveThumbColor,
    this.inactiveTrackColor,
    this.activeThumbImage,
    this.onActiveThumbImageError,
    this.inactiveThumbImage,
    this.onInactiveThumbImageError,
    this.materialTapTargetSize,
    this.dragStartBehavior,
    this.mouseCursor,
    this.focusColor,
    this.hoverColor,
    this.focusNode,
    this.autofocus}) {

    //Construct a default widget in order to fall back on default values when the optional params aren't passed
    var defaultSwitch = Switch(value: value, onChanged: onChanged);

    _switch = Switch(
        value: value,
        onChanged: onChanged,
        activeColor: activeColor != null ? activeColor : defaultSwitch.activeColor,
    );
  }

Basically, to override the super's functionality without passing on the nulled optional arguments (ie the optional args that were not passed into the constructor), create one widget with the required fields and then use a conditional argument that falls back on the default values from that widget (see activeColor above)

Dart...please make it easier to extend the constructor!

Was this page helpful?
0 / 5 - 0 ratings