Not null-able
If we annotate a type to be say, int, it means it can only be an integer. It cannot be null.
| 
					 1  | 
						int a = null // Error, a can't be null  | 
					
We can’t add two numbers if one of them is null:
| 
					 1 2 3 4  | 
						int a; // null int b = 2; print(a + b);  // Without Nullable, we get run time error!  | 
					
Nullable (?)
If you want that int to be null, use the question mark to denote that.
| 
					 1  | 
						int? a = null; // Can be null  | 
					
| 
					 1 2 3  | 
						int? a; // null int b = 2; print(a+b); // <-- compile time error here!  | 
					
With nullable, we get Compile Time Error.
This makes us write safer programs.
Assertion Operator (!)
If you’re sure  that a nullable variable will ALWAYS have non-nullable values,
it’s safe to assign it to a non-nullable variable with the ! operator.
| 
					 1 2  | 
						  int? aNullableInt = 8;   print(aNullableInt!.isEven); // 8  | 
					
However, if you’re wrong, you’ll get a runtime error!
| 
					 1 2 3 4 5  | 
						  int? aNullableInt = null;   print(aNullableInt!.isEven); // run time error!   // Unhandled exception:   // Null check operator used on a null value  | 
					
if-null operator (??)
In the example, x is -1, and thus maybeValue will be null;
| 
					 1 2 3 4 5 6  | 
						int x = -1; int? maybeValue; // null, due to x is -1 if ( x > 0) {   print('larger than 0');   maybeValue = x; }  | 
					
Usually, we’d write it like this:
| 
					 1  | 
						int value = maybeValue == null ? 0 : maybeValue;   | 
					
Now, we can use if-null operator like so:
| 
					 1 2 3  | 
						// if not null, assign value to maybeValue // if null, assign 0 to value int value = maybeValue ?? 0;  | 
					
Null Safety with type safety
So if x is -1, then maybeValue is left with null.
That’s not very good for future usage and printing.
So we can use a Type safety where if maybeValue is null, then we assign it to a default number.
Its a shorthand for if-null
| 
					 1 2 3 4 5 6 7 8 9 10 11  | 
						int x = -1; int? maybeValue; if ( x > 0) {   print('larger than 0');   maybeValue = x; } // if maybeValue is null, then take on 0. maybeValue ??= 0;  print(maybeValue);  | 
					
Conditional Access Operator
Say we declare an array, and its of type String?.
| 
					 1 2 3 4 5  | 
						const cities = <String?>['Shenzhen', 'Beijing', null]; for (var city in cities) {    print(city.toUppercase()); // run time crash at index 2    // city is null, and null does not have toUppercase method. }  | 
					
in order to fix this, we use conditional access operator:
| 
					 1 2 3 4 5 6 7 8  | 
						String? haha; print(haha?.toUpperCase()); // if null, would display null // then const cities = <String?>['Shenzhen', 'Beijing', null]; for (var city in cities) {    print(city?.toUpperCase()); // when city is null, it'll simply print it }  |