Dart: How to properly handle null-safety? - flutter

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.

Related

Assignment of null to File variable in Flutter

How can I assign null to File variable. I got error when assigning null to File variable.
File myImage=null;
(myImage==null)?
CircleAvatar(
backgroundImage:AssetImage('assets/user_new.png'),
backgroundColor: Colors.cyanAccent,
radius: 70,
)
:
CircleAvatar(
backgroundImage:FileImage(myImage),
backgroundColor: Colors.cyanAccent,
radius: 70,
)
Sound null safety is available in Dart 2.12 and Flutter 2.
When using variables you declare are non-nullable by default and to make them nullable you have to use ? after datatype.
example:
int? i = null
In your case, it will be
File? myImage=null;
and you can use it like below:
(myImage==null)?
CircleAvatar(
backgroundImage:AssetImage('assets/user_new.png'),
backgroundColor: Colors.cyanAccent,
radius: 70,
)
:
CircleAvatar(
backgroundImage:FileImage(myImage!),
backgroundColor: Colors.cyanAccent,
radius: 70,
)
Here when using myImage you will use ! to tell a program that myImage will not be null.
Note: We should avoid using ! wherever possible as it can cause a run time
error but in your case, you are already checking for the null using a
ternary operator so you can safely use !.
I suppose you have nullsafety enabled. With nullsafety a variable declared without a ? cannot be null. To fix your issue do the following.
File? myImage = null;
In Flutter null safety is a thing.
With it, no variables can be null if they haven't got a ?. Add a question mark to your File declaration, as:
File? myImage = null;
Down in your code, you will need to check if myImage is null (since now it can be) if you want to assign it to the backgroundImage parameter. You have 2 choices for this:
backgroundImage:FileImage(myImage!) //Null check: Exception throw only if null
backgroundImage:FileImage(myImage ?? _anotherWorkingFile) //If: use another standard file if null

Null check operator used on a null value & _CastError

Need help in resolving an exception saying "Bad state: Tried to read a provider that threw during the creation of its value."
The Error!
The following _CastError was thrown building Consumer<CartItemCounter>(dirty, dependencies:
[_InheritedProviderScope<CartItemCounter?>]):
Null check operator used on a null value
The relevant error-causing widget was:
Consumer<CartItemCounter>
Consumer:file:///C:/Users/USER/OneDrive/WebProjects/FLUTTER/PRACTICE/UDEMY/Build_eCommerce_App/e_shop/lib/Sto re/storeHome.dart:69:30
Model File: cartItemCounter.dart
#Am I doing the wrong implementation of the null operator here in this file?
import 'package:e_shop/Config/config.dart';
import 'package:flutter/foundation.dart';
class CartItemCounter extends ChangeNotifier {
int _counter = EcommerceApp.sharedPreferences
!.getStringList(EcommerceApp.userCartList)!
.length -
1;
int get count => _counter;
Future<void> displayResult() async {
int _counter = EcommerceApp.sharedPreferences!
.getStringList(EcommerceApp.userCartList)!
.length -
1;
await Future.delayed(
Duration(milliseconds: 100),
() {
notifyListeners();
},
);
}
}
#Implementation: Consuming the Model on my home page is as below.
Positioned(
child: Stack(
children: [
Icon(
Icons.ac_unit,
size: 20.0,
color: Colors.greenAccent,
),
Positioned(
top: 3.0,
bottom: 4.0,
left: 4.0,
child: Consumer<CartItemCounter>(
builder: (context, counter, child) => Text(
EcommerceApp.sharedPreferences!
.getStringList(EcommerceApp.userCartList)!
.length -
1 as String,
//'${EcommerceApp.sharedPreferences!.getStringList
// (EcommerceApp.userCartList)!.length - 1}',
style: TextStyle(
color: Colors.white,
fontSize: 12.0,
fontWeight: FontWeight.w500),
),
),
)
You used the ! operator a lot. Your compiler is warning you, that in some cases the variables you rely on hold no value, and instead of listening to your compiler and thinking about what you might want your program to do when that is the case, you basically yelled "oh shut the f*** up" and slammed the door by putting in a !. This ! did not solve a single problem though. The variable might still not hold a value, you only yelled at the one tool trying to help you with it to stop doing so. Your program still has unfixed problems, you just suppressed early reporting and now you got late reporting... aka a crash.
Remove all ! used to suppress warnings. Then look at each warning. Why could that variable be null here?
Why is EcommerceApp.sharedPreferences nullable? Well, it's your code, you decide. If you want to keep it as nullable, what do you want to do if it's null?
getStringList returns a list, but it can also return null. For example when you start your app for the first time, there will be no data. How do you want to handle that?
You have to answer those questions, not just tell your compiler to shut up about them. And I cannot answer them for you. It's your program. You decide.
So as an example, instead of this horrible line:
EcommerceApp.sharedPreferences!
.getStringList(EcommerceApp.userCartList)!
.length - 1;
That will fail if either sharedPreferences is null or getStringList returns null, you should think and make decisions what should happen if that is the case. I know the code can be more conscise, but this is a learning exercise, so we will do it the verbose way:
int UserCartListCount() {
final source = EcommerceApp.sharedPreferences;
if(source == null) {
// we have no way to read our data, so what now?
return 0;
}
final cartList = source.getStringList(EcommerceApp.userCartList);
if(cartList == null) {
// there was no cart previously saved
return 0;
}
// this is weird... but it's your logic, I have no idea why that would be correct
return cartList.length - 1;
}
And then, you call this function instead in your code.

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.

what does ":" mean or do in Dart?

I have been trying to get an clear explanation for what : means in dart.
As far as I can tell it seems so be some kind of operator , but I can't get a clear cut explanation of what exactly it does.
I understand that it gets used to so assign stuff to other stuff , but I am unsure of the specifics.
I have tried googling it , no luck.
I have tried searching through the dart documentation.
https://dart.dev/guides/language/language-tour#operators
I know that it gets used as part of conditional expressions like here:
https://dart.dev/guides/language/language-tour#conditional-expressions
Here are examples of how it gets used:
Example 1 :
class someBloc extends Bloc<someEvent, someState> {
someBloc() : super(someState.initial());
}
Example 2 :
class someClass {
final String requestType;
final String name;
factory someClass.fromJson(Map<String, dynamic> json) {
return SomeClass(
requestType: json['Type'],
name: json['Name']);
}
}
In examples 1 & 2:
The : is used to set a named parameter. For example, let's say I want to create a Card widget. The card widget optionally takes a named parameter called elevation. Therefore, I can do this:
Card(
elevation: 4.0,
)
Another way : is used is for writing quick conditional expressions.
final String isMyListEmpty = myList.isEmpty ?? 'Yes, the list is empty' : 'No, the list is not empty';
In this example, our condition is myList.isEmpty. If this condition is true, the first expression after ?? is returned. Else, if the condition is false, the expression after the : is returned.
It's just an assignment operator. You assign a value to a widget property. For example, Container widget has many properties, like color or height:
Container(
margin: const EdgeInsets.all(10.0),
color: Colors.amber[600],
width: 48.0,
height: 48.0,
),

How can you call a void function in Flutter without passing a parameter?

I'm new to Flutter and am working through the intro course on Udacity. In one of the tasks, I was trying to follow the code and I can't make much sense of it. Here's the code from the solution of the project (I've cut and paste the parts that matter, and also legal disclaimer I do not own any of this code, it's from the sample Flutter Udacity project):
Widget build(BuildContext context) {
final input = Padding(
padding: _padding,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextField(...),
_createDropdown(_fromValue.name, _updateFromConversion),
],
),
);
}
void _updateFromConversion(dynamic unitName) {
setState(() {
_fromValue = _getUnit(unitName);
});
if (_inputValue != null) {
_updateConversion();
}
}
Widget _createDropdown(String currentValue, ValueChanged<dynamic> onChanged) {
return Container(
margin: EdgeInsets.only(top: 16.0),
decoration: BoxDecoration(...),
padding: EdgeInsets.symmetric(vertical: 8.0),
child: Theme(...),
child: DropdownButtonHideUnderline(
child: ButtonTheme(
alignedDropdown: true,
child: DropdownButton(
value: currentValue,
items: _unitMenuItems,
onChanged: onChanged,
style: Theme.of(context).textTheme.title,
),
),
),
),
);
}
Here's where I'm stuck. _updateFromConversion requires an input parameter unitName. But when they call it, in _createDropdown, they don't pass any. So how does _updateFromConversion know what unitName is? Also, is _updateFromConversion executed before _createDropdown, or is it executed when the "onChanged" property of DropdownButton is set?
Second question: they're passing that function with return type void into _createDropdown, which is expecting ValueChanged. Shouldn't this throw an error?
If someone can explain the flow of this code and what I am missing I would greatly appreciate it.
Thank you!
You seem to be misunderstanding the assignment of a variable to a function for a function call.
Let me try to show it with example code.
void _updateFromConversion(dynamic unitName) {
print("Unit name: $unitName");
}
class SomeClass {
void Function(dynamic arg) myFunction;
}
void main() {
final c = SomeClass()..myFunction = _updateFromConversion;
print("Created c. Calling its function");
c.myFunction("foo");
print("Done");
}
When you run this code, you will see this printed:
Created c. Calling its function
Unit name: foo
Done
This shows that the _updateFromConversion function is not called when you create the SomeClass instance in SomeClass()..myFunction = _updateFromConversion;. This is only an assignment (it assigns to the field myFunction the value _updateFromConversion... yes, in Dart a function itself can be a value)!
You should know that because there's no () after the function name, and function invocation in Dart always must contain the list of arguments between () even if it's empty.
So, here's where the function is invoked:
c.myFunction("foo");
See? There's a list of arguments containing a single value, "foo". That's why the function then prints Unit name: foo, because the argument unitName takes the value "foo".
TL;DR
This is a function invocation:
c.myFunction("foo");
This is NOT:
c.myFunction;
Yashvin,
In dart function can be passed as parameter to other functions. This is usually used to pass callbacks for instance.
In example you provided, function _updateFromConversion is passed as parameter onChanged to another function _createDropdown.
In that function it will be assigned to onChanged listener of DropdownButton button.
Every time value of DropdownButton changes, this function will be invoked and will be passed selected value of DropdownButton.