Null 안전성으로 마이그레이션

이 페이지는 언제 어떻게 null 안전성을 준수하는 코드로 마이그레이션 해야 하는지 설명합니다. 각 패키지를 마이그레이션하기 위한 기본 단계는 다음과 같습니다:

  1. 의존하는 패키지 마이그레이션이 완료될 때까지 기다리세요.
  2. 상호작용형 마이그레이션 도구를 사용하여 패키지 코드를 마이그레이션 하세요.
  3. 패키지 코드를 정적으로 분석하세요.
  4. 변경 사항이 제대로 작동하는지 테스트 하세요.
  5. 해당 패키지가 이미 pub.dev에 있다면, null-safe 버전을 프리릴리즈 버전으로 퍼블리시 하세요.

다음 영상은 마이그레이션 툴의 사용법을 알려줍니다:

1. 마이그레이션을 대기하세요

우리는 개발자들이 코드를 순차적으로 마이그레이션할 것을 권고하며, 먼저 의존 관계의 가장 말단에 있는 의존성을 마이그레이션할 것을 권장합니다. 예를 들어, C가 B에 의존하고 B가 A에 의존하는 경우 A -> B -> C의 순서로 마이그레이션 해야 합니다.

Illustration of C/B/A sentence

모든 의존성의 마이그레이션이 완료되기 전에 마이그레이션을 수행할 수 있지만, 그렇게 하면 마이그레이션이 완료된 후에 코드를 다시 변경해야 할 수 있습니다. 예를 들어, 함수가 nullable 매개변수를 받을 수 있다고 추측할 경우, 의존 관계에 있는 패키지의 마이그레이션으로 인해 해당 매개변수가 non-nullable로 변경되면, nullable 매개변수를 전달할 때 컴파일 에러가 발생합니다.

이 섹션에서는 null 안전성 모드에서 dart pub outdated를 사용하여 종속성을 확인하고 업데이트하는 방법에 대해 설명합니다. 다음 지침은 코드의 버전 관리가 되고 있고, 언제든지 모든 변경을 롤백할 수 있다고 가정합니다.

Switch to the latest Dart 2.19 release

Dart SDK를 최신 Dart 2.19 릴리즈로 전환하세요. 이 버전은 Flutter 3.7 SDK에 포함되어있습니다.

Dart 버전이 2.19인지 확인하세요:

$ dart --version
Dart SDK version: 2.19.6

종속 상태 확인

다음 커맨드를 사용하여 패키지 의존성의 마이그레이션 상태를 체크합니다:

$ dart pub outdated --mode=null-safety

모든 패키지가 null 안전성을 지원한다면, 마이그레이션을 진행하면 됩니다. 그렇지 않는다면 Resolvable 열에 나열된 null 안전성 버전으로 마이그레이션 하세요.

다음은 간단한 패키지에 대한 예제입니다. 각 패키지의 녹색 체크 표시는 해당 버전이 null 안전성을 지원함을 나타냅니다:

Output of dart pub outdated

위의 출력은 패키지의 의존성이 사용 가능한 null 안전성을 지원하는 프리 릴리즈 버전을 가지고 있음을 보여줍니다.

패키지 의존성 중 일부가 아직 null 안전성을 지원하지 않는 경우, 해당 패키지 작성자에게 연락하는 것을 추천합니다. pub.dev의 패당 패키지 페이지에서 작성자의 연락처를 찾을 수 있습니다.

의존성 업데이트

패키지 코드를 마이그레이션하기 전에, 패키지의 의존성을 null-safe한 버전으로 업데이트 하세요:

  1. Null 안전성을 지원하는 최신 버전으로 업그레이드하고 싶다면 dart pub upgrade --null-safety 커맨드를 실행하세요. Note: 이 커맨드는 pubspec.yaml 파일을 수정합니다.

  2. dart pub get 커맨드를 실행하세요.

2. Migrate

Most of the changes that your code needs to be null safe are easily predictable. For example, if a variable can be null, its type needs a ? suffix. If a named parameter shouldn’t be nullable, mark it required or give it a default value.

You have two options for migrating:

Using the migration tool

The migration tool takes a package of null-unsafe Dart code and converts it to null safety. You can guide the tool’s conversion by adding hint markers to your Dart code.

Before starting the tool, make sure you’re ready:

  • Use the latest 2.19 release of the Dart SDK.
  • Use dart pub outdated --mode=null-safety to make sure that all dependencies are null safe and up-to-date.

Start the migration tool by running the dart migrate command in the directory that contains the package’s pubspec.yaml file:

$ dart migrate

If your package is ready to migrate, then the tool produces a line like the following:

View the migration suggestions by visiting:

Visit that URL in a Chrome browser to see an interactive UI where you can guide the migration process:

Screenshot of migration tool

For every variable and type annotation, you can see what nullability the tool infers. For example, in the preceding screenshot, the tool infers that the ints list (previously a list of int) in line 1 is nullable, and thus should be a list of int?.

Understanding migration results

To see the reasons for each change (or non-change), click its line number in the Proposed Edits pane. The reasons appear in the Edit Details pane.

For example, consider the following code, from before null safety:

var ints = const <int>[0, null];
var zero = ints[0];
var one = zero + 1;
var zeroOne = <int>[zero, one];

The default migration when this code is outside a function (it’s different within a function) is backward compatible but not ideal:

var ints = const <int?>[0, null];
var zero = ints[0];
var one = zero! + 1;
var zeroOne = <int?>[zero, one];

By clicking the line 3 link, you can see the migration tool’s reasons for adding the !. Because you know that zero can’t be null, you can improve the migration result.

Improving migration results

When analysis infers the wrong nullability, you can override its proposed edits by inserting temporary hint markers:

  • In the Edit Details pane of the migration tool, you can insert hint markers using the Add /*?*/ hint and Add /*!*/ hint buttons.

    These buttons add comments to your file immediately, and there’s no Undo. If you don’t want a hint that the tool inserted, you can use your usual code editor to remove it.

  • You can use an editor to add hint markers, even while the tool is still running. Because your code hasn’t opted into null safety yet, you can’t use new null-safety features. You can, however, make changes like refactoring that don’t depend on null-safety features.

    When you’ve finished editing your code, click Rerun from sources to pick up your changes.

The following table shows the hint markers that you can use to change the migration tool’s proposed edits.

Hint marker Effect on the migration tool
expression /*!*/ Adds a ! to the migrated code, casting expression to its underlying non-nullable type.
type /*!*/ Marks type as non-nullable.
/*?*/ Marks the preceding type as nullable.
/*late*/ Marks the variable declaration as late, indicating that it has late initialization.
/*late final*/ Marks the variable declaration as late final, indicating that it has late, one-time initialization.
/*required*/ Marks the parameter as required.

A single hint can have ripple effects elsewhere in the code. In the example from before, manually adding a /*!*/ marker where zero is assigned its value (on line 2) makes the migration tool infer the type of zero as int instead of int?. This type change can affect code that directly or indirectly uses zero.

var zero = ints[0]/*!*/;

With the above hint, the migration tool changes its proposed edits, as the following code snippets show. Line 3 no longer has a ! after zero, and in line 4 zeroOne is inferred to be a list of int, not int?.

First migration Migration with hint
var ints = const <int?>[0, null];
var zero = ints[0];
var one = zero! + 1;
var zeroOne = <int?>[zero, one];
var ints = const <int?>[0, null];
var zero = ints[0]/*!*/;
var one = zero + 1;
var zeroOne = <int>[zero, one];

Opting out files

Although we recommend migrating all at once, sometimes that isn’t practical, especially in a large app or package. To opt out a file or directory, click its green checkbox. Later, when you apply changes, each opted out file will be unchanged except for a 2.9 version comment.

For more information about incremental migration, see Unsound null safety.

Note that only fully migrated apps and packages are compatible with Dart 3.

Applying changes

When you like all of the changes that the migration tool proposes, click Apply migration. The migration tool deletes the hint markers and saves the migrated code. The tool also updates the minimum SDK constraint in the pubspec, which opts the package into null safety.

The next step is to statically analyze your code. If it’s valid, then test your code. Then, if you’ve published your code on, publish a null-safe prerelease.

Migrating by hand

If you prefer not to use the migration tool, you can migrate manually.

We recommend that you first migrate leaf libraries—libraries that don’t import other files from the package. Then migrate libraries that directly depend on the leaf libraries. End by migrating the libraries that have the most intra-package dependencies.

For example, say you have a lib/src/util.dart file that imports other (null-safe) packages and core libraries, but that doesn’t have any import '<local_path>' directives. Consider migrating util.dart first, and then migrating simple files that depend only on util.dart. If any libraries have cyclic imports (for example, A imports B which imports C, and C imports A), consider migrating those libraries together.

To migrate a package by hand, follow these steps:

  1. Edit the package’s pubspec.yaml file, setting the minimum SDK constraint to at least 2.12.0:

      sdk: '>=2.12.0 <3.0.0'
  2. Regenerate the package configuration file:

    $ dart pub get

    Running dart pub get with a lower SDK constraint of at least 2.12.0 sets the default language version of every library in the package to a minimum of 2.12, opting them all in to null safety.

  3. Open the package in your IDE.
    You’re likely to see a lot of analysis errors. That’s OK.

  4. Migrate the code of each Dart file, using the analyzer to identify static errors.
    Eliminate static errors by adding ?, !, required, and late, as needed.

See Unsound null safety for more help on migrating code by hand.

3. Analyze

Update your packages (using dart pub get in your IDE or on the command line). Then use your IDE or the command line to perform static analysis on your code:

$ dart pub get
$ dart analyze     # or `flutter analyze`

4. Test

If your code passes analysis, run tests:

$ dart test       # or `flutter test`

You might need to update tests that expect null values.

If you need to make large changes to your code, then you might need to remigrate it. If so, revert your code changes before using the migration tool again.

5. Publish

We encourage you to publish packages—possibly as prereleases—as soon as you migrate:

Update the package version

Update the version of the package to indicate a breaking change:

  • If your package is already at 1.0.0 or greater, increase the major version. For example, if the previous version is 2.3.2, the new version is 3.0.0.

  • If your package hasn’t reached 1.0.0 yet, either increase the minor version or update the version to 1.0.0. For example, if the previous version is 0.3.2, the new version is either 0.4.0 or 1.0.0.

Check your pubspec

Before you publish a stable null safety version of a package, we strongly recommend following these pubspec rules:

  • Set the Dart lower SDK constraint to the lowest stable version that you’ve tested against (at least 2.12.0).
  • Use stable versions of all direct dependencies.

Welcome to null safety

If you made it this far, you should have a fully migrated, null-safe Dart package.

If all of the packages you depend on are migrated too, then your program is sound with respect to null-reference errors. You should see output like this when running or compiling your code:

Compiling with sound null safety

From all of the Dart team, thank you for migrating your code.