はじめに

日本時間の3月4日に行われた Flutter のオンラインイベント Flutter Engage で、Flutter バージョン 2.0 のリリースが発表されました。

2018年12月に バージョン1.0 がリリースされたので約2年ぶりのメジャーバージョンアップになりますね。
イベント内ではその他にも色々と発表がありました。

このほかにもトピックはあるので気になった方は、以下の公式動画や記事を参考にしてみてください。

さて今回は、あわせて発表された Dart(Flutter で使用されている言語)の Sound Null Safety について調べてみました。

Sound Null Safety とは?

実行時に、Nullが原因のエラー NullPointerException を発生させない仕組みのことです。Null安全とも。

Dart の Sound Null Safety(以下 Null Safety) は、以下の3つの設計原則に基づいているそうです。

  • 変数はデフォルトで Non-Null
  • 段階的に採用可能
    • 同じプロジェクト内で、Nullable と Non-Null を混在させながら段階的に移行可能。
    • 移行のツールも用意されている。
  • 健全性
    • Dart の Null Safety は健全であり、コンパイラの最適化が可能です。
    • プロジェクト全体、また依存関係を Null Safety に移行するとバグの現象以外にも、バイナリサイズ減少、実行速度改善などメリットがある。

Null safety principles

Null Safety に関しては NNBD(Non-Null By Default) という名で、長いあいだ議論されていたようです。
Dart には以前から、Null-aware 演算子(?.や??)などはありましたが、型レベルでの Nullable / Non-Null を区別する仕組みはありませんでした。

今回のリリースで実装され、はれてNull安全な言語の仲間入りとなったと言えます。

// Null-aware 演算子(?.や??)で、NullPointerException を発生させない仕組みはあった
int num = null;
print(num?.toString());      // null

String str = null;
print(str ?? 'empty');        // empty

それでは、まず Null Safety のコード記述について見ていきましょう。

コード記述例

Nullable は Swift や Kotlin と同じく、型の後に ? をつけると Nullable になります。
デフォルトは Non-Null です。

// Nullable
String? nullableString = null;

// Non-Null
String nonNullString = 'Non-Null String';

Nullable から Non-Null の変換

Nullable のものを Non-Null として扱う方法はいくつかあります。

  • 参照する前に Non-Null の値を代入する
  • Null-aware 演算子(?? または ??=)を使う
  • !(Swift でいう強制アンラップ)を使う
  • 直前で Null チェックを行う
// 参照する前に Non-Null の値を代入する
String? str = null;
str = 'hoge';
print(str.isEmpty);

// Null-aware演算子(??)を使う
String? str2 = null;
//str2 ??= 'hoge';        // または Null-aware演算子(??=)を使うやり方
print(str2 ?? "empty");

// !(Swiftでいう強制アンラップ)を使う
String? str3 = null;
print(str3!.isEmpty);            // この場合 null なのでエラー

// 直前で Nullチェックを行う
String? str4 = null;
if (str4 != null) {
    print(str4);
}

List, Set について

公式ドキュメントで、ListやSet自体が Nullable の時、保持しているアイテムが Nullable の時などの書き方が、表でまとめられているので分かりやすいです。
また Nullable な List の中身を Non-null として扱う時は、 whereType() が便利そうです。
whereType() は、Collection から特定の型だけ抜き出すメソッド(値の順序も保持される)

var list = <int?>[1, 2, 3, 4, null];

print(list.runtimeType);        // List<int?>
list = list.whereType<int>().toList();
print(list.runtimeType);        // List<int>
print(list);                    // [1, 2, 3, 4]

Dartの型の理解しておきたいあれこれ(Null safety編)

map について

valueを取得する際、map の値が全て Non-Null だったとしても、無効な key を指定した場合 Null を返す可能性があるため Nullable になります。
そのため Non-Null の変数にそのまま値を代入できません。

int value = <String, int>{'one': 1}['one'];        // keyが有効でも、このままだとビルドエラー

対応としては3つありますが、公式ドキュメントにもある通り安全なアプローチは Null-aware演算子(??) を使うのが良さそうです。

  • Nullable にする
  • ! を使う
  • Null-aware 演算子(?? または ??=)を使う
int? value = <String, int>{'one': 1}['one'];        // Nullable にする
int value2 = <String, int>{'one': 1}['one']!;        // ! を使う
int value3 = <String, int>{'one': 1}['one'] ?? 0;    // Null-aware演算子(??)を使う
int value4 = <String, int>{'one': 1}['two'] ??= 0;    // Null-aware演算子(??=)を使う。無効な key なので value は 0 になる

移行について

記述の仕方がざっくり分かったところで、次は実際の移行についてですが…
「移行を一旦待つ」という選択肢も候補として考えておいた方が良さそうです。

公式ドキュメント 移行項目の、最初の記述にも以下の記載があります。

1. Wait to migrate
1. マイグレーションを待つ

We strongly recommend migrating code in order, with the leaves of the dependency graph being migrated first.
コードの移行は、依存関係のあるグラフの葉の部分から順番に行うことを強くお勧めします。

(中略)

Although you can migrate before your dependencies support null safety, you might have to change your code when your dependencies migrate.
依存関係がヌルの安全性をサポートする前に移行することは可能ですが、依存関係が移行するとコードを変更しなければならない場合があります。

Migrating to null safety

依存パッケージで Null Safety 対応・非対応が混在していても移行するのは可能だが、コード再修正が発生する可能性があるので、待った方がいい(強め)とのこと。

すこし記事が長くなってきたので、実際の移行に関してはまたの機会に書こうと思います。
ここでは最後に、移行前の確認作業について触れようかと思います。

移行の事前確認

まず前提として Null Safety は、以下のバージョンで利用可能です。それぞれバージョンを確認してみてください。

  • Flutter 2.0 以上
  • Dart 2.12 以上
$ flutter --version
Flutter 2.0.2 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 8962f6dc68 (7 days ago) • 2021-03-11 13:22:20 -0800
Engine • revision 5d8bf811b3
Tools • Dart 2.12.1

もしそれ未満ならバージョンアップすると Null Safety が有効になります。(バージョンアップ後に Null Safety を無効にして実行する方法もあります Testing or running mixed-version programs
ちなみに Flutter SDK の channel は、最新のものが必要などの理由がなければ、stableにしておくといいでしょう。

$ flutter channel
Flutter channels:
  master
* dev
  beta
  stable
$ flutter channel stable
$ flutter upgrade

Null Safety 対応済みのパッケージを調べる

以下のコマンドで、アプリが依存しているパッケージで Null Safety に対応済みかどうかを確認することができます。

$ flutter pub outdated --mode=null-safety

試しに個人開発しているもので実行してみました。


Resolvable の列でバージョン左に ✔︎ があるものは Null Safety 対応済みのバージョン ✖︎なら未対応です。分かりやすいですね。
私のだと2つがまだ対応されていないようです。

※ スクショを撮った時点ではまだでしたが google_mobile_ads は 0.12.0 で対応されました。
ちなみに google_mobile_ads は、今回発表された公式の広告表示のパッケージです。公式のものなので Null Safety 対応も早かったようですね。

image_galley_saver も Null Safety の PR が出ていたので、こちらも早いうちに対応されそうです。

その他

  • 詳細な Null Safety の公式ドキュメント
    • 以下の公式ドキュメント「Understanding null safety」の項は、 Dart での Null の扱われ方や、なぜそういう設計になったのか? などかなり細かい情報が記述されています。かなり長いですが、より深く理解したい方は読まれることをお勧めします。
  • DartPad(Web上でDartの動きを簡単に確認できるサイト)
    • Null Safety を有効にするかどうかの設定が追加されていました。
  • 公式動画
    • Flutter公式で、移行可能かの確認や移行作業の流れなどデモを交え説明しています。(日本語字幕付き🙏)

まとめ

いかがでしたでしょうか?
依存関係のパッケージによっては今すぐに移行することが難しいかもしれませんが、もし全て Null Safety に対応しているのであれば、移行対応して良いと思います。

実行速度の改善。バイナリサイズの減少が期待できますし、なにより Null Safety によって今までよりもアプリがクラッシュしづらい安全な開発ができるので、ぜひ一度試してみてください。

参考記事

Sound null safety
Dart の Sound null safety を試してみる
Dartの型の理解しておきたいあれこれ(Null safety編) nullable から non-nullable への変換




ギャップロを運営しているアップフロンティア株式会社では、一緒に働いてくれる仲間を随時、募集しています。 興味がある!一緒に働いてみたい!という方は下記よりご応募お待ちしております。
採用情報をみる