letter_openerの設定

ローカル開発環境でメール受信

ローカル開発環境でのメール受信はletter_openerを使います。プロジェクトの初期設定で必須になると思いますが、忘れがちなのでメモします。

gemの追加

letter_opener_webというgemがあるので追加します。Gemfileを編集します。

1
2
3
4
group :development do
gem "letter_opener_web"
end
(その他のgemは省略しています)

bundle installします

1
$ docker compose run --rm web ./bin/bundle install

インストールができました。GemfileとGemfile.lockをcommitしておきます。

routesの設定

http://localhost:3000/letter_openerでアクセスできるようconfig/routes.rbに以下の行を追加します

1
2
3
Rails.application.routes.draw do
mount LetterOpenerWeb::Engine, at: "/letter_opener" if Rails.env.development?
end

ActionMailerの設定

最後にActionMailerの設定をします。config/environments/development.rbに以下の2行を追加します。

1
2
3
config.action_mailer.default_url_options = { host: "localhost:3000"}

config.action_mailer.delivery_method = :letter_opener_web

config.action_mailerが記載されている付近に追加します。

1行目の設定はこの設定がないと、メールにURLを記載する場合のドメインがわからずエラーになってしまいます。

2行目の設定は、letter_opener_webでメールを送るという設定で、この設定がないとhttp://localhost:3000/letter_openerにアクセスしてもメールが見れません(メール送信のログが表示されて送られているように見えるがどこにも届かない)

まとめ

Railsの開発環境構築はいろいろ設定することが多いですが、設定する機会が少なく忘れてしまいます。出だしで躓かないようメモしておきました。

annotateの設定

テーブルのスキーマがわからなくなるときの対応

Railsで開発を行なっているときに、まだ馴染みのないモデルに遭遇すると、どういったカラムがあるかなど、スキーマがわからずに困ることがよくあります。

db/schema.rbを確認すればわかるのですが、モデルのファイルを見ながらdb/schema.rbを見るのは不便です。

何かしらの方法でテーブルのスキーマを確認しつつモデル内でロジックを実装したい、その場合に利用すると便利なgemがannotateです。

gemの追加

annotateというgemがあるので追加します。Gemfileを編集します。

1
2
3
4
group :development do
gem "annotate"
end
(その他のgemは省略しています)

bundle installします

1
2
3
4
$ docker compose run --rm web ./bin/bundle install
...
Fetching annotate 3.2.0
Installing annotate 3.2.0

インストールができました。GemfileとGemfile.lockをcommitしておきます。

annotateの設定ファイルの作成

annotateの設定ファイルを生成するgeneratorがあるので実行します。

1
2
$ docker compose run --rm web ./bin/rails g annotate:install
create lib/tasks/auto_annotate_models.rake

annotateのカスタマイズ

カスタマイズに関しては、先程作成したタスクのAnnotate.set_defaultsメソッドの引数を編集することで行います。

わたしが変更した内容を記載します。

position_in_xxxx

xxxxにはclassfactoryなどが入ります。それぞれのファイルのどの部分にannotateによるドキュメントを追加するか、という設定になります。
コードの後ろに追加した方が見通しがいいかなと思い、afterに変更しています(デフォルトはbefore

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
       'routes'                      => 'false',
'models' => 'true',
- 'position_in_routes' => 'before',
- 'position_in_class' => 'before',
- 'position_in_test' => 'before',
- 'position_in_fixture' => 'before',
- 'position_in_factory' => 'before',
- 'position_in_serializer' => 'before',
+ 'position_in_routes' => 'after',
+ 'position_in_class' => 'after',
+ 'position_in_test' => 'after',
+ 'position_in_fixture' => 'after',
+ 'position_in_factory' => 'after',
+ 'position_in_serializer' => 'after',
'show_foreign_keys' => 'true',

with_comment

trueだとカラム名の横にそのカラムに設定されているコメントが追加されます。ですが、カラムを削除する際に、annotateが正しく動作しなかったので、falseに設定します。

1
2
3
4
5
6
       'wrapper_open'                => nil,
'wrapper_close' => nil,
- 'with_comment' => 'true'
+ 'with_comment' => 'false'
)
end

動作確認

ここで正しく動作するか確認します。

1
2
3
$ docker compose run --rm web ./bin/bundle exec annotate

Annotated (ファイル数): ファイル名, ....

このように表示されればOKです。ファイル数にはコメントが追加されたファイル数、ファイル名にはコメントが追加されたファイルのパスが表示されます。

まとめ

annotateはいつも使っている便利なgemです。慣れている人なら馴染み深いものではありますが、始めたばかりの人にとっては知らないけど知っているととても便利なものという感じかと思います。ぜひ使ってみてください。

参考URL

RSpecの初期設定

RSpecの初期設定

RailsのプロジェクトでテストフレームワークはRSpecを使っています。

プロジェクト開始時に設定するのですが、あまり設定することもないので、毎回調べて時間がかかってしまいます。なので、備忘録として手順を記載します。

gemの追加

まずgemを追加します。FactoryBotも利用するのでついでに追加します。

1
2
3
4
group :development, :test do
gem "rspec-rails"
gem "factory_bot_rails"
end

bundle installします

1
$ docker compose run --rm web ./bin/bundle install

インストールができました。GemfileとGemfile.lockをcommitしておきます。

RSpecの設定

RSpecの設定ファイル等を生成するgeneratorがあるので実行します。

1
2
3
4
5
$ docker compose run --rm web ./bin/rails g rspec:install
create .rspec
create spec
create spec/spec_helper.rb
create spec/rails_helper.rb

出力のカスタマイズ

出力をドキュメント形式にするために、.rspecに以下の行を追加します。

1
2
3
$ vi .rspec
--require spec_helper
--format documentation

(2行目を追加)

動作確認

ここで正しく動作するか確認します。

1
2
3
4
5
$ docker compose run --rm web bundle exec rspec
No examples found.

Finished in 0.00087 seconds (files took 0.171 seconds to load)
0 examples, 0 failures

このように表示されればOKです。

binstubを作成する

毎回bundle exec rspecとタイプするのは面倒なので、binstubを作成して./bin/rspecとシェルの補完機能も使いながらタイプ数を減らしたいと思います。

1
$ docker compose run --rm web bundle binstubs rspec-core

bin/rspecというファイルが作成されました。

今度はbinstubを利用して実行してみます。

1
2
3
4
5
$ docker compose run --rm web ./bin/rspec 
No examples found.

Finished in 0.0012 seconds (files took 0.15613 seconds to load)
0 examples, 0 failures

同じように実行できました。

RSpecを利用するよう設定

generatorで自動生成するファイルをRSpecのファイルにしたいので、利用するテストフレームワークがRSpecだということをRailsに伝えます。

1
2
3
4
5
6
7
8
9
$ vi config/application.rb
...(省略)
config.generators do |g|
g.test_framework :rspec,
view_specs: false,
helper_specs: false,
routing_specs: false
end
...(省略)

上記のコードを追加することで、generatorがRSpecのファイルを生成してくれます。

まとめ

今後もテストフレームワークはRSpecを利用していくと思うので、導入手順をまとめました。

参考図書にリンクを記載したeveryday-railsですが、著者の伊藤さんが日本語版独自の改訂を続けてくれています。一度購入するとその後の改訂版は無料で受け取ることができます。とてもためになる書籍なので読んでみることを強くおすすめします。

参考図書

docker compose buildでpermission deniedが出た時の対応

docker compose buildでエラー発生

久しぶりにDockerfileを編集したので、イメージをビルドしようと思い、以下のコマンドを実行しました

1
$ docker compose build web

(docker-compose.yml上でwebというサービスが設定されている前提です)

何もなければこのまま終わるはずですが、エラーが発生して実行できてないようです。

1
2
$ docker compose build web
open /User/user/.docker/buildx/current: permission denied

対処法

いろいろ調べたのですが、手軽な対処法がありました。環境変数を設定し、buildxを利用しないようにするというものです。docker compose buildを実行するターミナルで以下のコマンドを実行します。

1
$ export DOCKER_BUILDKIT=0

docker compose buildを試します。

1
2
$ docker compose build web
buildが実行できるログ

まとめ

buildxはDocker Desktopを利用していると自動的に有効になるようです。原因がわからない場合は一旦buildxを無効にするのが簡単な対応方法です。

参考図書

host.docker.internalとは?

ローカル開発環境かどうかの判定

普段Railsを使って開発していると、環境の判定はRails.envで行うことができる。ローカル開発環境ではもちろん

1
2
3
irb(main):001:0> Rails.env
=> "development"
irb(main):002:0>

となる。

また、Rails.env == "development"としなくても

1
2
3
4
irb(main):002:0> Rails.env.development?
=> true
irb(main):003:0> Rails.env.production?
=> false

このように判定してくれるメソッドがある。

ところが、そうでないWebアプリケーションフレームワークもあるようで(ほんとにその機能がないかは定かではない)、ローカル開発環境かどうかの判定に

1
if 'host.docker.internal' in os.environ['S3_URL']:

という条件分岐を利用しているコードに出会った。

察しのいい方はわかると思いますが、S3にアクセスする際に、ローカルで稼働しているMinIOコンテナにアクセスすべきか、S3にアクセスすべきかの条件分岐のif文です。

この条件文が正しく動作せず苦労しました。

host.docker.internalについて

host.docker.internalというドメインが初見だったので調べてみると、Docker Desktop for xxで利用できる、コンテナから参照する場合のhostを指すドメインだということです。わたしの環境はDocker Desktop for Macを利用しているので利用できるはずです。

参考: コンテナからホスト上のサービスに対して接続したい

なるほど、であれば、コンテナからアクセスする際にhost.docker.internal:9000にアクセスすれば、コンテナからMinIOへアクセスできるはずですね。

host.docker.internalを利用する

先程のコードのようにS3_URLをhttp://host.docker.internal:9000とし、アプリケーションからファイルをPUTしてみました。

すると、正しくファイルをPUTできました。

host.docker.internalにアクセスできない場合

いろいろ調べてみるとhost.docker.internalというドメインにアクセスできないケースがあるようです。

その場合の対処法としては、docker-compose.ymlにextra_hostsの設定を行います。

extra_hosts

extra_hostsの説明は以下にありました。

https://docs.docker.jp/compose/compose-file/#extra-hosts

/etc/hostsに対してホストのマッピングを追加することができるようです。

対処法の例としては、hosts.docker.internalにアクセスするコンテナに対して

1
2
extra_hosts:
- "host.docker.internal:host-gateway"

を追加してくださいとのことです。host-gatewayはホストのアドレスになるようですね。

/etc/hostsの確認

extra_hostsを設定した状態でコンテナの/etc/hostsファイルを確認します

1
2
3
4
5
6
7
8
9
10
$ docker compose run --rm app /bin/bash
bash-4.2# cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
192.168.65.2 host.docker.internal
172.18.0.6 af51d5a15783

host.docker.internalのレコードが追加されていました。

まとめ

コンテナからホストにアクセスする際にはhost.docker.internalというドメインでアクセスできる。

host.docker.internalでアクセスできない場合は、extra_hostsを利用して明示的に/etc/hostsファイルにレコードを追加する。

参考図書

committee-railsを使ってスキーマ駆動開発を始める

フロントエンドエンジニアとの連携

わたしはRailsでバックエンドAPIを実装しているのですが、開発当初、フロントエンドエンジニアから以下のような提案がありました。

「OpenAPIでスキーマを記述してお互いコンセンサスを取ってから実装しませんか?」

スキーマ駆動開発のことを言っているんだなと思い、

「いいと思うよ、それでいこう」

と答えました。答えたものの、やり方がわかっていないので、調べたことを記載していきます。

committee-railsを使う

“Rails スキーマ駆動開発”と調べると、いろんな例が出てきます。大体の事例がcommittee-railsを使っているので、これが良さそうです。早速インストールします。(テストのときにしか使わないのでtest groupに追加します)

1
2
3
4
5
6
7
8
9
10
$ git diff Gemfile
diff --git a/api/Gemfile b/api/Gemfile
--- a/api/Gemfile
+++ b/api/Gemfile
@@ -76,6 +76,7 @@ group :test do
gem "capybara"
gem "selenium-webdriver"
gem "webdrivers"
+ gem "committee-rails"
end
1
2
3
4
5
6
7
8
9
10
11
$ docker-compose run --rm api ./bin/bundle install
Using devise-i18n 1.10.2
Using devise_token_auth 1.2.1
Installing openapi_parser 0.15.0
Installing json_schema 0.21.0
Fetching committee 4.4.0
Installing committee 4.4.0
Fetching committee-rails 0.6.1
Installing committee-rails 0.6.1
Bundle complete! 29 Gemfile dependencies, 114 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

インストールが完了しました。

committee-railsの設定

公式のGitHubにも記載がありますが、spec/rails_helper.rbにcommittee-rails用の設定を追記します。
この設定を行うことで、assert_response_schema_confirmメソッドなどが利用できるようになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ git diff spec/rails_helper.rb
diff --git a/api/spec/rails_helper.rb b/api/spec/rails_helper.rb
--- a/api/spec/rails_helper.rb
+++ b/api/spec/rails_helper.rb
@@ -60,4 +60,12 @@ RSpec.configure do |config|
config.filter_rails_from_backtrace!
# arbitrary gems may also be filtered via:
# config.filter_gems_from_backtrace("gem name")
+
+ config.include Committee::Rails::Test::Methods
+ config.add_setting :committee_options
+ config.committee_options = {
+ schema_path: Rails.root.join("open_api.yaml").to_s,
+ }

specの追加

committee-railsが正しく動作するかどうか確認するための簡単なspecを用意します。devise_token_authでメールアドレス登録のAPIをテストします。(OpenAPIのスキーマは記載済です)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
require "rails_helper"

RSpec.describe "API::Auth::RegistrationsController", type: :request do
describe "POST /api/v1/auth" do
it "returns 200" do
post "/api/v1/auth", params: { email: "example@example.com" }
assert_response_schema_confirm(200)
end

it "returns 422" do
post "/api/v1/auth", params: { password: "password" }
assert_response_schema_confirm(422)
end
end
end

specの実行

では実行します。

1
2
3
4
5
6
7
8
9
10
$ docker-compose run --rm api ./bin/rspec spec/api/v1/auth/registrations_controller_spec.rb

(省略)

Failures:
1) API::Auth::RegistrationsController POST /api/v1/auth return 200
Failure/Error: <p><%= link_to t('.confirm_account_link'), confirmation_url(@resource, {confirmation_token: @token, config: message['client-config'].to_s, redirect_url: message['redirect-url']}).html_safe %></p>

ActionView::Template::Error:
Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true

エラーが出てしまいました。どうやらメールのドメインが未設定だったようで、以下のように設定しました。
config/environments/test.rb

1
+  config.action_mailer.default_url_options = { host: 'localhost:3000'}

再度実行します。

1
2
3
4
5
6
7
8
$ docker-compose run --rm api ./bin/rspec spec/api/v1/auth/registrations_controller_spec.rb

(省略)

API::Auth::RegistrationsController
POST /api/v1/auth
[DEPRECATION] Committee: please set query_hash_key = rack.request.query_hash because we'll change default value in next major version.
[DEPRECATION] Committee: please set parse_response_by_content_type = false because we'll change default value in next major version.

正常に実行はできましたが、DEPRECATIONの警告が出ています。
公式サイトをよく見てみると、https://github.com/willnet/committee-rails#normal-usage にちゃんと書かれています

1
2
3
4
5
6
7
def committee_options
@committee_options ||= {
schema_path: Rails.root.join('schema', 'schema.json').to_s,
query_hash_key: 'rack.request.query_hash',
parse_response_by_content_type: false,
}
end

query_hash_keyparse_response_by_content_typeの設定を追加します。

1
2
3
4
5
6
7
+  config.include Committee::Rails::Test::Methods
+ config.add_setting :committee_options
+ config.committee_options = {
+ schema_path: Rails.root.join("open_api.yaml").to_s,
+ query_hash_key: "rack.request.query_hash",
+ parse_response_by_content_type: false,
+ }

再度実行します。

1
2
3
4
5
6
7
8
9
10
11
$ docker-compose run --rm api ./bin/rspec spec/api/v1/auth/registrations_controller_spec.rb

(省略)

API::Auth::RegistrationsController
POST /api/v1/auth
returns 200
returns 422

Finished in 0.5116 seconds (files took 9.9 seconds to load)
2 examples, 0 failures

無事実行できました。

おまけ

OpenAPIのschemaでresponse200と422の場合を記述していますが、422のときには値が設定されない場合があります。その場合はそのパラメータにnullableを設定しないといけないのですが、nullableかどうかだけの違いのためにschemaを全て書く必要があり、大変だと思いました。allOfでoverrideできればいいのですが、できないようで、現実的には変更のある箇所のみを別で定義して、allOfで組み合わせるのが良さそうです。

まとめ

今回はcommittee-railsを使ってスキーマ駆動開発の環境を準備し、動作確認用のサンプルを実行するとこまで行いました。

今後スキーマ駆動開発を進めていくとより難しい問題に直面しそうな気がします。その時にはまた記載したいと思います。

参考図書

ローカル開発環境でメールを送信する

ローカルでメール送信がしたい

Djangoでメール送信機能を実装するために、ローカルでテストする方法を調べました。

Railsだとletter_opener_webがあるので楽ですが、Djangoではどうなのでしょうか?

MailCatcherを利用

Djangoではletter_opener_webのようなパッケージは見つかりませんでした。

どうしようかなと考えていたところ、以前、別のシステムのステージング環境でMailCatcherを利用していたことを思い出しました。

MailCatcher用のコンテナを立てて、そのコンテナをSMTPサーバとして指定することで簡単にメール送信環境が構築できると思います。

MailCatcherの設定

ではMailCatcherを利用するための設定を行っていきます。

MailCatcherコンテナイメージ

DockerHubで検索してみるとMailCatcherのコンテナイメージがありました。

検索結果の一番上に出てきたこちらを利用しようと思います。

docker-compose.ymlの設定

ローカルでコンテナを起動するようdocker-compose.ymlを設定していきます。

MailCatcherコンテナの設定

設定自体はとてもシンプルです。現在はwebとdbのコンテナを起動するようにしていますが、そのならびにsmtpというホスト名でコンテナを追加するだけです。

1
2
3
4
5
6
7
8
9
10
services:
web:
(省略)
db:
(省略)
smtp:
image: schickling/mailcatcher
ports:
- "1080:1080"
- "1025:1025"

設定したこととしては、imageの指定と、ポートの指定のみです。1080はメールを確認する際のWebインターフェースのポート番号、1025はSMTPで利用するポート番号になっています。

webコンテナの設定

webコンテナでは、SMTPのエンドポイントとポート番号を指定する必要があります。

STMPのエンドポイントはsmtp、ポート番号は1025になります。

1
2
3
4
web:
environment:
EMAIL_HOST: smtp
EMAIL_PORT: 1025

これでコンテナの設定は完了です。

Djangoの設定

Djangoでメール送信設定を行います。

Djangoのメール送信設定については、SendGridのページがよくまとまっていたので参考にしました。

settings.pyに以下を追記します。

1
2
3
4
5
EMAIL_HOST = os.getenv('EMAIL_HOST', '')
EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER', '')
EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD', '')
EMAIL_PORT = os.getenv('EMAIL_PORT', 587)
EMAIL_USE_TLS = os.getenv('EMAIL_USE_TLS', False)

これでMailCatcherを利用するための設定は完了です。

メールの送信テスト

ではメール送信ができるかどうかテストを行います。

コンテナの起動

docker-compose upでsmtpコンテナを起動しておきます。

1
$ docker-compose up

次に実際にメールを送信します。shell_plusを利用して、メール送信関数を実行します。

1
2
3
4
$ docker-compose run --rm web python manage.py shell_plus
>>> from django.core.mail import send_mail
>>> send_mail('subject', 'message', 'mail_from@example.com', ['mail_to@example.com'])
1

1がかえってきました。ドキュメントをみると、

1
The return value will be the number of successfully delivered messages (which can be 0 or 1 since it can only send one message).

と書かれていて、配送できたメールの数が戻り値になっているようです。今回は一通しか送っていないため、1がかえってきています。受信者が増えても配送できたメッセージの数なので、1メッセージであれば1が返ってきます。

WebUIでメールの確認

無事メールが配信されているかを確認します。 http://localhost:1080 にアクセスします。

メールが配送できていることを確認できました。

まとめ

Djangoのローカル開発環境でメール送信とその確認を行う方法を記載しました。

コンテナのおかげでローカル開発環境は簡単に拡張できて本当にありがたいです。

次回はメール本文の記述方法について記載したいと思います。

参考図書

Djangoで特定のドメインで発生したエラーについて

特定のドメインでの起動でエラー発生

ローカルでの開発もある程度終わったので、特定のドメインで稼働するようDjangoアプリケーションをデプロイしました。

デプロイ後、そのドメインでアクセスを試みるといくつかエラーが発生したのでまとめておきます。

Disallowed Hostエラー

ブラウザにドメインのURLを入力してアクセスすると、Disallowed Host Errorが発生しました。

エラーメッセージをよくみると、このドメインをALLOWED_HOSTSに追加してくれと書かれています。

よって、settings.pyに以下の記述を行いました。

1
ALLOWED_HOSTS = ['特定のドメイン', 'localhost', '127.0.0.1']

再度デプロイを行うと、無事エラーが解消されていることが確認できました。

CSRF検証エラー

次に、admin管理画面にログインするために、管理画面ログインページにアクセスしました。

IDとパスワードを入力し、ログインボタンを押下すると、CSRF検証エラーと表示され、ドメイン名 does not match any trusted origins.となってしまいます。

原因調査

先程のエラーメッセージで検索すると、それっぽいものがひっかかりました。

https://stackoverflow.com/questions/70285834/forbidden-403-csrf-verification-failed-request-aborted-reason-given-for-fail

この記事にあるように、Django4.0からこのエラーは起こるようになったようです。

対応

対応方法としては、settings.pyにCSRF_TRUSTED_ORIGINSの設定をすれば良さそうです。早速行います。

1
CSRF_TRUSTED_ORIGINS = ['https://特定のドメイン']

再度デプロイしアクセスすると、無事admin管理画面でログインすることができました。

まとめ

Djangoのローカル開発環境以外で動作させるときのエラーについて記載しました。

まだまだ経験が少ないため、プロダクション環境やステージング環境での動作時に予期せぬことが起こるかもしれません。

環境ごとでの設定ファイル(settings.py)の指定も行う必要が出てきます。その辺りも対応していければと思います。

参考図書

Flutterでのパッケージ管理

パッケージの追加

パッケージの追加はflutter pub addコマンドで行えます。

試しによく使われるだろうshared_preferencesを追加してみます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
$ flutter pub add shared_preferences
╔════════════════════════════════════════════════════════════════════════════╗
║ A new version of Flutter is available! ║
║ ║
║ To update to the latest version, run "flutter upgrade". ║
╚════════════════════════════════════════════════════════════════════════════╝

Resolving dependencies...
async 2.8.2 (2.9.0 available)
collection 1.15.0 (1.16.0 available)
fake_async 1.2.0 (1.3.0 available)
+ ffi 1.1.2
+ file 6.1.2
flutter_lints 1.0.4 (2.0.0 available)
+ flutter_web_plugins 0.0.0 from sdk flutter
+ js 0.6.3 (0.6.4 available)
lints 1.0.1 (2.0.0 available)
material_color_utilities 0.1.3 (0.1.4 available)
path 1.8.0 (1.8.1 available)
+ path_provider_linux 2.1.5
+ path_provider_platform_interface 2.0.3
+ path_provider_windows 2.0.5
+ platform 3.1.0
+ plugin_platform_interface 2.1.2
+ process 4.2.4
+ shared_preferences 2.0.13
+ shared_preferences_android 2.0.11
+ shared_preferences_ios 2.1.0
+ shared_preferences_linux 2.1.0
+ shared_preferences_macos 2.0.3
+ shared_preferences_platform_interface 2.0.0
+ shared_preferences_web 2.0.3
+ shared_preferences_windows 2.1.0
source_span 1.8.1 (1.8.2 available)
test_api 0.4.8 (0.4.9 available)
vector_math 2.1.1 (2.1.2 available)
+ win32 2.5.1
+ xdg_directories 0.2.0+1
Downloading shared_preferences 2.0.13...
Downloading shared_preferences_platform_interface 2.0.0...
Downloading shared_preferences_macos 2.0.3...
Downloading shared_preferences_windows 2.1.0...
Downloading shared_preferences_web 2.0.3...
Downloading shared_preferences_linux 2.1.0...
Downloading shared_preferences_ios 2.1.0...
Downloading shared_preferences_android 2.0.11...
Downloading win32 2.5.1...
Changed 20 dependencies!

Flutter自体の更新を促されましたが、一旦はインストールできたようです。

パッケージの追加による変更内容の確認

ソースコード管理上差分として上がってくるのは以下の2ファイルでした。

1
2
3
4
5
6
7
$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: pubspec.lock
modified: pubspec.yaml

pubspec.yaml

こちらには必要なパッケージの一覧が書かれています。今回追加したパッケージが差分として表示されています。

1
2
3
4
5
6
7
8
9
10
11
12
13
$ git diff pubspec.yaml 
diff --git a/pubspec.yaml b/pubspec.yaml
index 037db97..2c583dc 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -34,6 +34,7 @@ dependencies:
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
+ shared_preferences: ^2.0.13

dev_dependencies:
flutter_test:

pubspec.lock

こちらはpubspec.yamlで指定したパッケージをインストールする場合の依存パッケージが具体的なバージョンとともに記載されています。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ git diff pubspec.lock 
diff --git a/pubspec.lock b/pubspec.lock
index 7e78bae..1da871d 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -57,6 +57,20 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
+ ffi:
+ dependency: transitive
+ description:
+ name: ffi
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.1.2"
+ file:
+ dependency: transitive
+ description:
...(省略)...

つまるところ、bundlerやpoetry、npmと同じような仕組みですね。

lib/generated_plugin_registrant.dart

.gitignoreに記載されているので差分には出てこないですが、libディレクトリの下にファイルが追加されています。このファイルを調べてみると、webアプリケーション向けのファイルのようです。

では一旦このままの状態でflutter runすると、問題なく起動しました。

次にファイルを削除してから起動してみます。すると、起動しないのかと思いきや問題なく起動しています。

なぜだろうと思ったのですが、みてみるとlibディレクトリの下に削除したgenerated_plugin_registrant.dartが作成されていました。自動的に作成されるので.gitignoreに記載されているということですね。

Flutterの更新

最初に指摘されていたFlutter自体の更新も行っておきます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
$ flutter upgrade
Upgrading Flutter to 2.10.4 from 2.10.3 in /Users/user/flutter...
Downloading Dart SDK from Flutter engine 57d3bac3dd5cb5b0e464ab70e7bc8a0d8cf083ab...
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed

0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
1 209M 1 2270k 0 0 3404k 0 0:01:03 --:--:-- 0:01:03 3399k
7 209M 7 15.8M 0 0 9722k 0 0:00:22 0:00:01 0:00:21 9717k
13 209M 13 29.1M 0 0 10.9M 0 0:00:19 0:00:02 0:00:17 10.9M
20 209M 20 43.2M 0 0 11.7M 0 0:00:17 0:00:03 0:00:14 11.7M
27 209M 27 56.6M 0 0 12.1M 0 0:00:17 0:00:04 0:00:13 12.1M
33 209M 33 70.0M 0 0 12.3M 0 0:00:16 0:00:05 0:00:11 13.5M
39 209M 39 83.2M 0 0 12.4M 0 0:00:16 0:00:06 0:00:10 13.4M
45 209M 45 94.9M 0 0 12.3M 0 0:00:16 0:00:07 0:00:09 13.1M
50 209M 50 106M 0 0 12.3M 0 0:00:17 0:00:08 0:00:09 12.6M
56 209M 56 118M 0 0 12.2M 0 0:00:17 0:00:09 0:00:08 12.3M
62 209M 62 130M 0 0 12.2M 0 0:00:17 0:00:10 0:00:07 12.0M
67 209M 67 141M 0 0 12.1M 0 0:00:17 0:00:11 0:00:06 11.6M
73 209M 73 154M 0 0 12.2M 0 0:00:17 0:00:12 0:00:05 11.9M
80 209M 80 168M 0 0 12.2M 0 0:00:17 0:00:13 0:00:04 12.2M
86 209M 86 181M 0 0 12.3M 0 0:00:16 0:00:14 0:00:02 12.5M
93 209M 93 194M 0 0 12.4M 0 0:00:16 0:00:15 0:00:01 12.9M
99 209M 99 208M 0 0 12.4M 0 0:00:16 0:00:16 --:--:-- 13.2M
100 209M 100 209M 0 0 12.4M 0 0:00:16 0:00:16 --:--:-- 13.3M
Building flutter tool...

Upgrading engine...
Downloading android-arm-profile/darwin-x64 tools... 628ms
Downloading android-arm-release/darwin-x64 tools... 480ms
Downloading android-arm64-profile/darwin-x64 tools... 500ms
Downloading android-arm64-release/darwin-x64 tools... 477ms
Downloading android-x64-profile/darwin-x64 tools... 492ms
Downloading android-x64-release/darwin-x64 tools... 455ms
Downloading android-x86 tools... 1,246ms
Downloading android-x64 tools... 1,583ms
Downloading android-arm tools... 1,240ms
Downloading android-arm-profile tools... 618ms
Downloading android-arm-release tools... 452ms
Downloading android-arm64 tools... 1,600ms
Downloading android-arm64-profile tools... 662ms
Downloading android-arm64-release tools... 478ms
Downloading android-x64-profile tools... 666ms
Downloading android-x64-release tools... 489ms
Downloading android-x86-jit-release tools... 705ms
Downloading ios tools... 4.8s
Downloading ios-profile tools... 4.4s
Downloading ios-release tools... 18.3s
Downloading Web SDK... 4.6s
Downloading CanvasKit... 1,524ms
Downloading package sky_engine... 228ms
Downloading flutter_patched_sdk tools... 366ms
Downloading flutter_patched_sdk_product tools... 756ms
Downloading darwin-x64 tools... 3.3s
Downloading darwin-x64/font-subset tools... 338ms

Flutter 2.10.4 • channel stable • https://github.com/flutter/flutter.git
Framework • revision c860cba910 (13 days ago) • 2022-03-25 00:23:12 -0500
Engine • revision 57d3bac3dd
Tools • Dart 2.16.2 • DevTools 2.9.2

Running flutter doctor...
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 2.10.4, on macOS 11.6.4 20G417 darwin-x64, locale ja-JP)
[✓] Android toolchain - develop for Android devices (Android SDK version 32.1.0-rc1)
[✓] Xcode - develop for iOS and macOS (Xcode 13.2.1)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2021.1)
[✓] VS Code (version 1.66.0)
[✓] Proxy Configuration
[✓] Connected device (1 available)
[✓] HTTP Host Availability

• No issues found!

最後にflutter doctorを実行して確認してくれていますね。

まとめ

Flutterのバージョン管理は確立した仕組みがあり、とても頼もしいです。このあたりで特に問題になることはなさそうですね。

参考図書

linterで指摘された点まとめ

背景

サンプルアプリを作成するため、多分1週間くらいでできるFlutter入門に載っていたToDoアプリを作成してみようと思い、せっせと写経していました。

この本は2021年11月発売で、それほど古くないはずなのですが、VSCodeのプラグインからStyleの警告がいくつか表示されます…

今後も増えてきそうなのでこちらでまとめていきたいと思います。

final_not_initialized

ToDoアプリのモデル(/lib/model/todo.dart)の写経を上から進めていくと、以下のコードでエラーが表示されました。

1
2
3
4
5
enum TodoStatus { to, doing, done }

class TodoContent {
final String title;
final String content;

title,contentの両方でエラーがでてしまい、final_not_initializedと表示されていました。

final_not_initializedで検索すると以下のページが見つかりました。

https://dart.dev/tools/diagnostic-messages#final_not_initialized

final修飾子を指定した場合は初期化しないとダメですよということのようです。

困ったなと思ったのですが、一旦先に進むことにしました。次はコンストラクタを定義するところでした。

1
2
TodoContent({required this.title, required this.content, TodoStatus? state}) {
}

コンストラクタを定義した途端、先程のfinal_not_initializedのエラーは消えました。コンストラクタで初期化が必須になったからだと思います。

unnecessary_this

以下のコードで警告が表示されました。

1
2
3
void setTo() {
this.state = TodoStatus.to;
}

警告を見るとunnecessary_thisと表示されていました。詳細は以下のリンクに記載がありました。

https://dart-lang.github.io/linter/lints/unnecessary_this.html

リンクに記載の通り、thisを削除することで、警告はなくなります。thisを書かなくても、インスタンスのプロパティと判断できる場合はthisは省略した方が良いということですね。

1
this.state = state;

このようなコードの場合はthisは必要です。

unnecessary_brace_in_string_interps

以下のコードで警告が表示されました。

1
2
3
4
@override
String toString() {
return "${title}, ${content}, ${state}";
}

toStringをオーバーライドしているメソッドですが、title, content,stateにそれぞれ警告が表示されています。unnecessary_brace_in_string_interpsと表示されており、リンクをクリックすると以下のページに遷移します。

https://dart-lang.github.io/linter/lints/unnecessary_brace_in_string_interps.html

単純な文字列展開だったら{}は省いてねっていうことだと思います。

1
2
3
4
@override
String toString() {
return "$title, $content, $state";
}

このように修正することで、警告はなくなりました。

まとめ

Dartについてはまだ始めたばかりなので、いろいろ躓くところが出てくると思いますが、一つずつ着実に理解して進めていこうと思います。

参考図書