Not sure if 'conditional statements' is the right terminology but frequent issues while using Dart as UI are as follows:
Widget build(BuildContext context) {
return CupertinoPageScaffold(
child: ListView(
children: [
PageHeader(),
isSignedIn ? SignInButton : justDon'tDoAnything
RestOfThePage()
],
),
);
}
In the case above, we don't actually want to return null since null has a different semantic meaning as one of the list elements.
There are of course imperfect work-arounds such as appending ..where((Widget item) => item != null) or returning : SomeFlutterWidgetThatDoesn'tDoAnythingInTheListView but they're still impedances to Dart as UI.
Another example is
WidgetA( // <= constructor
paramA: overrideValueA,
paramB: ifIShouldOverride ? overrideValueB : justDon'tDoAnythingAndLeaveConstructorDefaultWhichMayBeDifferentFromNull
)
Again, the present workaround solution is to wrap the whole constructor and invoke it differently twice, but it would be great if this can be expressed succinctly in the spirit of #47
cc @munificent
I very strongly recommend to always let explicitly passing null mean the same thing as omitting an argument. Dart doesn't enforce that. I hope it eventually will (e.g., if we get non-nullable types, it might mesh well with that).
Until then, it would be possible to reuse the if syntax of control-flow-elements for arguments as well.
passing null mean the same thing as omitting an argument.
How would that work in the presence of default values?
I believe we have cases in Flutter where we have arguments where "null" is a reasonable value to pass, and where "null" is not the default. It's very hard to grep for these cases though and I don't remember any off-hand.
If I properly understood Ian's comment, we have 2 patterns:
border argument defaults to a particular type of border, passing null explicitly means let it not have a border. InnerWidgetThatWeCompose(border: border ?? kSomeDefaultBorder).Here, option 1 is great because an immutable UI representation actually represents the UI it's mean to represent without additional implicit runtime interpretations. But option 1 introduces the issue described in the original post.
Despite all the problems that undefined causes in JavaScript, this is one case where it provides a nice solution.
@lrhn:
explicitly passing null mean the same thing as omitting an argument. Dart doesn't enforce that. I hope it eventually will
So does that mean that, say, f will print 5 below twice?:
void f([int x = 5]) {
print(x);
}
void main() {
f(); // 5
f(null); // 5
}
It would be really weird if null means one thing elsewhere in the language, but means omit here.
I think the use-cases listed in this discussion, where null or undefined are proposed as solutions, seem to actually want a more direct way to express more states a variable can be in other than the range of values in the normal state. I don't know what the most idiomatic approach would be for Dart, but that's what Rust and Swift use enums for.
@yjbanov in order for the solution to work properly with default parameter values, it needs to be builtin. We can't do that with a user-defined enum.
Also, FWIW, I just remembered a clever approach used by some JS libraries. It uses the list spread operator like this:
Widget build(BuildContext context) {
return CupertinoPageScaffold(
child: ListView(
children: [
PageHeader(),
...(isSignedIn ? [SignInButton] : [])
RestOfThePage()
],
),
);
}
It works, and I think it's better than extracting the children list into a variable, but is still not ideal.
Most helpful comment
@yjbanov in order for the solution to work properly with default parameter values, it needs to be builtin. We can't do that with a user-defined enum.
Also, FWIW, I just remembered a clever approach used by some JS libraries. It uses the list spread operator like this:
It works, and I think it's better than extracting the
childrenlist into a variable, but is still not ideal.