こんにちは、教育系エンジニアのひらまつ(@hiramatsuu)です。
書籍「ゼロからわかる Linuxコマンド200本ノック(技術評論社)」の著者。Udemy受講者8万人。
プログラミング教育をメインに活動するエンジニアとして、動画教材の作成・技術書の執筆・学習アプリの開発などを行なっています(詳しくはこちら)。
今回は、flutter開発における、パッケージを導入する際の流れについて深く理解していきたいと思います。
単に、パッケージを導入するだけなら、pub.devの「Installing」を見れば済みますが、そこに書かれている手順で、具体的に何をしているのか?を、理解せずに行なっている方も多いのではないかと思います。
パッケージの導入手順について深く理解しておくと、デバッグが楽になるなどメリットも大きいので、本記事で学んでおきましょう。
pub.devのパッケージの使用方法の基本
pub.devにあるパッケージをFlutterアプリに追加するための手順は、以下のようになる。1
- pubspec.yamlのdependencies下に、依存するパッケージを記入する
- プロジェクトの最上位のディレクトリから、flutter pub getを実行する(もしくはIDEの機能を使う。VS Codeなら「Get Packages」をクリックするなど。)
- パッケージを使用するファイルにおいて、パッケージをimportする
- パッケージがプラグイン(プラットフォーム固有のコードを含むパッケージ)ならアプリをフルリスタートする
ちなみに、1と2のプロセスは、「flutter pub add <パッケージ名>」というコマンドを実行する、という1つの手順にまとめられることもある。pub.devの「Installing」では、こちらの手順が記載されている。
例えば、本記事執筆時点のflutter_riverpodでは以下のようになっている。
この手順の裏側で起きていることをイメージできるようになるのが、本記事の目標。
flutter pub getは何をしているのか
上記の手順を理解する上で、一番重要なのが、flutter pub getが何をしているのかを理解すること。
flutter pub getがやっていることを、一言で言えば、「pubspec.yamlをもとにして、パッケージを使用できる状態にすること」と言える。
そのためにコマンドの裏側では、以下の作業を行なっている。
- pubspec.yamlに記載された、バージョン制約を元にして、transitive dependencyも含めた、すべての依存パッケージ(dependency)の具体的なバージョンを決定する
- 決定した具体的なバージョンの依存パッケージを、pubspec.lockにまとめてバージョンを固定し、システムキャッシュに保存する
- パッケージの名前と場所(URI)のマップである、package_config.jsonファイルを作成して、パッケージが保存されているシステムキャッシュにアクセスできるようにする
① pubspec.yamlをもとに、すべての依存パッケージ(dependency)のバージョンを決定
pubspec.yamlでは、依存するパッケージのバージョンを、以下のように範囲で指定する。
dependencies:
flutter_riverpod: ^2.5.1
「^2.5.1」は、「バージョン2.5.1以上3.0.0未満」の意味。このキャレット記法については「pubspec.yamlの「^2.4.0」はどういう意味か?【caret syntaxについて解説】」を参照。
このように、バージョンを範囲で指定することで、バージョンロックを起こすことなく、他の開発者が作った様々なパッケージに依存できるようになる。より詳しくは、「なぜpubspec.yamlとpubspec.lockの2つが必要なのか?【Dart・Flutter】」を参照。
とはいえ、実際にパッケージを使う時には、何か1つの具体的なバージョンに依存する必要がある。そのため、flutter pub getを実行して、pubspec.yamlに記載されたバージョン制約(version constraint)すべてを満たす、具体的なバージョンを見つけ出す(バージョン制約の解決)。
② 具体的なバージョンをpubspec.lockに記載して固定し、システムキャッシュに保存
バージョン制約の解決の結果、1つに定まった依存パッケージのバージョンは、pubspec.lock(通称lockfile)に記載されて、固定される。バージョンが固定されることで、別の環境でもアプリのパッケージへの依存関係を、完全に再現することが可能になる。より詳しくは、「lockfileはコミットすべきか?【Flutter・Dart】」を参照。
lockfileは、あなたのパッケージが使っているdependency(immediateとtransitive両方)の特定のバージョンを記載しており、pubspec.yamlの隣に作成される。
そして、lockfileに記載されたバージョンの依存パッケージをダウンロードして、ローカルのシステムキャッシュに保存する。
一度、システムキャッシュにダウンロードされたGitリポジトリやpub.devなどの外部のパッケージは、その後はローカルへのアクセスで使えるようになる。ちなみに、システムキャッシュのデフォルトのディレクトリは、macとLinuxなら「~/.pub-cache」。環境変数「PUB_CACHE」の値をいじれば変更可能。
③ パッケージの名前と場所(URI)のマップである、package_config.jsonファイルを作成
取得したパッケージに適宜アクセスする上では、パッケージの名前と場所を対応させたマップがあると便利。それが、lockfileを元にして生成される、package_config.jsonファイル。このファイルは、.dart_toolディレクトリ内に作成される。
パッケージを使用するファイル内で、「import package:<パッケージ名>」というように指定すると、Dartランタイムが、package_config.jsonファイルを使ってパッケージの場所を探し出して、パッケージにアクセスする。2
flutter pub getを実行した後に、importディレクティブを使用できるようになるのはこのため。
ちなみに、package_config.jsonファイルをバージョン管理する必要はない。より詳しくは、「lockfileはコミットすべきか?【Flutter・Dart】」を参照。
flutter pub getとflutter pub addの違い
「flutter pub add <パッケージ名>」を実行すると、引数に指定されたパッケージが以下のような形式でpubspec.yamlに記載された上で、flutter pub getが実行される。
dependencies:
パッケージ名: ^最新バージョン
つまり、以下のように言える。
flutter pub add <パッケージ名> = pubspec.yamlへのパッケージの記入 + flutter pub get
余談:flutter pub remove
flutter pub addと合わせて理解したいものとして、flutter pub removeがある。
「flutter pub remove <パッケージ名>」と実行すると、引数に指定されたパッケージが、pubspec.yamlから削除された上で、flutter pub getが実行される。
依存パッケージが削除されて、flutter pub getが実行されると、不要になったtransitive dependencyも含めて削除され、その後はimportできなくなる。この削除のプロセスにおいては、他のパッケージのバージョンは変更されない。
つまり、以下のように言える。
flutter pub add <パッケージ名> = pubspec.yamlからのパッケージの削除 + flutter pub get
flutter pub getとflutter pub upgradeの違い
「flutter pub get」と違いがわかりづらいものとして、「flutter pub upgrade」がある。
「flutter pub get」と「flutter pub upgrade」の違いを一言で言えば、
- 既存のlockfileになるべく従うのが「flutter pub get」
- 既存のlockfileを無視して、lockfileを1から作り直すのが「flutter pub upgrade」
- lockfileがまだない場合は、これら2つのコマンドはまったく同じ動作になる
となる。
lockfileがすでにある場合、flutter pub getでは、lockfileにすでに記載されているパッケージのバージョンをなるべく維持して、新しい依存パッケージのバージョン制約の解決を行う。既存のlockfileに記載されたバージョンを、解決の過程で絶対に変更したくない場合は、–enforce-lockfileオプションをつけて実行する。3
一方、flutter pub upgradeでは、lockfileがすでにあろうとなかろうと、lockfileを1から作り直し、最新のバージョンのパッケージで解決を行う。つまり、すでに使われていた依存パッケージのバージョンが、更新される可能性がある。
flutter pub upgradeは、引数なしで実行すると、すべてのdependencyを最新バージョンにするが、引数でパッケージを指定することも可能。その場合、引数に指定したパッケージだけを、最新バージョンにすることができる。引数に指定されたパッケージ以外は、できるだけバージョンが保たれるようになるが、引数に指定したパッケージによってはその限りではない。
また、flutter pub upgradeコマンドは、常にpubspec.yamlに記載された、すべてのパッケージを最新バージョンにアップグレードするわけではない。バージョン制約のコンフリクトがpubspec内で起こる可能性があるため。最新バージョンでないパッケージを特定するには、dart pub outdatedコマンドが使える。4
flutter pub upgradeを実行するたびに、実行前と同じように使えるかを確かめるための、テストを行うことが推奨されている。5パッケージのバージョンは、なるべく最新のものを使うことが推奨されている6ため、定期的にflutter pub upgradeと一通りのテストを行うようにする。
余談:flutter upgrade
よく似たコマンドに、「flutter upgrade」があるが、これはFlutter SDKのバージョンを上げるコマンド。7
その他補足情報
パッケージがプラグインの場合
新たに導入したパッケージがプラグイン(Dartコード以外も含む)なら、Hot ReloadやHot Restartでは更新されない。Hot ReloadやHot Restartでは、Dartコードの変更しか反映しないため。パッケージの使用時に、MissingPluginExceptionが出たら、フルリスタート(full restart)するようにしよう。8
オフラインでのflutter pub getの実行
オフラインでもflutter pub getを実行することは可能。pubはキャッシュにパッケージをダウンロードするため、過去にダウンロードしていれば、使えることも多い。
しかし、デフォルトではオンラインにアクセスしようとするので、オフラインで行いたいなら–offlineオプションを指定する。オフラインモードでは、pubはローカルのキャッシュだけをチェックして、利用可能なバージョン内で解決しようとする。
パッケージのimportについて
パッケージ内のライブラリをインポートするには、「package:」プリフィックス(prefix:接頭辞)を使う。
import 'package:js/js.dart' as js;
import 'package:intl/intl.dart';
Dartランタイムは、「package:」プリフィックスの後のすべてを読み取り、あなたのアプリのpackage_config.jsonファイル内から探し出す。
このインポートの書き方は、パッケージ内の他の場所からのインポートにも使える。例えば、transmogrifyパッケージが以下のような構成で配置されているとする。
transmogrify/
lib/
transmogrify.dart
parser.dart
test/
parser/
parser_test.dart
parser_test.dartファイルから、parser.dartファイルは以下のようにインポートできる。
import 'package:transmogrify/parser.dart';
その他の情報
Flutter開発についての、他の記事はこちらを参照。
参考文献
脚注
- https://docs.flutter.dev/packages-and-plugins/using-packages ↩︎
- https://dart.dev/guides/packages#importing-libraries-from-packages ↩︎
- https://dart.dev/tools/pub/dependencies#verify-the-integrity-of-downloaded-packages ↩︎
- https://docs.flutter.dev/release/upgrade#upgrading-packages ↩︎
- https://dart.dev/tools/pub/dependencies#test-whenever-you-update-package-dependencies ↩︎
- https://dart.dev/tools/pub/dependencies#best-practices ↩︎
- https://docs.flutter.dev/release/upgrade#upgrading-the-flutter-sdk ↩︎
- https://docs.flutter.dev/tools/hot-reload#how-to-perform-a-hot-reload ↩︎