Why can I do a ternary but not an if statement - flutter

Why does the first work, but the second does not? The issue with the first is that null throws assertion exception since the widget cannot be null. what is the best practice in this scenario?
Row(
children: listOfWidgets != null ? listOfWidgets : null
)
Row(
children: if (listOfWidgets != null) listOfWidgets;
)
Current workaround:
void List<Widget> _rowWidgets() {
if(listOfWidgets != null) return listOfWidgets;
}
Row(
children: _rowWidgets();
)

Each of the three options listed has its own problem, so I'll address them individually.
Issue:
Row(
children: listOfWidgets != null ? listOfWidgets : null,
)
Solution:
Row(
children: listOfWidgets != null ? listOfWidgets : [],
)
Explanation
This is fine except for if listofWidgets is null. That would cause the ternary operator to return null, and Row doesn't allow its children property to be null. You need to give it a non-null default value like [] instead.
Issue:
Row(
children: if (listOfWidgets != null) listOfWidgets;
)
Solution:
Row(
children: [ if (listOfWidgets != null) ...listOfWidgets ],
)
Explanation:
This syntax is wrong in Dart because you can't have a nested if statement like this. if statements have to either exist on their own live of code or (since Dart 2.3) nested within a list/map literal. If you do the nested-in-list-literal route, you will also need the spread operator ... on the listOfWidgets to expand it into the other list. Otherwise, you will end up trying to put a list within a list, which Row will not allow you to do.
Issue:
void List<Widget> _rowWidgets() {
if (listOfWidgets != null) return listOfWidgets;
}
Row(
children: _rowWidgets();
)
Solution:
List<Widget> _rowWidgets() {
if (listOfWidgets != null) return listOfWidgets;
return [];
}
Row(
children: _rowWidgets(),
)
Ignoring the more glaring syntax errors (semicolon where a comma is expected, dual return types), this one is similar to the ternary issue, albeit a bit more subtly. In Dart, every method that doesn't have void as a return type has to return something. If you don't explicitly return something, the method will return null by default. This is what happens in your _rowWidgets method - you don't state what would be returned if listOfWidgets is null, so Dart will return null for you in that case. And just like the ternary issue, Row will not allow children to be null.
Overall Suggestion:
Row(
children: listOfWidgets ?? [],
)
Explanation:
Ultimately, all you are concerned with is making sure the Row receives a non-null value if listOfWidgets happens to be null. Dart has the perfect operator for this: the null-coalescing operator ??. What it does is it returns the value on the left unless that value is null, in which case it returns the value on the right.
To illustrate it more clearly, the following lines of code perform identically:
// If option
if (listOfWidgets != null) return listOfWidgets;
else return [];
// Ternary option
return listOfWidgets != null ? listOfWidgets : [];
// Null-coalescing option
return listOfWidgets ?? [];

The first case, the ternary has the form statement? a : b, and always returns or a or b. Thats the trick, the ternary always has a return statement even if we cant see it. The if doesnt has that statement by default, so you need to explicitly declare the return if you want one.

Related

Flutter || how to make three conditions in single container

I am working on a UI where I have 3 checks in the same container.
I am able to achieve only one condition but not able to the second one or third one.
till now I have created a container where I have made a column and included all things and used a bool variable which changes when I click the text and it reverts back when I click the close button.
But now the problem is How can I use 3 conditions in the same column?
my code till now
bool makePayment = false;
makePayment ? Column( crossAxisAlignment:
children: [
const Text('Quick actions'),
const SizedBox(),
Row(),
Row()
]) : Column()
you could use a lot of condition in the same way as you did just the question will be more complicated , example :
A.isNumber ? do1 : A.isAlpha? do2 : A.isSymbol ? do3 : do4
You can use else-if into your column children.
The Syntax is
Column(
children: <Widget>[
if(your condition) ...[
//YOUR CODE
] else if(your condition) ...[
//YOUR CODE
] else ...[
//YOUR CODE
]
]
)

Dart: How to properly handle null-safety?

I have upgraded my project to null-safety and it's a bit confusing, because if I wrap into a
if(someObject.field != null) {
doSomething(someObject.field); // error, can't assign String? to String
}
a method call that requires a not-nullable property and my variable I am trying to pass is nullable, then I get a type error that I cannot assign a nullable value to a not-nullable type.
But when I do
String? someObjectField = someObject!.field;
if(someObjectField != null) {
doSometing(someObjectField); // Can assign
}
it works as expected.
doSomething = (String foo) {}
For example:
class Person {
final String name;
Person(this.name);
}
Function test = () {
Map<String, String?> pers = {
'name': 'John',
};
if(pers['name'] != null) {
Person(pers['name']); // Error, even if it will be never executed
Person(pers['name'] as String); // no error
Person(pers['name']!); // no error
}
};
And if I do something like:
if (widget.event != null && widget.event.featuredImage != null)
Then it complains in the second statement that the receiver (widget.event) can be null and I need to use !, but the second statemen should never execute and it should never cause a runtime exception.
So I need to modify it to:
if (widget.event != null && widget.event!.featuredImage != null)
But then when I try to use a nested widget inside Flutter, then even if I use the if as wrapper I still need to add ! everywhere
Stack(
children: [
// Add Container with image only when event and futured image are not null
if (widget.event != null && widget.event!.featuredImage != null) ...[
Container(
height: 250,
decoration: BoxDecoration(
color: Colors.transparent,
image: DecorationImage(
fit: BoxFit.cover,
image: NetworkImage(widget.event!.featuredImage!), // here i Need to use ! to satisfy the compiler
),
),
),
],
],
)
Alternatively I can extract the variable into another one String? image = widget.event!.featuredImage then modify the if statement to if(image != null) and use the widget like NetworkImage(image) which works as expected.
Compared to TypeScript which detects if I checked for null in a condition above, this makes no sense to me.
Longstory short, even if I check for null value to render/not-render a component, I still need to use !.
Is there something obvious that I am missing?
Thank you in advance
Since you compared it to TypeScript, yes, you are missing something.
Typescript is a mess that works in a very limited environment and "works" is grossly overstated. For example you could write a method in typescript that takes a string and then at runtime find out it's not actually a string, surprise, it's a completely different type. The joys of JavaScript. Calling TypeScript "type safe" is correct compared to JS and ridiculous compared to actually compiled languages.
So you are missing the fact that the Dart compiler can guarantee that something is not null once you checked it. To do that, it needs additional constraints. For example, you could have getters that do not return the same value every time you call them. Your call for example could easily return different values between the first and second call, depending on the code of the getter. Or you could use inheritance and polymorphism to build some even more problematic constructs. See here for an interesting example. So you need to have a local variable, that is guaranteed to have the same value, unless explicitely changed.
Your example of if (widget.event != null && widget.event.featuredImage != null) could easily be modified to:
final image = widget?.event?.featuredImage;
Stack(
children: [
// Add Container with image only when event and futured image are not null
if (image != null) ...[
Container(
height: 250,
decoration: BoxDecoration(
color: Colors.transparent,
image: DecorationImage(
fit: BoxFit.cover,
image: NetworkImage(image),
),
),
),
],
],
)
Yes, you have to implement some tiny bit of logic, you cannot just slap ? and ! on your code and it runs as before. But once you understood what that little change in logic is, it is pretty easy.

How do I map a list of potentially null widgets?

I'm new to Flutter and haven't found much success in my brief online search for an answer to this, which is the reason for this post.
Here's the code in question:
// `myList` can potentially be null.
children: widget.myList?.map((item) {
return Text("Hi.");
}).toList(),
I'm trying to loop over a List<String>? of errors in my stateful widget, inside of the children: property of a Column.
Dart is telling me that I cannot map over a List<String>?, and suggests that I use myList?.map instead.
However, when I do that, the issue now becomes that children: expects a List<Widget> and can therefore not accept a List<Widget>? ...
I seem to be stuck in circuitous errors, but somehow I feel the solution is simple. I'm still learning about null-safety.
So tl;dr:
How do I reconcile between a potentially null list of widgets, and a property that expects a list of widgets that isn't null?
Solution
children: myList?.map((e) => Text(e)).toList() ?? [],
If your List is List<Widget>?, you can add simply a null check like so:
children: _widgets?.map((item) => item).toList() ?? [Text('List was null')],
If your List is List<Widget?>? you can change it to:
children: _widgets?.map((item) => item ?? Text('widget was null')).toList() ?? [Text('List was null')],
If you want to map a List<String?> inside a Column
Column(
children: _strings.map((e) => Text(e ?? 'String was null')).toList(),
)
OR
Column(
children: _strings.map((e) => e == null ? Text('was null') : Text(e)).toList(),
)
If your List is List<String>?
Column(
children: _strings?.map((e) =>Text(e)).toList() ?? [Text('The list was null')],
)

Ternary condition with equal to not working

Trying to check userStatus and currentUser while comparing it with a value but it is not working and on the other hand if I put not equal to then it works.
I want to compare the value like mentioned below:-
body: currentUser == null ?
Center(child: Splash())
:currentUser.userStatus == 'isBlocked'
? BlockUser():
Center(
child: Container(),
And on the other hand if I put != sign then it works:-
body: currentUser != null ?
Center(child: Splash())
:currentUser.userStatus != 'isBlocked'
? BlockUser():
Center(
child: Container(),
But here I want to compare the values which are stored in database and then show the desired result. Pls help.
As I hinted about in the comment to your question. I would suggest to ditch the ternary operator and write a method instead.
Ternary operators are best suited for small and very simple logic.
The code will be more readable and simple mistakes wouldn't happen as easily. Instead do something like this:
body: myAwesomeUserChecker(),
...
With the method:
Widget myAwesomeUserChecker() {
if (currentUser == null) {
return Center(child: Splash());
}
if (currentUser.userStatus == 'isBlocked') {
return BlockUser();
}
return Center(child: Container());
}
Ps. This matches the first option that you wrote, but which you said didn't do what you wanted... The second option you wrote somehow did what you expected, but logically that alternative seemed flawed. So, if what I wrote here doesn't give you your expected outcome, then I would assume the fault might be somewhere else, or you'd have to provide more information so that the case can be better understood.
Please define your expected behavior more precisely. If we better understand your desired outcome, we can help better.
I tried to unify the indentation and punctuation of your code and added some comments. Does this accurately represent the behavior you intended and it is still not behaving as the comments below indicate?
body: currentUser == null ?
// If currentUser == null, Splash() will be centered
// userStatus will not even be checked
Center(child: Splash()) :
// Else (currentUser != null), userStatus will be checked
currentUser.userStatus == 'isBlocked' ?
// If currentUser != null and userStatus == 'isBlocked' then BlockUser()
BlockUser() :
// Else (currentUser != and userStatus != 'isBlocked') center Container()
Center(child: Container()
Well, before making this logic, try to print the value of both currentUser and the value currentUser.userStatus to know what the currentUser has and what the currentUser.userStatus returns. After that, you may make a logic using simple if-else statements. Once you get success in that, then convert it to ternary condition.

How to display a column when a variable is true in Flutter?

I keep getting this error that res is not defined, so I am trying to figure out how to run the column code after res is defined like in js you can do {res && (code)}.
Column(
children: res['rooms'].map((r) => Card(
'name',
'${r['messages'][r['messages'].length - 1]['content']}',
'${r['_id']}'
)),
),
You can check if your res variable is equal to null.
This is the non nullable approach to your problem, but you should check to migrate your project to null safety to avoid all those null checks.
res == null
? Column(
children: res['rooms'].map((r) => Card(
'name',
'${r['messages'][r['messages'].length - 1]['content']}',
'${r['_id']}')),
)
: Container(),
It can be hard to read ternary expressions sometimes if you have a large widget within a expression. It also requires you to provide an "else" widget.
I approach this with the spread operator, with a single if statement
Column(
children: <Widget>[
if (res != null)
...res.map((r) => Card(
'name',
'${r['messages'][r['messages'].length - 1]['content']}',
'${r['_id']}')),
],
)
Simple use ternary login like this -
Container(
child: res != null
? Column(
children: []
)
: SizedBox() //Use SizedBox instead of Container for better efficiency
)
And update the UI again once "res" is populated.