I'm currently trying to improve null safety in my flutter app, but having relatively less real-world experiences in working with null safety, I'm not confident in some of my decisions.
For example, my app requires user login, and so I have an Auth class that preserves the state of authentication.
class Auth {
final String? token;
final User? user;
Auth(this.token, this.user);
}
In my app, I ensure that the user property is accessed only when the user is logged in, so it's safe do the following:
final auth = Auth(some_token, some_user);
// and when I need to access the user
final user = auth.user!
which leads to the first question:
Is it recommended to use the null assertion operator in many places in the app?
I personally find it kind of uncomfortable to do things like auth.user!.id throughout the app, so I'm currently handling it like this:
class Auth {
final User? _user;
Auth(this._token, this._user);
User get user {
if (_user == null) {
throw StateError('Invalid user access');
} else {
return _user!;
}
}
}
but I'm not sure if this is a recommended practice in null-safety.
For the next question, I have a class that handles API calls:
class ApiCaller {
final String token;
ApiCaller(this.token);
Future<Data> getDataFromBackend() async {
// some code that requires the token
}
}
// and is accessed through riverpod provider
final apiCallerProvider = Provider<ApiCaller>((ref) {
final auth = ref.watch(authProvider);
return ApiCaller(auth.token);
})
My ApiCaller is accessed through providers and thus the object is created when the App starts. Obviously, it requires token to be available and thus depends on Auth. However, token could also be null when the app starts and the user is not logged in.
Since I'm confident that apiCaller isn't used when there is no existing user, doing this:
class ApiCaller {
// make token nullable
final String? token;
ApiCaller(this.token);
Future<Data> getDataFromBackend() async {
// some code that requires the token
// and use token! in all methods that need it
}
}
final apiCallerProvider = Provider<ApiCaller>((ref) {
final auth = ref.watch(authProvider);
if (auth.token == null) {
return ApiCaller()
} else {
return ApiCaller(auth.token);
}
})
should be fine. However, this also makes me use a lot of token! throughout all methods, and I'm not too sure about that.
I could also simply do ApiCaller('') in the non-null token version, but this seems more of a workaround than a good practice.
Sorry for the lengthy questions. I tried looking for some better articles about real-world practices in null-safety but most are only language basics, so I hope some of you on here could give me some insights. Thanks in advance!
The simplest way to avoid using ! when you know a nullable variable is not null is by making a non-null getter like you did on your first question:
User get user {
if (_user == null) {
throw StateError('Invalid user access');
} else {
return _user!;
}
}
I will let you know that there is no need to check if the value is null before throwing an error, the null check operator does exactly that:
Uset get user => _user!;
Unless of course you care a lot about the error itself and want to throw a different error.
As for your second question, that one is a bit trickier, you know you will not access the variable before it is initialized, but you have to initialize it before it has a value, thus your only option is to make it null, I personally don't like to use the late keyword, but it was built expressly for this purpose, so you could use it. A late variable will not have a value until expressly assigned, and it will throw an error otherwise, another solution is to make a non-null getter like on the other page.
Also, you don't need a null check here because the result is the same:
if (auth.token == null) {
return ApiCaller()
} else {
return ApiCaller(auth.token);
}
instead do this:
return ApiCaller(auth.token);
This does feel to me like a simple problem, you are just not used to working with null-safety, which means that to you, the ! looks ugly or unsafe, but the more you work with it the more you'll become comfortable with it and the less it will look as bad code even if you use it a lot around your app.
Hopefylly, my answer is helpful to you
Is it recommended to use the null assertion operator in many places in the app?
I consider the null assertion operator to be a bit of a code smell and try avoid using it if possible. In many cases, it can be avoided by using a local variable, checking for null, and allowing type promotion to occur or by using null-aware operators for graceful failure.
In some cases, it's simpler and cleaner to use the null assertion as long as you can logically guarantee that the value will not be null. If you're okay with your application crashing from a failed null assertion because that should be logically impossible, then using it is perfectly fine.
I personally find it kind of uncomfortable to do things like
auth.user!.id throughout the app, so I'm currently handling it like
this:
class Auth {
final User? _user;
Auth(this._token, this._user);
User get user {
if (_user == null) {
throw StateError('Invalid user access');
} else {
return _user!;
}
}
}
but I'm not sure if this is a recommended practice in null-safety.
Unless you want to control the error, throwing the StateError is pointless. The null assertion operator will throw an error (a TypeError) anyway.
I personally don't see much value in the user getter. You'd still be using the null assertion operator everywhere, but it'd just be hidden behind a method call. It'd make the code prettier, but it'd be less clear where potential failure points are.
If you find yourself using the null assertion operator for the same variable multiple times in a function, you still can use a local variable to make that nicer:
void printUserDetails(Auth auth) {
final user = auth.user;
user!;
// `user` is now automatically promoted to a non-nullable `User`.
print(user.id);
print(user.name);
print(user.emailAddress);
}
I think ultimately you need to decide what you want your public API to be and what its contracts are. For example, if a user is not logged in, does it make sense to have an Auth object at all? Could you instead have make Auth use non-nullable members, and have consumers use Auth? instead of Auth where null means "not logged in"? While that would be passing the buck to the callers, making them check for null everywhere instead, they're already responsible to not do anything that accesses Auth.user when not logged in.
Another API consideration is what you want the failure mode to be. Does your API contract stipulate in clear documentation that callers must never access Auth.user when not logged in? If the caller is in doubt, are they able to check themselves? If so, then making accesses to Auth.user fatal when it's null is reasonable: the caller violated the contract due to a logical error that should be corrected.
However, in some situations maybe that's too harsh. Maybe your operation can fail at runtime for other reasons anyway. In those cases, you could consider failing gracefully, such as by returning null or some error code to the caller.
final apiCallerProvider = Provider<ApiCaller>((ref) {
final auth = ref.watch(authProvider);
if (auth.token == null) {
return ApiCaller()
} else {
return ApiCaller(auth.token);
}
}
Your ApiCaller class as presented does not have a zero-argument constructor, so that doesn't make sense. If you meant for its constructor to be:
final String? token;
ApiCaller([this.token]);
then there's no difference between ApiCaller() and ApiCaller(null), so you might as well just unconditionally use ApiCaller(auth.token).
Related
I want to create a Flutter tab view, tab count and content must be based on the multiple API calls.
There is 3 section in each tab and each section get data from a separate API.
What is the best method to create a solution? Bloc is the best way to manage the state.
I tried to implement a CustomTabView and set tab data but I'm facing various issues and wired tab combinations. Please help me to solve this problem.
Thank you
This is a general question and doesn't have debug details, so the answer can seem a bit dumb.
Anyway,
The best case is to have a separate block for each section.
But you can also have all requests in one block. It is not recommended at all, but I will explain the method.
For call Multi api in one bloC:
when you use the state manager bloC , you have a method named mapEventToState that calls a second method of Stream type according to your request.
It is in this method that you return the State. In order to call several APIs, you need to define a dynamic variable here for each one. Then connect to each API here and fill these variables with the corresponding response.
To return the result, it is better if all variables containing the response containing statusCode were successful, then the state of this bloC should be succeeded.
...
#override
Stream<MyBlocState> mapEventToState(MyBlocEvent event) async* {
if (event is CallTheAPIsEvent) {
yield ApisHaveNeedCalles();
}
// second method of Stream
Stream<MyBlocState> ApisHaveNeedCalles() {
var resultApi1;
var resultApi2;
var resultApi3;
try {
resultApi1 = _repository.getAPI1();
resultApi2 = _repository.getAPI2();
resultApi3 = _repository.getAPI3();
} catch (e) {
yield ErrorState();
}
if (resultApi1 != null && resultApi2 != null && resultApi13 != null) {
yield SuccessState(resultList: [resultApi1, resultApi2, resultApi3 ]);
}
// more event handling
}
...
Is there any to way use a non-null safe code to a null safety code properly? For example, I have a function that returns a null value but it's not migrated to null safety. For example,
// #dart=2.9
User getUser(){
if(loggedIn){
return user;
}
return null
}
Now I want to call the above code in null safe codeā¦
void build(){
final user = getUser();
if(user==null){
return LoginScreen();
}else{
return HomeScreen();
}
}
Now the linter says that user == null will always return false, assuming the user return by getUser() will always be non-null, but the issue is that getUser() is running inside the non-null safe code.
So whenever I run linter the LoginScreen widget is get removed and the whole logic falls apart.
Now I am not sure if we can fix the linter for this, but I think the better way is to handle annotation in IDE, how Kotlin does with java code. You mark the java function as #Nullable or #Nullable based on your function knowledge and then the Kotlin compiler or IDE takes care of this.
Do we have a way to do this dart? Because I am not able to find it anywhere.
One way to solve this would be to use Null Pattern, but since the codebase is 50% null safe code it would be hard to do everywhere, rather, I would rely on a tool for that.
Maybe you can do that
// #dart=2.9
dynamic getUser(){
if(loggedIn){
return user;
}
return null
}
...
void build(){
final user = getUser();
if(user is User){
return LoginScreen();
}else{
return HomeScreen();
}
}
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.
Is it bad practice to change my getter method like version 2 in my class.
Version 1:
public String getMyValue(){
return this.myValue
}
Version 2:
public String getMyValue(){
if(this.myValue == null || this.myValue.isEmpty()){
this.myValue = "N/A";
}
return this.myValue;
}
I think it is actually quite a bad practice if your getter methods change the internal state of the object.
To achieve the same I would suggest just returning the "N/A".
Generally speaking this internal field might be used in other places (internally) for which you don't need to use the getter method. So in the end, the call to foo.getMyValue() could actually change the behaviour of foo.
Alternatively, the translation from null to "N/A" could be done in the setter, i.e. the internal value could be set to "N/A" if null is passed.
A general remark:
I would only add states such as "N/A" if they are expected by some API or other instance relying on your code. If that is not the case you should rely on the standard null types that are available to you in your programming language.
In my opinion, unless you are doing lazy-loading (which you are not in that case), getters should not change the value. So I would either:
Put the change in the setter
public void setMyValue(String value) {
if(value == null || value.isEmpty()){
this.myValue = "N/A";
} else {
this.myValue = value;
}
}
Or make the getter return a default value if value not set properly:
public String getMyValue() {
if(this.myvalue == null || this.myvalue.isEmpty()){
return "N/A";
}
return this.myValue;
}
In the case of lazy-loading, where I would say that changing your members in a getter is fine, you would do something like:
public String getMyValue() {
if (this.myvalue == null) {
this.myvalue = loadMyValue();
}
return this.myValue;
}
No. You're doing two things here. Getting and setting.
Yes. It's a bad practice.
Why?
When the value is set (in a constructor or setter method), it should be validated, not when a getter method is called. Creating a private validate* method for this is also a good idea.
private boolean validateThisValue(String a) {
return this.myValue != null && !this.myValue.isEmpty();
}
public void setThisValue(String a) {
if (validateThisValue(a)) {
this.myValue = a;
}
else {
// do something else
// in this example will be
this.myValue = "N/A";
}
}
And, in the getter method, never ever change the state of the object. I have worked on some projects, and the getter often must be made const: "this method cannot change internal state".
At least, if you do not want to complicate things, in the getter method, you should return "N/A" rather than change internal state and set myValue to "N/A".
I usually define a specific getter.
Never alter original getter:
public String getMyValue(){
return this.myValue
}
And create a specific getter:
public String getMyValueFormatted(){
if(this.myvalue == null || this.myvalue.isEmpty()){
return "N/A";
}else{
return this.myValue;
}
}
I think it's better to initialize this.myValue = "N/A". And subsequent calls to setMyValue should modify the this.myValue according to your business conditions.
The getMyValue shouldn't modify in any way this.myValue. If your needs are to return a certain value, you should return that value (like "N/A") and not alter this.myValue . Getters must not modify member's value.
I would change better the setter method so, if the value is null or empty, the N/A is assigned to the attribute. So, if you use the attribute in other methods inside the class (v.g. toString()) you will have the intended value there.
Alternatively, change the setter method to launch an exception when the value being set is not right, so the programmer is forced to improve its handling prior to setting the value.
Other than that, it is ok.
I do feel this is a bad practice unless and until you explain the reason why it is so necessary for you modify the object inside the getter method instead of doing it inside the setter method.
Do you feel this cannot be done for some reason? Could you please elaborate?
Do what ever you like. After all getters and setters are just another public methods. You could use any other names.
But if you use frameworks like Spring, you are bound to use those standard names and you should never put your custom codes inside them.
absolutely yes, it's a bad pratice.
Imagine you communicate accross network with a third party (remoting, COM, ...), this will increase the round-trip and then hit application performance.
A setter could modify as part of validation, but a getter should return the value and let the validation be done by the caller. If you do validate, then how should be documented.
This actually highly depends on the contract you want to enforce with your get()-method. According to design-by-contract conventions the caller has to make sure that the preconditions are met (which means doing a validation in a setter method often is actually bad design) and the callee (I do not know if that's the correct english term for that, i.e., the called one) makes sure that the post conditions are met.
If you define your contract so that the get()-method is not allowed to change the object then you are breaking your own contract. Think about implementing a method like
public isValid() {
return (this.myvalue == null || this.myvalue.isEmpty());
}
Advantage of this approach is that you do not have to check wether the return of your get() is "N/A" or something else. This also can be called before calling set() to validate that you do not insert illegal values into your object.
If you want to set a default value you should do that during initialization.
State changes in getters should be a hanging offence. It means that client code must be careful about the order in which it accesses getters and setters and to do this it must have knowledge of the implementation. You should be able to call the getters in any order and still get the same results. A related problem occurs when the setter modifies the incoming value depending on the current state of the object.
You can use some value holder for this purpose. Like Optional class in guava library.