Is there a way to do null safe GORM dynamic finders? - grails

Is there a way ensure null safety in a GORM dynamic finder?
If I wanted to do something like Book.findByName(author.bookname), I could guard against null authors by changing it to Book.findByName(author?.bookname) but how do I avoid an exception on nulls?
Book.findByName() is applicable for argument types: () values: []

auther?.bookname ? Book.findByName(author.bookname) : null //or do nothing
Based on the fact from the example that bookname is NOT NULL.
or you can use find
Book.find {name == author?.bookname} //Returns null if no records found

Related

'List<dynamic>' is not a subtype of type 'List<Comment>'. Why can't Dart infer this type? How do I fix this?

I have nested comments. In order to fetch them from JSON, I have the following fromJson() function:
Comment.fromJson(Map<String, dynamic> json)
: id = json['id'],
...
children = json['children'] != null
? json['children'].map((c) => Comment.fromJson(c)).toList()
: null,
...
This checks if the current comment has any children comments, and if so, recursively parses them from JSON into a Comment. However when I run this, I get the following error:
[ERROR:flutter/shell/common/shell.cc(214)]
Dart Unhandled Exception:
type 'List<dynamic>' is not a subtype of type 'List<Comment>'
stack trace: #0 new Comment.fromJson (package:app/models/comment.dart:43:18)
Which points to the line where I assign children with children = json.... I'm new to dart, but I don't understand how mapping over a list, and returning a Comment doesn't let Dart infer the type of the list. How do I fix this? Adding as List<Comment> after toList() didn't work. If I add as Comment after Comment.fromJson(c), I get unnecessary cast.
Dart can't infer anything from json['children'].map(...).toList() because json['children'] is of type dynamic, and therefore it doesn't know what the .map call is going to resolve to at runtime.
At runtime, since .map is called without an explicit type, it ends up being a call to .map<dynamic>(...), and then .toList() returns List<dynamic>.
Try either:
Explicitly using .map<Comment>(...).
Casting json['children']: (json['children'] as List<Map<String, dynamic>>).map(...). It'd probably be a good idea to validate this anyway.

Null safety type promotion when assigning non-null value literal

In nullsafety.dartpad.dev if I write the following code:
void main() {
String? name = 'Bob';
print(name.length);
}
I get the following compile-time error:
An expression whose value can be 'null' must be null-checked before it can be dereferenced
And the following runtime error:
Property 'length' cannot be accessed on 'String?' because it is potentially null.
The Type promotion on null checks documentation says:
The language is also smarter about what kinds of expressions cause promotion. An explicit == null or != null of course works. But explicit casts using as, or assignments, or the postfix ! operator we’ll get to soon also cause promotion. The general goal is that if the code is dynamically correct and it’s reasonable to figure that out statically, the analysis should be clever enough to do so.
Question
There is no possible way name could be null in the code above. The documentation also says assignments should cause type promotion. Am I misunderstanding type promotion or is this a bug in DartPad?
Clarification
Since a couple of the answers are providing workaround solutions to the error messages, I should clarify that I'm not trying to solve the coding problem above. Rather, I'm saying that I think the code should work as it it. But it doesn't. Why not?
This answer is in response to the bounty that was added to the original question. The bounty reads:
Please explain how String? is different from String and how type
promotion works in Dart.
String? vs String
The type String? can contain a string or null. Here are some examples:
String? string1 = 'Hello world';
String? string2 = 'I ❤️ Dart';
String? string3 = '';
String? string4 = null;
The type String, on the other hand, can only contains strings (once null safety is a part of Dart, that is). It can't contain null. Here are some examples:
String string1 = 'Hello world';
String string2 = 'I ❤️ Dart';
String string3 = '';
If you try to do the following:
String string4 = null;
You'll get the compile-time error:
A value of type 'Null' can't be assigned to a variable of type 'String'.
The String type can't be null any more than it could be an int like 3 or a bool like true. This is what null safety is all about. If you have a variable whose type is String, you are guaranteed that the variable will never be null.
How type promotion works
If the compiler can logically determine that a nullable type (like String?) will never be null, then it converts (or promotes) the type to its non-nullable counterpart (like String).
Here is an example where this is true:
void printNameLength(String? name) {
if (name == null) {
return;
}
print(name.length);
}
Although the parameter name is nullable, if it actually is null then the function returns early. By the time you get to name.length, the compiler knows for certain that name cannot be null. So the compiler promotes name from String? to String. The expression name.length will never cause a crash.
A similar example is here:
String? name;
name = 'Bob';
print(name.length);
Although name is nullable here, too, the string literal 'Bob' is obviously non-null. This also causes name to be promoted to a non-nullable String.
The original question was regarding the following:
String? name = 'Bob';
print(name.length);
It seems that this should also promote name to a non-nullable String, but it didn't. As #lrn (a Google engineer) pointed out in the comments, though, this is a bug and when null safety comes out, this will also work like the previous example. That is, name will be promoted to a non-nullable String.
Further reading
Sound null safety
Type promotion on null checks
I understand what you are saying. Try this out.
In order for type promotion to work you must first confirm that the value is not null as the documentation says.
As you can see in the picture dart is able to do the type promotion or understand that name is not going to be null because it checks that on the if statement beforehand.
But if using it outside the if statement without checking if it is not null beforehand, dart knows it can be assigned null anytime again. That’s why it encourages always checking if it is null. Because any instatiated variable ( a variable with a value assigned) can be assigned null in the future.

What's the difference between ? and ! in collections like Map, List and Set?

What's the difference between ? and ! when used in a collection in Dart?
Say, I have:
var list = [1, 2];
Now, I can either use
print(list?[0]); // prints 1
or
print(list![0]); // also prints 1
Both of them seems to do the same job, so what's the difference?
Both of them seem to do the same job because your list is of type List<int> (non-nullable) and not the List<int>? (nullable). If your list had been of nullable type like:
List<int>? list;
you'd see the difference.
Using? (Null aware operator)
It would be safe to use ? because in case list is null, list?[0] would still print null rather than throwing an error.
print(list?[0]); // Safe
or you could also use ?? to provide a default value.
print(list?[0] ?? -1); // Safe. Providing -1 as default value in case the list is null
Using ! (Bang operator)
However, ! throws runtime error because you're explicitly saying that your list is not null and you're downcasting it from nullable to non-nullable:
print(list![0]); // Not safe. May cause runtime error if list is null
This is equivalent to
print((list as List)[0]);

Is there null aware sum? [duplicate]

In Dart the null-aware operator for methods in combination with the ?? operator does not work well.
Image having a call chain like this:
object.getter.getter.getter.getter
Now when object could be null and I do not want an exception I have to write this:
object?.getter?.getter?.getter?.getter
That is a null check for every getter, even though only object can be null, but then the getter's obviously do not work.
I am okay with this. But now I have a scenario, where I want to return another value if null, but I am working with a list:
list[index][index][index]
How do I use the null-aware operator on the List getter?
This does not work:
list?[index]
and this does not exist for List's in Dart.
list?.get(index)
I want to achieve something like this:
list?[index]?[index]?[index] ?? 0
Where I know that only list will ever be null.
This long code would work:
list == null ? 0 : list[index][index][index]
There is no null-aware operator for the indexing operator ([])
You can use elementAt() instead:
list?.elementAt(index)?.elementAt(index)?.elementAt(index) ?? 0
UPDATE - 2021/05/24
If you are using null-safety, you can now use this:
list?[index]?[index]?[index] ?? 0

Strange behavior of gorm finder

In a controller I have this finder
User.findByEmail('test#test.com')
And works.
Works even if I write
User.findByEmail(null)
But if i write
User.findByEmail(session.email)
and session.email is not defined (ergo is null) it throw exception
groovy.lang.MissingMethodException: No signature of method: myapp.User.findByEmail() is applicable for argument types: () values: []
Is this behavior right?
If i evaluate "session.email" it give me null so I think it must work as it do when I write
User.findByEmail(null)
Even more strange....
If I run this code in groovy console:
import myapp.User
User.findByEmail(null)
It return a user that has null email but if I run the same code a second time it return
groovy.lang.MissingMethodException: No signature of method: myapp.User.findByEmail() is applicable for argument types: () values: []
You can't use standard findBySomething dynamic finders to search for null values, you need to use the findBySomethingIsNull version instead. Try
def user = (session.email ? User.findByEmail(session.email)
: User.findByEmailIsNull())
Note that even if User.findByEmail(null) worked correctly every time, it would not necessarily give you the correct results on all databases as a findBySomething(null) would translate to
WHERE something = null
in the underlying SQL query, and according to the SQL spec null is not equal to anything else (not even to null). You have to use something is null in SQL to match null values, which is what findBySomethingIsNull() translates to.
You could write a static utility method in the User class to gather this check into one place
public static User byOptEmail(val) {
if(val == null) {
return User.findByEmailIsNull()
}
User.findByEmail(val)
}
and then use User.byOptEmail(session.email) in your controllers.
Jeff Brown from grails nabble forum has identified my problem. It's a GORM bug. see jira
More info on this thread
This jira too
I tried with debugger and it looks it should be working, as you write. Maybe the groovy itself is a little bit confused here, try to help it this way:
User.findByEmail( session['email'] )

Resources