I'm learning Dart after coming from Python and I wanted to know what is the closest Dart has to using a non-Boolean variable as a Boolean in conditional statements. Like using a string where an empty string is false and a non-empty string is true.
For example, in Python:
name = 'Yes'
print('True' if name else 'False') // 'True'
name2 = ''
print('True' if name else 'False') // 'False'
Does Dart have something similar without having to convert the variable to a Boolean statement?
Dart has no affordance for using non-boolean values in tests. None, whatsoever. Can't be done.
The only expressions allowed in a test positions are those with static type:
bool
dynamic (which is implicitly downcast to bool as if followed by as bool, which throws at runtime if it's not actually a bool.)
Never, which always throws before producing a value.
The bool type, in non-null-safe code can evaluate to null. The test also throws at runtime if that happens.
So, for any test, if the test expression does not evaluate to a bool, it throws. You can only branch on an actual bool true or false.
If you have a value where you want to treat both null and empty as false, I'd do: if (value?.isEmpty ?? false) ...
I don't think such code will even compile. Type checking in dart tends to be very strict, and whenever the compile tries to evaluate a condition which is not of type bool it raises an error.
For example, the following won't compile:
final String value = "truthyValue" ? "truth" : "dare";
Instead, dart offers the isNonEmpty and isEmpty methods on many built in types such as strings, different containers so on, so you could do something like someString.isNotEmpty ? "good" : "bad";.
You can write extension on any types, if you need to handle the specific values such as '0' or '00000'. I like it very much because it is handy for any parsing and if you name it well it is readable.
extension BoolTest on String? {
bool get toBool {
if (this == null) {
return false;
} else {
return true;
}
}
}
then you simply use it like this:
String? a = 'hello';
String? b;
print(a.toBool); // true
print(b.toBool); // false
print('hello'.toBool) // true
Have a look at: https://dart.dev/guides/language/extension-methods
Related
I am trying to write a function that takes two arguments: givenType and targetType. If these two arguments match, I want givenType to be returned, otherwise null.
For this objective, I am trying to utilize Dart's is expression (maybe there is a better way to go about it, I am open to suggestions). Initially, I thought it would be as simple as writing this:
matchesTarget(givenType, targetType) {
if (givenType is targetType) {
return givenType;
}
return null;
}
But this produces an error:
The name 'targetType' isn't a type and can't be used in an 'is'
expression. Try correcting the name to match an existing
type.dart(type_test_with_non_type)
I tried looking up what satisfies an is expression but cannot seem to find it in the documentation. It seems like it needs its right-hand operand to be known at compile-time (hoping this is wrong, but it does not seem like I can use a variable), but if so, how else can I achieve the desired effect?
I cant guess the purpose of the function (or the scenario where it would be used, so if you can clarify it would be great). First of all, I don't know if you are passing "types" as arguments. And yes, you need to specify in compile time the right hand argument of the is function.
Meanwhile, if you are passing types, with one change, you can check if the types passed to your function at runtime.
matchesTarget(Type givenType, Type targetType) {
print('${givenType.runtimeType} ${targetType.runtimeType}');
if (givenType == targetType) {
return givenType;
}
return null;
}
main(){
var a = int; //this is a Type
var b = String; //this is also a Type
print(matchesTarget(a,b)); //You are passing different Types, so it will return null
var c = int; //this is also a Type
print(matchesTarget(a,c)); //You are passing same Types, so it will return int
}
But if you are passing variables, the solution is pretty similar:
matchesTarget(givenVar, targetVar) {
print('${givenVar.runtimeType} ${targetVar.runtimeType}');
if (givenVar.runtimeType == targetVar.runtimeType) {
return givenVar.runtimeType;
}
return null;
}
main(){
var a = 10; //this is a variable (int)
var b = "hello"; //this is also a variable (String)
print(matchesTarget(a,b)); //this will return null
var c = 12; //this is also a variable (int)
print(matchesTarget(a,c)); //this will return int
}
The Final Answer
matchesTarget(givenVar, targetType) {
print('${givenVar.runtimeType} ${targetType}');
if (givenVar.runtimeType == targetType) {
return givenVar;
}
return null;
}
main(){
var a = 10; //this is a variable (int)
var b = String; //this is a type (String)
print(matchesTarget(a,b)); //this will return null because 'a' isnt a String
var c = int; //this is also a type (int)
print(matchesTarget(a,c)); //this will return the value of 'a' (10)
}
The as, is, and is! operators are handy for checking types at runtime.
The is operator in Dart can be only used for type checking and not checking if two values are equal.
The result of obj is T is true if obj implements the interface specified by T. For example, obj is Object is always true.
See the below code for an example of how to use the is operator
if (emp is Person) {
// Type check
emp.firstName = 'Bob';
}
Even the error message that you're getting says that
The name 'targetType' isn't a type and can't be used in an 'is'
expression.
So the bottomline is that you can use is only for checking if a variable or value belongs to a particular data type.
For checking equality, you can use the == operator if comparing primitive types, or write your own method for comparing the values. Hope this helps!
Is there any way to get this to return "default" without writing out special functions to check the argument and set it?
void main() {
Thing stuff = Thing(text: null);
print(stuff.text);
}
class Thing{
String text;
Thing({this.text: "default"});
}
I have a map coming in from Firebase and sometimes values will be null and I'd like my class to use its default values when it is provided null.
Thing({text}) : this.text = text ?? 'default';
You will need to add this small snippet because default values in constructors only work if there is no value specified.
The ?? null-aware operator will only use the 'default' value if the value that is being passed is actually null (which will also be the case if no value is specified).
I've recently came across this question How do I solve the 'Failed assertion: boolean expression must not be null' exception in Flutter
where the problem comes from a should be invalid code that gets treated as valid.
This code can be summarized as :
int stuff;
if (stuff = null) { // = instead of ==
}
But why does this code compiles ? As the following will not.
int stuff;
if (stuff = 42) {
}
With the following compile error :
Conditions must have a static type of 'bool'.
So I'd expect out of consistency that if (stuff = null) to gives the same error.
null is a valid value for a bool variable in Dart, at least until Dart supports non-nullable types.
bool foo = null;
or just
bool foo;
is valid.
Therefore in the first case there is nothing wrong from a statical analysis point of view.
In the 2nd case the type int is inferred because of the assignment, which is known to not be a valid boolean value.
bool foo = 42;
is invalid.
When you say var stuff; with no initial value it is giving stuff a static type of dynamic. Since dyamic might be a bool, it's legal to assign null to a variable of type dynamic, and it's legal to use a possibly null bool in a conditional, the compiler doesn't flag this. When you say int stuff; the compiler knows that stuff could not be a bool. The reported error in that case is cause by the static type of stuff, not the assignment to null.
Edit: Got the real answer from someone who knows how to read the spec.
The static type of an assignment expression is the right hand side of the assignment. So the expression stuff = null has the static type of Null which is assignable to bool.
The reasoning is that the value of an assignment is the right hand side, so it makes sense to also use it's type. This allows expressions like:
int foo;
num bar;
foo = bar = 1;
Commonly assignment operation returns the value that it assigns.
int a = 0;
print(a = 3);//Prints 3
So,
When stuff = null,
'stuff = null' returns null. if statement needs a boolean .null is a sub-Type of boolean.
if(null){}
is valid
When stuff = 42,
'stuff = 42' returns 42. if statement needs a boolean .42 is not a sub-Type of boolean.
if(42){}
is not valid
I'm using Dart's analyzer API, which allows me to introspect Dart code.
Here is some example code:
void soIntense(anything, {bool flag: true, int value}) { }
Notice how the flag parameter has a default value of true.
How can I get the default value, assuming I have an instance of ParameterElement?
Here's the best way that I found. I'm hoping there's a better way.
First, check that there is a default value:
bool hasDefaultValue = _parameter.defaultValueRange != null &&
_parameter.defaultValueRange != SourceRange.EMPTY;
Then, you can use a ParameterElement's defaultValueRange.
SourceRange range = _parameter.defaultValueRange;
return _parameter.source.contents.data.substring(range.offset, range.end);
In english:
Get the parameter element's Source's content's data's substring.
I have a custom object called Field. I basically use it to define a single field in a form.
class Field {
var name: String
var value: Any?
// initializers here...
}
When the user submits the form, I validate each of the Field objects to make sure they contain valid values. Some fields aren't required so I sometimes deliberately set nil to the value property like this:
field.value = nil
This seems to pose a problem when I use an if-let to determine whether a field is nil or not.
if let value = field.value {
// The field has a value, ignore it...
} else {
// Add field.name to the missing fields array. Later, show the
// missing fields in a dialog.
}
I set breakpoints in the above if-else and when field.value has been deliberately set to nil, it goes through the if-let block, not the else. However, for the fields whose field.value I left uninitialized and unassigned, the program goes to the else block.
I tried printing out field.value and value inside the if-let block:
if let value = field.value {
NSLog("field.value: \(field.value), value: \(value)")
}
And this is what I get:
field.value: Optional(nil), value: nil
So I thought that maybe with optionals, it's one thing to be uninitialized and another to have the value of nil. But even adding another if inside the if-let won't make the compiler happy:
if let value = field.value {
if value == nil { // Cannot invoke '==' with an argument list of type '(Any, NilLiteralConvertible)'
}
}
How do I get around this? I just want to check if the field.value is nil.
I believe this is because Any? allows any value and Optional.None is being interpreted as just another value, since Optional is an enum!
AnyObject? should be unable to do this since it only can contain Optional.Some([any class object]), which does not allow for the case Optional.Some(Optional) with the value Optional.None.
This is deeply confusing to even talk about. The point is: try AnyObject? instead of Any? and see if that works.
More to the point, one of Matt's comment mentions that the reason he wants to use Any is for a selection that could be either a field for text input or a field intended to select a Core Data object.
The Swifty thing to do in this case is to use an enum with associated values, basically the same thing as a tagged/discriminated union. Here's how to declare, assign and use such an enum:
enum DataSelection {
case CoreDataObject(NSManagedObject)
case StringField(String)
}
var selection : DataSelection?
selection = .CoreDataObject(someManagedObject)
if let sel = selection { // if there's a selection at all
switch sel {
case .CoreDataObject(let coreDataObj):
// do what you will with coreDataObj
case .StringField(let string):
// do what you will with string
}
}
Using an enum like this, there's no need to worry about which things could be hiding inside that Any?. There are two cases and they are documented. And of course, the selection variable can be an optional without any worries.
There's a tip to replace my Any? type with an enum but I couldn't get this error out of my head. Changing my approach doesn't change the fact that something is wrong with my current one and I had to figure out how I arrived at an Optional(nil) output.
I was able to reproduce the error by writing the following view controller in a new single-view project. Notice the init signature.
import UIKit
class Field {
var name: String = "Name"
var value: Any?
init(_ name: String, _ value: Any) {
self.name = name
self.value = value
}
}
class AppState {
var currentValue: Field?
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let f = Field("Amount", AppState().currentValue)
NSLog("\(f.value)")
}
}
In short, I was passing a nil value (AppState().currentValue) to an initializer that accepts Any, and assigns it to a property whose type is Any?. The funny thing here is if I directly passed nil instead, the compiler will complain:
let f = Field("Amount", nil) // Type 'Any' does not conform to protocol 'NilLiteralConvertible'
It seems that somewhere along the way, Swift wraps the nil value of AppState().currentValue in an optional, hence Optional(nil).