person t-kobayashi

自社アプリをflutter 1.22から2.0にアップグレードする手順

calendar_today 2021年05月13日 update 2021年05月13日
Facebook Twitter LINE はてなブックマーク Pocket

Flutter 1.22.5(Dart 2.10.4)でリリースしている自社アプリを、先日リリースされたFlutter 2.0にアップグレードしてNull Safeコードのするまでの手順をまとめます。

flutter --version

結果

Flutter 1.22.5 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 7891006299 (4 months ago) • 2020-12-10 11:54:40 -0800
Engine • revision ae90085a84
Tools • Dart 2.10.4

Flutterのアップグレード

まず、以下のコマンドで、現在使っているflutterがstableチャンネルなのか確認します。

flutter channel

結果

Flutter channels:
  master
  dev
  beta
* stable

次に、公式サイトの指示に従って、FlutterをStableチャンネルの最新版にアップグレードします。

flutter upgrade

結果

・・・ダウンロードログ・・・

Flutter 2.0.3 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 4d7946a68d (10 days ago) • 2021-03-18 17:24:33 -0700
Engine • revision 3459eb2436
Tools • Dart 2.12.2

・・・flutter doctorの結果・・・

記事を書いている時点での、stableチャンネルの最新版は上記の通りです。幸いflutter doctorの結果はすべて合格でした。古いバージョンからアップグレードした場合は、なんらかのエラーが発生する可能性があるので、エラーが発生した場合は、一つずつ解決してください。

動作確認

Flutter 2.0へのアップグレードが完了したら、エミュレーターでアプリが正常に動作するか確認します。

flutter run

結果

アプリは正常に動作することを確認できましたが、ログに以下の文言が表示されます。

・・・その他のログ・・・

Running with unsound null safety
For more information see https://dart.dev/null-safety/unsound-null-safety

unsound null safetyの状態で動作しているという内容のメッセージです。参考用サイトにある説明を読むと、「Unsound Null Safety」とは、Null SafeコードとNull Safeではないコードが混在している状態で実行されている場合に、使われる表現であるということがわかります。つまり、Flutter 2.0はNull Safeだが、アプリ自体にはNull Safeではないコードが残っているため、Unsound Null Safetyな状態でアプリが実行されているということになります。

この状態でもアプリは動きますが、今後の開発をスムーズに行えるようにするためにも、unsound null safetyの状態からsound null safetyの状態に移行していきたいと思います。Dartチームが提供してる移行ツールを利用します。

まず、ログにある参考用ページから、移行ガイドのページへ移動して、説明に従って移行していきます。

移行

以下のコマンドで、Dartチームが提供してる移行ツールを実行します。しかし、上記の状態で実行してもエラーが発生する可能性が高いです。

dart migrate

移行をスムーズに行うために、末端のパッケージがNull Safeに対応するまで、それらのパッケージに依存するパッケージは移行を待つことが推奨されています。つまり、まずはアプリで利用しているすべてのパッケージの移行が完了するまでは、移行しないほうがいいということのようです。

そこで、アプリが依存しているパッケージの状態を確認するために以下のコマンドを実行します。

dart pub outdated --mode=null-safety

結果

現時点でNull Safeになっていないパッケージの一覧が表示されます。 Resolvable列に表示されているバージョンに「✗」がついているパッケージがある場合は、 そのパッケージの利用をやめるか、そのパッケージがNull Safeに対応するのを待ってから 移行するようにしましょう。

Showing dependencies that are currently not opted in to null-safety.
[✗] indicates versions without null safety support.
[✓] indicates versions opting in to null safety.

Package Name        Current   Upgradable  Resolvable  Latest  

direct dependencies:
after_layout        ✗1.0.7+2  ✓1.1.0      ✓1.1.0      ✓1.1.0  
cupertino_icons     ✗0.1.3    ✗0.1.3      ✓1.0.2      ✓1.0.2  
json_serializable   ✗3.5.0    ✗3.5.1      ✓4.1.0      ✓4.1.0  
just_audio          ✗0.2.2    ✗0.2.2      ✓0.7.2      ✓0.7.2  
package_info        ✗0.4.3    ✗0.4.3+4    ✓2.0.0      ✓2.0.0  
path_provider       ✗1.6.18   ✗1.6.28     ✓2.0.1      ✓2.0.1  
provider            ✗4.1.3    ✗4.3.3      ✓5.0.0      ✓5.0.0  
shared_preferences  ✗0.5.7+3  ✗0.5.12+4   ✓2.0.5      ✓2.0.5  
url_launcher        ✗5.5.0    ✗5.7.10     ✓6.0.3      ✓6.0.3  

7 upgradable dependencies are locked (in pubspec.lock) to older versions.
To update these dependencies, use `dart pub upgrade`.

8  dependencies are constrained to versions that are older than a resolvable version.
To update these dependencies, edit pubspec.yaml, or run `dart pub upgrade --null-safety`.

上記のログを確認すると7件のアップグレード可能なパッケージが古いバージョンにロックされているため、dart pub upgradeを使ってアップデートできる旨が記載されています。実行してみると、いくつか互換性がないパッケージが存在している旨のログが表示されます。

dart pub upgrade

結果

・・・更新ログ・・・
36 packages have newer versions incompatible with dependency constraints.
Try `dart pub outdated` for more information.

詳細を確認してみます。

dart pub outdated

結果

Showing outdated packages.
[*] indicates versions that are not the latest available.

Package Name                           Current    Upgradable  Resolvable  Latest  

direct dependencies:                  
cupertino_icons                        *0.1.3     *0.1.3      1.0.2       1.0.2   
json_serializable                      *3.5.1     *3.5.1      4.1.0       4.1.0   
just_audio                             *0.2.2     *0.2.2      0.7.2       0.7.2   
package_info                           *0.4.3+4   *0.4.3+4    2.0.0       2.0.0   
path_provider                          *1.6.28    *1.6.28     2.0.1       2.0.1   
provider                               *4.3.3     *4.3.3      5.0.0       5.0.0   
shared_preferences                     *0.5.12+4  *0.5.12+4   2.0.5       2.0.5   
url_launcher                           *5.7.10    *5.7.10     6.0.3       6.0.3   

transitive dependencies:              
・・・依存パッケージの一覧・・・

34  dependencies are constrained to versions that are older than a resolvable version.
To update these dependencies, edit pubspec.yaml, or run `dart pub upgrade --major-versions`.

この時点で、アプリの方は正常にコンパイルし動作するように見受けられるので、上記のログの指示に従って以下のコマンドを実行してみます。

dart pub upgrade --major-versions

しかし、いっぺんにアップグレードしようとすると、互換性が無いコードがありコンパイルエラーが検知されてしまうので、エラーがでないパッケージから順番にpubspec.yamlファイルを編集していくことにします。※gitの機能を使って、pubspec.yamlpubspec.lockを上記のコマンド実行前に戻します。

アップグレード

pubspec.yamlの依存パッケージのバージョンを一つずつNull Safeバージョンに変えては、flutter pub getを実行してエラーが発生しないか確認していきます。私のアプリは8つほどのパッケージだけでしたが、依存しているパッケージが多い場合は、結構たいへんな作業になるかもしれません。

以下の5つのパッケージは問題なくバージョン更新ができました。

cupertino_icons
json_serializable
package_info
url_launcher
provider

しかし、以下の3つは互いに依存していて、少々手こずりました。

path_provider
just_audio
shared_preferences

just_audioがpath_providerの2.0.0に依存していたため、path_providerのバージョンを^2.0.0に更新しました。そうすると、pub getによるエラーがでなくなりました。

just_audioパッケージは0.2.2から0.7.2へと大幅なアップデートであり、かなり実装方法が変更されていたため、ソースコードを書き換える必要がありました。そこをなんとかクリアして、ある程度動作確認ができたところで、shared_preferencesパッケージをNull Safeバージョンに変更することで、パッケージをすべてNull Safeな状態にすることができました。(長かった!just_audioパッケージの更新作業については、別記事にしようと思います。)

今一度、dart pub outdated --mode=null-safetyを実行してみます。

結果

Showing dependencies that are currently not opted in to null-safety.
[✗] indicates versions without null safety support.
[✓] indicates versions opting in to null safety.

Package Name  Current  Upgradable  Resolvable  Latest  

direct dependencies: all support null safety.
You are already using the newest resolvable versions listed in the 'Resolvable' column.
Newer versions, listed in 'Latest', may not be mutually compatible.

dart migrate

そして、ついにアプリ自体をNull Safeにする段階になります。下記のコマンドを実行します。

dart migrate

結果

Migrating /Users/tkobayashi/projects/flag_app

See https://dart.dev/go/null-safety-migration for a migration guide.

Analyzing project...
[-----------------------------------------\]Bad state: Error: package has unmigrated dependencies.

Before migrating your package, we recommend ensuring that every library it
imports (either directly or indirectly) has been migrated to null safety, so
that you will be able to run your unit tests in sound null checking mode.  You
are currently importing the following non-null-safe libraries:

  package:flag_app/models/flag.dart

Please upgrade the packages containing these libraries to null safe versions
before continuing.  To see what null safe package versions are available, run
the following command: `dart pub outdated --mode=null-safety`.

To skip this check and try to migrate anyway, re-run with the flag
`--skip-import-check`.

依存しているパッケージはすべてNull Safetyに対応しているという結果がでているのに、なぜかプロジェクト内のファイルがNull Safety非対応のパッケージとして認識されてしまい、ツールの分析が途中で止まってしまいました。下記のコマンドでビルドをリセットしてから、再度ツールを実行したら、分析が完了しました。

flutter clean
flutter pub get
dart migrate

結果

Migrating /Users/tkobayashi/projects/flag_app

See https://dart.dev/go/null-safety-migration for a migration guide.

Analyzing project...
[-----------------------------------------\]No analysis issues found.

Generating migration suggestions...
[------------------------------------------]

Compiling instrumentation information...
[------------------------------------------]

View the migration suggestions by visiting:

  http://127.0.0.1:63621/Users/tkobayashi/projects/flag_app?authToken=xxxxxxxxxxxxxx

Use this interactive web view to review, improve, or apply the results.
When finished with the preview, hit ctrl-c to terminate this process.

If you make edits outside of the web view (in your IDE), use the 'Rerun from
sources' action.

ログに表示されているリンクをブラウザで開いて、各ファイルの右側に変更提案数が表示されていることが確認できます。ざっと足してみると350件ほどの変更がありました。

一通り目を通したところ、ほとんどがnullableへの変更(?記号の追加)とnullチェックの追加(!記号の追加)でした。1つ1つ判断するのは時間がかかりすぎるので、これはそのまま適用することにしました。

laterequiredキーワードの追加に関する提案も、とりあえずそのまま適用して問題なさそうだったので、そのまま受け入れます。

気になったところとして、データ型が指定されていない場合に、dynamic型を明示的に指定するように提案されている箇所があったので、データ型が特定できる箇所は、明示的に指定しました。

StatelessWidgetに渡していたコールバック関数については、as void Function()?というようにキャストされているのも気になりましたが、これも問題なさそうだったので、そのまま適用することにしました。

その他、戻り値がint型となっている関数に対して、boolean型を戻している箇所が見つかったので、型を揃えるなどの修正を入れました。

ソースコードに変更を加える度に、こまめに「RERUN FROM SOURCES」を実行しながら、一通り修正を終えた後、「APPLY MIGRATION」をクリックします。

結果

Applying migration suggestions to disk...
Migrated 28 files.

上記のメッセージがログに表示され、ツールが終了します。gitでトラッキングしているファイルで変更があったのは22ファイルだったので、6ファイルほどリポジトリ外のファイルが変更されたようですが、どのファイルかはわかりませんでした。

上記の修正後、以下のようなエラーが3つ見つかりました。

The method '+' can't be unconditionally invoked because the receiver can be 'null'.
Try making the call conditional (using '?.') or adding a null check to the target ('!').
The method '-' can't be unconditionally invoked because the receiver can be 'null'.
Try making the call conditional (using '?.') or adding a null check to the target ('!').
Dead code.
Try removing the code, or fixing the code before it so that it can be reached.

1つ目と2つ目のエラーは以下のように修正しました。

修正前

_currentQuestionIndex++;
・・・
_currentQuestionIndex--;

修正後

if (_currentQuestionIndex != null) {
    _currentQuestionIndex = _currentQuestionIndex! + 1;
}
・・・
if (_currentQuestionIndex != null) {
    _currentQuestionIndex = _currentQuestionIndex! - 1;
}

3つ目のエラーについては、不要なコードを削除することで修正しました。

最終確認

以上でNull Safetyへの移行が完了です。下記のコードでアプリを実行するとSound Null Safetyに対応しているとメッセージが表示されるようになります。

flutter run

結果

💪 Running with sound null safety 💪

まとめ

アプリをFlutter 2.0のNull Safetyに移行する手順をまとめると以下のようになると思います。

  • アプリが依存しているパッケージがすべてNull Safetyに対応するのを待つ。
  • 依存しているパッケージがすべてNull Safetyに対応したら、各パッケージをアップグレードする。
  • 各パッケージのアップグレードが完了したら、dart migrateなどを使って、アプリのソースコードをNull Safetyに対応するように変更する。

アップグレードしたり、ソースコードを書き換える際にエラーが見つかることがありますが、通常のバグを修正するのと同様に根気強く修正していくしかなさそうです。

参考サイト

https://dart.dev/null-safety/migration-guide

https://dart.dev/null-safety/understanding-null-safety

https://dart.dev/null-safety

https://dart.dev/null-safety/tour

Let's migrate this app to null safety - YouTube

How to migrate Dart packages to null safety - YouTube

関連記事

Flutterの記事一覧を見る

Flutterの質問

soichiro1210 が1年前に投稿

質問日時 2023年07月31日

a-sato が3年前に投稿

質問日時 2021年07月01日

a-sato が3年前に投稿

質問日時 2021年06月30日

takumi が3年前に投稿

質問日時 2021年05月20日

a-sato が3年前に投稿

質問日時 2021年05月14日

Flutterの質問一覧を見る
search