I know that I can require one parameter using #required like below :
class Foo{
String firstParam;
String secondParam;
Foo({#required this.firstParam, this.secondParam});
}
However, I don't know how to require only one of the two parameters without requiring both. I have something that looks like what I want using assert :
class Foo{
String firstParam;
String secondParam;
Foo({this.firstParam, this.secondParam}): assert(this.firstParam != null || this.secondParam!= null);
}
But it doesn't warn me in VS Code and asserts are not used in Release mode.
Is there some way to do it in Dart?
Use this and provide a message for your assertion:
class Foo {
String firstParam;
String secondParam;
Foo({this.firstParam, this.secondParam})
: assert(
(firstParam != null || secondParam != null),
'One of the parameters must be provided',
);
}
Warning if none of the parameters is provided:
I think it's not possible, though you could make both parameters to be required and set explicitly to null if it is needed
Related
In the ToDo tutorial of BlocLibrary I cam along the following snippet.
TodosOverviewState copyWith({
TodosOverviewStatus Function()? status,
List<Todo> Function()? todos,
TodosViewFilter Function()? filter,
Todo? Function()? lastDeletedTodo,
}) {
return TodosOverviewState(
status: status != null ? status() : this.status,
todos: todos != null ? todos() : this.todos,
filter: filter != null ? filter() : this.filter,
lastDeletedTodo:
lastDeletedTodo != null ? lastDeletedTodo() : this.lastDeletedTodo,
);
}
What does this exactly do, how does it work? I know the copyWith but I don't get the .. Function()? ... part.
I found one more reference with Google where it was stated that it helps overcoming a null issue. But why do I need Function and what happens otherwise?
========================
UPDATE: I am not asking what copyWith is good for, only why they are using Function()? to copy a state and a simpler construct to copy a data object. The tutorial can be found here. There you will find the Function in the bloc code in "ToDosOverview" and the simpler one in the ToDoModel in "LocalStorageTodosApi"
the copyWith(...) function is the only pattern used in Dart to copy an object. In Dart an object does not have a copy constructor as in C++, you must do it with copyWith(...) . Moreover , in the view part , state objects cannot be modified, thus you cannot make a setter function, the only way to modify a state object is to copy it changing one/more items. Why this? Because the framework (flutter) must be awared when to repaint a Widget or not by changing the object (internally it uses a boolean isDirty flag to decide if /not if to repaint the widget). How to aware it? if you 'hooked' an object to a View (Widget), how to aware that the object was changed ? you have two scenarios: one, you test the equivalence for every field in that object (in deeep), or : two, simply check if the new object is the same as before or not ( i.e.: the "pointer" was the same as before?). In that manner Flutter awares to repaint the Widget in significally less time. That's why objects that carry out data should all be "immutable" and why you will use the copyWith(...) everywhere to change them (and update the view). In fact freezed library is very useful because you will have your copyWith(...) without write it ( and many other features, like Unions, json serialize, etc...)
Why the fields are functions ?
Because in that manner you can set to null a field.
For example, if you want to copy TodosOverviewState setting to null the status, with a "plain" implementation of the copyWith function you can not:
`TodosOverviewState` copyWith({
TodosOverviewStatus? status,
List<Todo> ? todos,
TodosViewFilter ? filter,
Todo? lastDeletedTodo,
}) {
return TodosOverviewState(
status: status != null ? status: this.status,
todos: todos != null ? todos : this.todos,
filter: filter != null ? filter : this.filter,
lastDeletedTodo:
lastDeletedTodo != null ? lastDeletedTodo : this.lastDeletedTodo,
);
}
In this "plain" implementation of the copyWith, if you make:
TodosOverviewState myObj = ... ;
var copied = myObj.copyWith(status : null );
You obtain that the copied object is the same as myObj.
But, with the functions, you will set status to null .
I am looking for syntactic sugar with null-safe-operators to do this:
map["key"] == null ? default : (int.tryParse(map["key"]!) ?? default)
This however accesses map twice and requires default twice.
Better, but verbose code that also compiles:
String? val = map["key"];
int? res;
if (val != null) { res = int.tryParse(val) }
res ??= default;
In essence I am looking for a way to only call a function if the parameter is not null. E.g. in kotlin you could do (pseudocode)
map["key"].let{ int.tryParse(it) } ...
I found this related question, however writing helper methods is even more verbose and I cannot edit the function to take nullable parameters.
What i would love to see for dart (but afaik this does not exist):
int.tryParse(map["key"]?) ?? default;
I.e. func(null?) => null, no matter what func is, or sth similar.
Is there a smooth way to do that or is the verbose way for now the "accepted" way?
EDIT
I am aware of the int.tryParse("") or int.tryparse(default.toString() hacks, however both are somewhat naughty as they will call tryParse either way instead of skipping it if the value is null anyways. (imagine replacing tryParse with a very expensive factory method)
There is two things you can do:
int.parse(map["key"] ?? default.toString());
This would work if you are sure that map["key"] can be parsed if it is not null.
If you have to do this operation a lot you can also write your own extension function and then use the null-safe operator:
extension NullSaveParser on String {
int? tryToInt() {
return int.tryParse(this);
}
}
And then use this:
map["key"]?.tryToInt() ?? default;
This extension is neccessary because there is currently no way in dart to not call a method if an argument would be null, only if the object that you are calling it on is null can be caught.
I hope this helps.
It's sort of a hack and maybe ugly but for this case you might want to write something like
int.tryParse(map["key"] ?? '') ?? default
That is, put a fallback value inside the tryParse that you know will make the tryParse return null
String playerName(String? name) {
if (name != null) {
return name;
} else {
return 'Guest';
}
}
? checks whether name is null or not, then why is special if (name != null) { condition required?
The String? name means that the parameter name is nullable as you can see lower in the code the if statement then checks if your parameter is not null.
Dart docs definition:
If you enable null safety, variables can’t contain null unless you say they can. You can make a variable nullable by putting a question mark (?) at the end of its type. For example, a variable of type int? might be an integer, or it might be null. If you know that an expression never evaluates to null but Dart disagrees, you can add ! to assert that it isn’t null (and to throw an exception if it is). An example: int x = nullableButNotNullInt!
Link to docs
I'm just working through this whole null-safety mode with my Flutter project and unsure what the difference is with ? and ! in calls to object methods.
For example, the hint was to add a ! conditional. Here's an example I have right now, and I'm unsure if this should be a ? or a ! at the findNbr!.replaceAll().
Future checkItem({String? findNbr}) async {
int? x = int.tryParse(findNbr!.replaceAll('-', ''));
...
Does this mean replaceAll() will not run if findNbr is null?
Or should it be a ? instead? findNbr?.replaceAll()
EDIT: I just noticed I cannot use findNbr?, it's telling String? can't be assigned parameter String.
Or does it mean I say it's not null and run it anyway?
For your information, I have not come close to running my app yet so I have no idea if it even works. But I figure I better know what it's doing before get too much more done. I'm still in the process of converting everything and there's 75-100 dart files. I'm not sure I get the point of it all to be honest, because I just add ? to everything, so its all nullable anyway.
Future checkItem({String? findNbr}) async {
int? x = int.tryParse(findNbr!.replaceAll('-', ''));
...
Does this mean replaceAll() will not run if findNbr is null?
Correct. If findNbr is null, then findNbr! will throw a runtime exception. That would be bad, especially since checkItem's function signature advertises that findNbr is allowed to be null, and therefore it would violate callers' expectations.
Or should it be a ? instead? findNbr?.replaceAll()
EDIT: I just noticed I cannot use findNbr?, it's telling String? can't be assigned parameter String.
You can't use findNbr?.replaceAll(...) because if findNbr is null, then it would be invoking int.tryParse(null), but int.tryParse is not allowed to take a null argument.
What you need to do is one of:
Make findNbr no longer optional:
Future checkItem({required String findNbr}) async {
int? x = int.tryParse(findNbr.replaceAll('-', ''));
...
Allow findNbr to be optional but have a non-null default value:
Future checkItem({String findNbr = ''}) async {
int? x = int.tryParse(findNbr.replaceAll('-', ''));
...
Allow findNbr to be optional but explicitly decide what to do if it is null. For example:
Future checkItem({String? findNbr}) async {
int? x = findNbr == null ? null : int.tryParse(findNbr.replaceAll('-', ''));
...
I'm not sure I get the point of it all to be honest, because I just add ? to everything, so its all nullable anyway.
If you blindly add ? to all types and add ! to all variables, then yes, null-safety would be pointless: doing that would give you the same behavior as Dart before null-safety.
The point of null-safety is to prevent things that shouldn't be null from ever being null. You could have written such code before, but without null-safety, that meant performing runtime null checks (e.g. assert(x != null);, if (x != null) { ... }, or relying on a null-pointer-exception to crash the program if null was used where it wasn't expected). Null-safety means that such checks now can be done at build-time by static analysis, which means that errors can be caught earlier and more completely. Furthermore, whereas previously functions needed to explicitly document whether arguments and return values were allowed to be null (and inadequate or incorrect documentation could be a source of errors), now they're self-documenting in that regard. It's just like using int foo(String s) versus dynamic foo(dynamic s); using strong types catches errors earlier and better describes the function's contract.
I recommend reading Understanding Null Safety if you haven't already done so.
I would like to advice you to use the ! operator, also the called bang operator, as little as possible. You should only use this operator when the dart analyser is wrong and you know for 100% that the value will never be null.
Below is an example of where the dart analyser would be wrong and you should use the bang operator.
// We have a class dog with a nullable name.
class Dog {
String? name;
Dog({this.name});
}
void main() {
// We create a dog without a name.
final dog = Dog();
// We assign the dog a name.
dog.name = 'George';
// The dart analyser will show an error because it can't know if the
// name of the object is not null.
//
// Will throw: `A value of type 'String?' can't be assigned to a
// variable of type 'String'`.
String myDogsName = dog.name;
// To avoid this, you should use the bang operator because you `know` it
// is not null.
String myDogsName = dog.name!;
}
The ? operator simply tells Dart that the value can be null. So every time you want to place a ? operator, ask yourself, can this value ever be null?
The null safety features in Dart are mainly created for helping the developer remember when a value can be null. Dart will now simply tell you when you made a variable nullable in order to force null checks or default values for example.
Pretty basic question, but wondering what the best practice is and can't seem to find any references to this on SO or elsewhere.
Should you check the runTimeType of properties before you attempt to store them when you parse fromJson even if you are confident it should never be anything but the type you think, or null? Or do we just accept the error if this highly unlikely event ever happens?
Thanks !
factory SomeClass.fromJson(Map data) {
if (data == null) return null;
String someString = data['someString']; //no runTimeType check
int someInt = data['someInt']; //no runTimeType check
try {
assert(someString != null, 'Some String was null in Some Class Json');
assert(someInt != null,
'Some Int null in Some Class Json');
} catch (e) {
return null;
}
return SomeClass(someString: someString, someInt: someInt);
}
It will be really nice to check the runtime type of the variable as well, as it will prevent any potential app crash when the database is updated in the future.
As far as the null checks are concerned, I personally pass in a default value like for String I store an empty string as the default value instead of null.
String someString = data['someString'] ?? '';
Even if the database returns a null value, our app should be able to handle those conditions.
The most common type of error that I've personally experienced is during the parsing of data. Type String is not a subtype of int. Something like that. So, I guess it would be nice to check the runtime types of values returned from the backend.