Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

dart nullsaftey and using old packages

Tags:

null

dart

I've enabled the dart 2.8 nullsaftey experiment.

I've converted my app to nullsaftey but its using an old pre-nullsafety package.

The problem is that the old package has a method which can return null:

/// Returns the environment variable with [name] or null if it doesn't
/// exist
String env(String name);

Which is used as follows:

var home = env('HOME');

If the HOME environment variable is missing, env returns null.

The problem is that env is declared as returning a String.

So when I write

var home = env('HOME');
home ??= '/home';

I get an error:

The operand can't be null, so the condition is always false.
Try removing the condition, an enclosing condition, or the whole conditional statement.

Given that all the nullsaftey release announcements say you can use nullsaftey with older packages, I'm guessing there is some way to declare an imported packages as non-nullsafe.

Problem is that I can't find any documentation on how to do this.

like image 211
Brett Sutton Avatar asked Jul 16 '20 00:07

Brett Sutton


People also ask

How do you handle null safety in darts?

Use the null assertion operator ( ! ) to make Dart treat a nullable expression as non-nullable if you're certain it isn't null. is null, and it is safe to assign it to a non-nullable variable i.e.

How do you fix a null safety error in Flutter?

Copy: --no-sound-null-safety and add into "additional run args". Run your app with "Run/Play" Button or from "Run" Menu. In this way, you can solve "Error: Cannot run with sound null safety, because the following dependencies don't support null safety" error on Flutter project.


1 Answers

null safety has not been released yet! that is why you need to provide the experiment flag.

Language versioning

By default, whether or not null safety is supported in a library is determined by its language version. Any language version 2.8 or lower counts as opted out of null safety, and 2.9 or higher (subject to change) is opted in. The language version itself can come from one of two places:

  1. The minimum bound of the package's declared SDK constraint. The following package will have a language version of 2.8.

name: foo
env:
  sdk:
    ">=2.8.0 <3.0.0"
  1. A language override comment at the top level of the file, before any other declarations. The following library will have a language version of 2.8.
// @dart=2.8

class Foo {}

The language override comment will take precedence over the SDK constraint, but only within the single library where it is declared.

Interaction between null safe and non-null safe code

The problem you are having is reproducible without different packages or incorrect language versions though, and has to do with the interaction between null-safe and non-null-safe code. Consider the following example:

// @dart=2.8

String foo() {
  return null;
}
// @dart=2.9

import 'a.dart';

void main() {
  var value = foo();
  value ??= 'asd';
}

The return type of foo doesn't become String?, instead it gets tagged as String* - this is known as a legacy type. A legacy type is treated as a non-null type in opted in libraries. The goal of legacy types is to make it easier to migrate to null-safety through an in-order migration Consider the example below:

// @dart=2.9
void foo(String value) {
 // do something with non-null String.
}
// @dart=2.8
import 'a.dart';

void main() {
  foo(getStringFromAPI());
}

While foo requires a non-null string, it isn't possible for the entry-point to actually pass it one - since it has not opted in yet. Without the treatment of legacy types as non-nullable types, it would not be possible to gradually migrate - because all libraries would need to be updated at once, or only updated to accept nullable types.

Out of order migration

By calling code that has not been migrated to null-safety from a null safe library, you are increasing the risk that you will be broken when that dependency eventually migrates. In you example, if home was treated as non-nullable then updating to a version of the dependency with an updated return value of String? would cause a compilation error.

For your specific case, I would recommend specifically annotating the type of home as String?. This is a perfectly valid type annotation, since in general T and T* are always assignable to T?. It also more correct, since you know the API can return null.

String? home = env('HOME');
home ??= '/home';

EDIT June 2021:

Null safety has released, yay! The first version of Dart with null safety enabled by default ended up being 2.12 and not 2.9 as documented in the question above.

like image 139
Jonah Williams Avatar answered Dec 04 '22 05:12

Jonah Williams