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

参考図書

DjangoでPDFを出力する

背景

業務用WebでPDFを出力する機能が必要になり、経験がなかったのでいろいろ調べてみました。

検索結果によく表示されているのがwkhtmltopdfというツールでした。django用のプラグインもあったので、一旦こちらで実装を進めていこうと思います。

wkhtmltopdfとは?

wkhtmltopdfはその名の通りhtmlをPDFとして出力するツールです。

公式サイトはこちら

今回はWebブラウザに表示されている内容をそのままPDFで出力できるようにしようと思っているので、要件にはあっています。また、Django用のプラグインもあり、容易にPDF出力できそうです。

wkhtmltopdfのインストール

まずコンテナにwkhtmltopdfをインストールします。apt installでインストールも可能ですが、パッケージが新規で200ほど、容量で500Mほど増えてしまうので、一旦wkhtmltopdfのバイナリパッケージのみインストールしてみます。

wkhtmltopdfのダウンロード

こちらからダウンロードできます。debianはbullseyeを使っていますが、bullseyeのパッケージがないので、一番近いbusterのパッケージをダウンロードします。

Dockerfileの修正

次にDockerfileを修正します。ダウンロードしたパッケージをコピーするディレクトリ(wkhtmltopdfディレクトリを作成しその下)に置き、apt installコマンドを追加します。

1
2
3
WORKDIR /code
ADD . /code/
RUN apt install ./wkhtmltopdf/wkhtmltox_0.12.6-1.buster_amd64.deb

イメージを再ビルドし、無事コンテナがビルドできることを確認します。

Djangoプラグインのインストール

次にDjangoプラグイン(django-wkhtmltopdf)を追加します。

1
$ poetry add django-wkhtmltopdf

pyproject.tomlとpoetry.lockが更新されます。

Djangoの設定

settings.pyにwkhtmltopdfの実行ファイルの場所などを指定します。

1
2
3
4
5
STATIC_ROOT = '/code/static/'
WKHTMLTOPDF_CMD = '/usr/local/bin/wkhtmltopdf'
WKHTMLTOPDF_CMD_OPTIONS = {
'quiet': True,
}

テスト用Viewの実装

最後に動作確認用のViewを仮で実装します。

1
2
3
4
5
6
7
8
9
10
from wkhtmltopdf.views import PDFTemplateView

class SamplePDFView(PDFTemplateView):
filename = "sample.pdf"
template_name = "sample.html"

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["sample_data"] = SampleData.objects.get(pk=kwargs["pk"])
return context

ViewはPDFTemplateViewを継承して作成します。

filenameでダウンロードするときのファイル名を指定します。

PDFを出力する場合、ほとんどが動的に設定したい箇所だと思います。動的に設定したい箇所については、contextでデータを渡すことで対応できます。

日本語対応

先程作成したViewにアクセスできるURLを設定し、アクセスしてみます。

すると無事PDFをダウンロードできました。早速ファイルの中身を確認すると、日本語の部分が□になってしまっています…

フォントのインストール

日本語のフォントが入っていないDockerイメージでは日本語のフォントをインストールしないと日本語表示はできません。

幸い日本語フォントのパッケージがあったので、それをインストールします。Dockerfileを再度書き換えます。

1
RUN apt install -y fonts-takao-gothic

インストール後コンテナを再起動して、無事日本語が表示されました。

まとめ

Djangoの出力するHTMLをPDFに変換することができました。

いろんな選択肢があるようですが、今回利用したwkhtmltopdfは比較的利用しやすいものだと思います。

ヘッダー、フッターなどカスタマイズする場合があればまた記載したいと思います。

FlutterアプリケーションをiOS, Androidエミュレーターで起動する

背景

前回、Flutterのプロジェクトを作成し、デモアプリケーションが起動するところまで確認しました。

しかし、アプリケーションが起動したのはChromeでした。本来はiOSとAndroidのエミュレーターで動作させたいです。

今回は、デモアプリケーションをそれぞれのエミュレーターで動作させる方法を確認します。

iOSエミュレーターでの起動

iOSエミュレーターは

1
/Applications/Xcode.app/Contents/Developer/Applications

ディレクトリの下にあるSimulator.appです。一度上記のディレクトリを開いて、アイコンをダブルクリックしてみます。

iOSエミュレーター

無事エミュレーターが起動しました。

Flutterアプリケーションの実行

この状態でflutter runを実行してみます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ flutter run
Launching lib/main.dart on iPhone 13 in debug mode...
Running Xcode build...
└─Compiling, linking and signing... 4.3s
Xcode build done. 12.6s
Syncing files to device iPhone 13... 90ms

Flutter run key commands.
r Hot reload. 🔥🔥🔥
R Hot restart.
h List all available interactive commands.
d Detach (terminate "flutter run" but leave application running).
c Clear the screen
q Quit (terminate the application on the device).

💪 Running with sound null safety 💪

An Observatory debugger and profiler on iPhone 13 is available at: http://127.0.0.1:57320/EBquF8FZZxI=/
The Flutter DevTools debugger and profiler on iPhone 13 is available at: http://127.0.0.1:9100?uri=http://127.0.0.1:57320/EBquF8FZZxI=/

iOSエミュレーターでFlutterデモアプリケーション起動

エミュレーター上でデモアプリケーションが起動しました。

今回はアイコンをダブルクリックで起動しましたが、Spotlightから起動できることも確認しました。こちらの方が便利ですね。

Androidエミュレーターでの起動

次にAndroidのエミュレーターで起動してみます。

公式ドキュメントを参考にコマンドラインからの起動を試してみます。

インストールされているエミュレーターの確認

コマンドラインで起動する際には、仮想デバイスを指定しないといけません(-avdオプション)。なので、インストールされているエミュレーターを確認する必要があります。

先程の公式ドキュメントによると、emulator -list-avdsというコマンドでインストールされている仮想デバイスの一覧が表示できるようです。emulatorのパスは

1
/Users/user/Library/Android/sdk/emulator/emulator

なので、パス指定して実行してみます。

1
$ ~/Library/Android/sdk/emulator/emulator -list-avds

なにも表示されません。ということは仮想デバイスがインストールされていないということですね。早速インストールしてみます。

仮想デバイスの追加

公式ドキュメントの仮想デバイスを作成して管理するを参考にデバイスを追加してみます。

Android Studioを起動し、More Actions->Virtual Device Managerをクリックします。

No virtual Device addedとなっており、やはり仮想デバイスは一つも登録されていません。左上のCreate Deviceボタンをクリックしてみます。

すると、デバイスを選ぶ画面が表示されます。左のCategoryPhoneのままにしておき、右の一覧からデバイスを選択します。今回はPixel 5にしてみます。Pixel 5を選択し、Nextをクリックします。

次にSystem Imageの選択画面になります。ダウンロードしないと次に進めないので、推奨されたバージョンのダウンロードリンクをクリックしてダウンロードします。

ダウンロードが完了すると、Nextがクリックできるようになるので、次に進みます。

最後に仮想デバイス名を入力して完了です。デフォルトで名前が入っているので今回はそのままにしておきます。

追加した仮想デバイスの確認

再度コマンドラインで仮想デバイスの一覧を確認します。

1
2
$ ~/Library/Android/sdk/emulator/emulator -list-avds
Pixel_5_API_30

追加されていることが確認できました。

エミュレーターの起動

ではコマンドラインで起動します。仮想デバイスの指定以外のオプションはなしで起動してみます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ ~/Library/Android/sdk/emulator/emulator -avd Pixel_5_API_30
INFO | Android emulator version 31.2.8.0 (build_id 8143646) (CL:N/A)
WARNING | unexpected system image feature string, emulator might not function correctly, please try updating the emulator.
INFO | configAndStartRenderer: setting vsync to 60 hz
WARNING | cannot add library /Users/user/Library/Android/sdk/emulator/qemu/darwin-x86_64/lib64/vulkan/libvulkan.dylib: failed
INFO | added library /Users/user/Library/Android/sdk/emulator/lib64/vulkan/libvulkan.dylib
INFO | Started GRPC server at 127.0.0.1:8554, security: Local
INFO | Advertising in: /Users/user/Library/Caches/TemporaryItems/avd/running/pid_77656.ini
INFO | Your emulator is out of date, please update by launching Android Studio:
- Start Android Studio
- Select menu "Tools > Android > SDK Manager"
- Click "SDK Tools" tab
- Check "Android Emulator" checkbox
- Click "OK"

Androidエミュレーター

起動できました。

flutter runの実行

ではエミュレーターが起動した状態でflutter runを実行します。

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
$ flutter run
Using hardware rendering with device Android SDK built for x86. If you notice graphics
artifacts, consider enabling software rendering with "--enable-software-rendering".
Launching lib/main.dart on Android SDK built for x86 in debug mode...
Checking the license for package Android SDK Tools in /Users/user/Library/Android/sdk/licenses
License for package Android SDK Tools accepted.
Preparing "Install Android SDK Tools (revision: 26.1.1)".
"Install Android SDK Tools (revision: 26.1.1)" ready.
Installing Android SDK Tools in /Users/user/Library/Android/sdk/tools
"Install Android SDK Tools (revision: 26.1.1)" complete.
"Install Android SDK Tools (revision: 26.1.1)" finished.
Checking the license for package Android SDK Build-Tools 29.0.2 in /Users/user/Library/Android/sdk/licenses
License for package Android SDK Build-Tools 29.0.2 accepted.
Preparing "Install Android SDK Build-Tools 29.0.2 (revision: 29.0.2)".
"Install Android SDK Build-Tools 29.0.2 (revision: 29.0.2)" ready.
Installing Android SDK Build-Tools 29.0.2 in /Users/user/Library/Android/sdk/build-tools/29.0.2
"Install Android SDK Build-Tools 29.0.2 (revision: 29.0.2)" complete.
"Install Android SDK Build-Tools 29.0.2 (revision: 29.0.2)" finished.
Checking the license for package Android SDK Platform 31 in /Users/user/Library/Android/sdk/licenses
License for package Android SDK Platform 31 accepted.
Preparing "Install Android SDK Platform 31 (revision: 1)".
"Install Android SDK Platform 31 (revision: 1)" ready.
Installing Android SDK Platform 31 in /Users/user/Library/Android/sdk/platforms/android-31
"Install Android SDK Platform 31 (revision: 1)" complete.
"Install Android SDK Platform 31 (revision: 1)" finished.
Running Gradle task 'assembleDebug'... 157.2s
✓ Built build/app/outputs/flutter-apk/app-debug.apk.
Installing build/app/outputs/flutter-apk/app.apk... 1,035ms
Syncing files to device Android SDK built for x86... 116ms

Flutter run key commands.
r Hot reload. 🔥🔥🔥
R Hot restart.
h List all available interactive commands.
d Detach (terminate "flutter run" but leave application running).
c Clear the screen
q Quit (terminate the application on the device).

💪 Running with sound null safety 💪

An Observatory debugger and profiler on Android SDK built for x86 is available at:
http://127.0.0.1:58754/V-rtq-7NtAg=/
The Flutter DevTools debugger and profiler on Android SDK built for x86 is available at:
http://127.0.0.1:9100?uri=http://127.0.0.1:58754/V-rtq-7NtAg=/

AndroidエミュレーターでFlutterデモアプリケーション起動

初回なのでちょっと時間がかかりましたが、無事起動できました。

まとめ

Android, iOSそれぞれのエミュレーターでFlutterでもアプリケーションを実行することができました。それぞれのエミュレーターを起動した状態でflutter runすることで、エミュレーター上でアプリケーションを動作させることができるということがわかりました。

エミュレーターで起動するとモバイルアプリを開発するんだなという実感が湧いてきます。

次回はプロジェクトの中のファイルがどういったものなのかを確認していきたいと思います。

参考図書

Flutterでプロジェクトを作成する

背景

前回flutter doctorでのチェックが通りました。

今回はプロジェクトを作成して開発を始めたいと思います。

プロジェクトの作成

プロジェクトの作成方法は2つありまして、コマンドラインでの作成とAndroid Studioを使ってのGUIでの作成です。

再現性が高いようにコマンドラインで作成してみます。

コマンドラインでのプロジェクトの作成

1
2
3
4
$ flutter create -i swift -a kotlin --androidx --org org.book-reviews --description 'flutter sample app' flutter_sample_app
Could not find an option named "androidx".

Run 'flutter -h' (or 'flutter <command> -h') for available flutter commands and options.

androidxというオプションがないと言われています。書籍やWebサイトで調べてみると、最新の状態にすることでこのエラーを回避することができるようです。

まずは安定版に切り替えます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ flutter channel stable
Running "flutter pub get" in flutter_tools... 23.4s
Switching to flutter channel 'stable'...
git: From https://github.com/flutter/flutter
git: + b101bfe32f...680962aa75 beta -> origin/beta (forced update)
git: + b101bfe32f...680962aa75 dev -> origin/dev (forced update)
git: 79a0aad87c..db5b5c7dbb flutter-2.12-candidate.3 -> origin/flutter-2.12-candidate.3
git: * [new branch] flutter-2.12-candidate.4 -> origin/flutter-2.12-candidate.4
git: * [new branch] flutter-2.12-candidate.5 -> origin/flutter-2.12-candidate.5
git: * [new branch] flutter-2.12-candidate.6 -> origin/flutter-2.12-candidate.6
git: * [new branch] flutter-2.12-candidate.7 -> origin/flutter-2.12-candidate.7
git: 940986e00a..5e6a653865 master -> origin/master
git: * [new branch] update_console_link -> origin/update_console_link
git: * [new tag] 2.12.0-4.1.pre -> 2.12.0-4.1.pre
git: * [new tag] 2.12.0-4.0.pre -> 2.12.0-4.0.pre
git: Already on 'stable'
git: Your branch is up to date with 'origin/stable'.
Successfully switched to flutter channel 'stable'.
To ensure that you're on the latest build from this channel, run 'flutter upgrade'

flutter channelについては、ドキュメントで確認できます。

次にメッセージにあるようにflutter upgradeを実行します。

1
2
3
4
5
6
$ flutter upgrade
Flutter is already up to date on channel stable
Flutter 2.10.3 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 7e9793dee1 (2 weeks ago) • 2022-03-02 11:23:12 -0600
Engine • revision bd539267b4
Tools • Dart 2.16.1 • DevTools 2.9.2

もともと最新だったようです。再度実行してみます。

1
2
3
4
$ flutter create -i swift -a kotlin --androidx --org org.book-reviews --description 'flutter sample app' flutter_sample_app
Could not find an option named "androidx".

Run 'flutter -h' (or 'flutter <command> -h') for available flutter commands and options.

結果は変わりません…-hオプションを指定して実行してみます。

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
$ flutter create -h
Create a new Flutter project.

If run on a project that already exists, this will repair the project, recreating any files that are missing.

Global options:
-h, --help Print this usage information.
-v, --verbose Noisy logging, including all shell commands executed.
If used with "--help", shows hidden options. If used with "flutter doctor", shows additional diagnostic information. (Use "-vv"
to force verbose logging in those cases.)
-d, --device-id Target device id or name (prefixes allowed).
--version Reports the version of this tool.
--suppress-analytics Suppress analytics reporting when this command runs.

Usage: flutter create <output directory>
-h, --help Print this usage information.
--[no-]pub Whether to run "flutter pub get" after the project has been created.
(defaults to on)
--[no-]offline When "flutter pub get" is run by the create command, this indicates whether to run it in offline mode or not. In offline mode,
it will need to have all dependencies already available in the pub cache to succeed.
--[no-]overwrite When performing operations, overwrite existing files.
--description The description to use for your new Flutter project. This string ends up in the pubspec.yaml file.
(defaults to "A new Flutter project.")
--org The organization responsible for your new Flutter project, in reverse domain name notation. This string is used in Java package
names and as prefix in the iOS bundle identifier.
(defaults to "com.example")
--project-name The project name for this new Flutter project. This must be a valid dart package name.
-i, --ios-language The language to use for iOS-specific code, either ObjectiveC (legacy) or Swift (recommended).
[objc, swift (default)]
-a, --android-language The language to use for Android-specific code, either Java (legacy) or Kotlin (recommended).
[java, kotlin (default)]
--platforms The platforms supported by this project. Platform folders (e.g. android/) will be generated in the target project. This
argument only works when "--template" is set to app or plugin. When adding platforms to a plugin project, the pubspec.yaml will
be updated with the requested platform. Adding desktop platforms requires the corresponding desktop config setting to be
enabled.
[ios (default), android (default), windows (default), linux (default), macos (default), web (default)]
-t, --template=<type> Specify the type of project to create.

[app] (default) Generate a Flutter application.
[module] Generate a project to add a Flutter module to an existing Android or iOS application.
[package] Generate a shareable Flutter project containing modular Dart code.
[plugin] Generate a shareable Flutter project containing an API in Dart code with a platform-specific implementation for Android, for
iOS code, or for both.
[skeleton] Generate a List View / Detail View Flutter application that follows community best practices.

-s, --sample=<id> Specifies the Flutter code sample to use as the "main.dart" for an application. Implies "--template=app". The value should be
the sample ID of the desired sample from the API documentation website (http://docs.flutter.dev/). An example can be found at:
https://api.flutter.dev/flutter/widgets/SingleChildScrollView-class.html
--list-samples=<path> Specifies a JSON output file for a listing of Flutter code samples that can be created with "--sample".

Run "flutter help" to see global options.

見たところ、--androidxというオプションはなさそうなので、なしで実行してみます。

1
2
3
4
5
6
7
8
9
10
11
12
$ flutter create -i swift -a kotlin --org org.book-reviews --description 'flutter sample app' flutter_sample_app 
Creating project flutter_sample_app...
Running "flutter pub get" in flutter_sample_app... 2,764ms
Wrote 96 files.

All done!
In order to run your application, type:

$ cd flutter_sample_app
$ flutter run

Your application code is in flutter_sample_app/lib/main.dart.

作成できました!

アプリケーションの起動

プロジェクト作成後のメッセージにアプリケーションの起動方法が書かれていたので早速実行してみます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ cd flutter_sample_app
$ flutter run
Launching lib/main.dart on Chrome in debug mode...
Waiting for connection from debug service on Chrome... 15.4s
This app is linked to the debug service: ws://127.0.0.1:52600/qyihwl33cdE=/ws
Debug service listening on ws://127.0.0.1:52600/qyihwl33cdE=/ws

💪 Running with sound null safety 💪

🔥 To hot restart changes while running, press "r" or "R".
For a more detailed help message, press "h". To quit, press "q".

An Observatory debugger and profiler on Chrome is available at: http://127.0.0.1:52600/qyihwl33cdE=
A message on the flutter/keyevent channel was discarded before it could be handled.
This happens when a plugin sends messages to the framework side before the framework has had an opportunity to register a listener. See the ChannelBuffers
API documentation for details on how to configure the channel to expect more messages, or to expect messages to get discarded:
https://api.flutter.dev/flutter/dart-ui/ChannelBuffers-class.html

Chromeが起動し、Flutter Demo Home Pageが表示されました。右下の+ボタンをクリックすると、真ん中に表示されている数字が増えていきます。

これでデモ画面が表示されるところまで確認できました。

まとめ

今回はプロジェクトの作成とアプリケーションの起動まで行いました。

それほどスムーズにはできなかったですが、モバイルアプリ開発の準備をすることができました。

これからアプリケーションの設定と実装を行っていきたいと思います。

参考図書

flutter doctorを使って開発環境を構築する

背景

前回flutter doctorの実行で躓いてしまいました。今回はflutter doctorで状況を確認し、開発環境を構築するところまで行いたいと思います。

flutter doctorの実行

まずflutter doctorを実行して状況を確認します。

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
$ ./bin/flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 2.10.3, on macOS 11.6 20G165 darwin-x64, locale ja-JP)
[✗] Android toolchain - develop for Android devices
✗ Unable to locate Android SDK.
Install Android Studio from: https://developer.android.com/studio/index.html
On first launch it will assist you in installing the Android SDK components.
(or visit https://flutter.dev/docs/get-started/install/macos#android-setup for
detailed instructions).
If the Android SDK has been installed to a custom location, please use
`flutter config --android-sdk` to update to that location.

[✗] Xcode - develop for iOS and macOS
✗ Xcode installation is incomplete; a full installation is necessary for iOS
development.
Download at: https://developer.apple.com/xcode/download/
Or install Xcode via the App Store.
Once installed, run:
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
sudo xcodebuild -runFirstLaunch
✗ CocoaPods not installed.
CocoaPods is used to retrieve the iOS and macOS platform side's plugin code that
responds to your plugin usage on the Dart side.
Without CocoaPods, plugins will not work on iOS or macOS.
For more info, see https://flutter.dev/platform-plugins
To install see https://guides.cocoapods.org/using/getting-started.html#installation
for instructions.
[✓] Chrome - develop for the web
[!] Android Studio (not installed)
[✓] VS Code (version 1.65.0)
[!] Proxy Configuration
! NO_PROXY does not contain 127.0.0.1
! NO_PROXY does not contain ::1
[✓] Connected device (1 available)
[✓] HTTP Host Availability

! Doctor found issues in 4 categories.

バツと!になっている箇所について対応が必要そうです。

Xcodeのインストール

まずは簡単そうなXcodeのインストールから行います。

App Storeからダウンロード

まずApp StoreからXcodeをインストールします。

ライセンスに同意する

ライセンスに同意する必要があるので、以下のコマンドを実行します

1
$ sudo xcodebuild -license

するとライセンスに関する文面が表示されます。スペースキーでページ送りをして、最後に以下の表示になったらagreeとタイプしてエンターキーを押します。

1
2
3
By typing 'agree' you are agreeing to the terms of the software license agreements. Type 'print' to print them or anything else to cancel, [agree, print, cancel] agree

You can view the license agreements in Xcode's About Box, or at /Applications/Xcode.app/Contents/Resources/English.lproj/License.rtf

状況確認

Xcodeがインストールできたので、再度flutter doctorで確認します。

1
2
3
4
5
6
7
8
9
10
11
$ flutter doctor
...(省略)...
[!] Xcode - develop for iOS and macOS (Xcode 13.2.1)
✗ CocoaPods not installed.
CocoaPods is used to retrieve the iOS and macOS platform side's plugin code that
responds to your plugin usage on the Dart side.
Without CocoaPods, plugins will not work on iOS or macOS.
For more info, see https://flutter.dev/platform-plugins
To install see https://guides.cocoapods.org/using/getting-started.html#installation
for instructions.
...(省略)...

ということで、CocoaPodsのインストールが必要でした。

CocoaPodsのインストール

先程のflutter doctorのメッセージにあるインストールのドキュメントを見てみます。

冒頭にYou can use a Ruby Version manager, however we recommend that you use the standard Ruby available on macOS unless you know what you're doing.とあるので、グローバルにインストールされているRubyを利用しようと思います。

1
2
3
4
5
6
7
8
$ which ruby
/Users/user/.anyenv/envs/rbenv/shims/ruby
$ ruby -v
ruby 2.6.3p62 (2019-04-16 revision 67580) [universal.x86_64-darwin20]
$ rbenv versions
* system
2.4.10
3.1.0

グローバルにインストールされているRubyが2.6.3ということですね。こちらにインストールします。

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
$ sudo /usr/bin/gem install cocoapods
Password:
Fetching concurrent-ruby-1.1.9.gem
Fetching tzinfo-2.0.4.gem
Fetching zeitwerk-2.5.4.gem
Fetching activesupport-6.1.5.gem
Fetching fuzzy_match-2.0.4.gem
Fetching i18n-1.10.0.gem
Fetching nap-1.1.0.gem
Fetching httpclient-2.8.3.gem
Fetching algoliasearch-1.27.5.gem
Fetching ffi-1.15.5.gem
Fetching ethon-0.15.0.gem
Fetching typhoeus-1.4.0.gem
Fetching netrc-0.11.0.gem
Fetching public_suffix-4.0.6.gem
Fetching addressable-2.8.0.gem
Fetching cocoapods-core-1.11.3.gem
Fetching claide-1.1.0.gem
Fetching cocoapods-deintegrate-1.0.5.gem
Fetching cocoapods-downloader-1.5.1.gem
Fetching cocoapods-plugins-1.0.0.gem
Fetching cocoapods-search-1.0.1.gem
Fetching cocoapods-trunk-1.6.0.gem
Fetching cocoapods-try-1.2.0.gem
Fetching molinillo-0.8.0.gem
Fetching atomos-0.1.3.gem
Fetching colored2-3.1.2.gem
Fetching nanaimo-0.3.0.gem
Fetching rexml-3.2.5.gem
Fetching xcodeproj-1.21.0.gem
Fetching escape-0.0.4.gem
Fetching fourflusher-2.3.1.gem
Fetching gh_inspector-1.1.3.gem
Fetching ruby-macho-2.5.1.gem
Fetching cocoapods-1.11.3.gem
Successfully installed concurrent-ruby-1.1.9
Successfully installed i18n-1.10.0
Successfully installed tzinfo-2.0.4
Successfully installed zeitwerk-2.5.4
Successfully installed activesupport-6.1.5
Successfully installed nap-1.1.0
Successfully installed fuzzy_match-2.0.4
Successfully installed httpclient-2.8.3
A new major version is available for Algolia! Please now use the https://rubygems.org/gems/algolia gem to get the latest features.
Successfully installed algoliasearch-1.27.5
Building native extensions. This could take a while...
ERROR: Error installing cocoapods:
ERROR: Failed to build gem native extension.

current directory: /Library/Ruby/Gems/2.6.0/gems/ffi-1.15.5/ext/ffi_c
/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/bin/ruby -I /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0 -r ./siteconf20220317-44199-1ctmdly.rb extconf.rb
checking for ffi.h... *** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of necessary
libraries and/or headers. Check the mkmf.log file for more details. You may
need configuration options.

Provided configuration options:
--with-opt-dir
--without-opt-dir
--with-opt-include
--without-opt-include=${opt-dir}/include
--with-opt-lib
--without-opt-lib=${opt-dir}/lib
--with-make-prog
--without-make-prog
--srcdir=.
--curdir
--ruby=/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/bin/$(RUBY_BASE_NAME)
--with-ffi_c-dir
--without-ffi_c-dir
--with-ffi_c-include
--without-ffi_c-include=${ffi_c-dir}/include
--with-ffi_c-lib
--without-ffi_c-lib=${ffi_c-dir}/lib
--enable-system-libffi
--disable-system-libffi
--with-libffi-config
--without-libffi-config
--with-pkg-config
--without-pkg-config
/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/mkmf.rb:467:in `try_do': The compiler failed to generate an executable file. (RuntimeError)
You have to install development tools first.
from /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/mkmf.rb:585:in `block in try_compile'
from /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/mkmf.rb:534:in `with_werror'
from /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/mkmf.rb:585:in `try_compile'
from /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/mkmf.rb:1109:in `block in have_header'
from /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/mkmf.rb:959:in `block in checking_for'
from /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/mkmf.rb:361:in `block (2 levels) in postpone'
from /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/mkmf.rb:331:in `open'
from /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/mkmf.rb:361:in `block in postpone'
from /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/mkmf.rb:331:in `open'
from /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/mkmf.rb:357:in `postpone'
from /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/mkmf.rb:958:in `checking_for'
from /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/mkmf.rb:1108:in `have_header'
from extconf.rb:10:in `system_libffi_usable?'
from extconf.rb:42:in `<main>'

To see why this extension failed to compile, please check the mkmf.log which can be found here:

/Library/Ruby/Gems/2.6.0/extensions/universal-darwin-20/2.6.0/ffi-1.15.5/mkmf.log

extconf failed, exit code 1

Gem files will remain installed in /Library/Ruby/Gems/2.6.0/gems/ffi-1.15.5 for inspection.
Results logged to /Library/Ruby/Gems/2.6.0/extensions/universal-darwin-20/2.6.0/ffi-1.15.5/gem_make.out

エラーが発生してしまいました。メッセージを確認すると、ffi.hが見つからないということのようです。

libffiがインストールされていないからかもしれないので、インストールしてみます。

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
$ brew install libffi
Running `brew update --preinstall`...
...(省略)...
==> Downloading https://ghcr.io/v2/homebrew/core/libffi/manifests/3.4.2
######################################################################## 100.0%
==> Downloading https://ghcr.io/v2/homebrew/core/libffi/blobs/sha256:a461f6ad21a23a725691385dbbec3eff958cf61d5282e84dc3f0483e307e1875
==> Downloading from https://pkg-containers.githubusercontent.com/ghcr1/blobs/sha256:a461f6ad21a23a725691385dbbec3eff958cf61d5282e84dc3f0483e307e1875?se=202
######################################################################## 100.0%
==> Pouring libffi--3.4.2.big_sur.bottle.tar.gz
==> Caveats
libffi is keg-only, which means it was not symlinked into /usr/local,
because macOS already provides this software and installing another version in
parallel can cause all kinds of trouble.

For compilers to find libffi you may need to set:
export LDFLAGS="-L/usr/local/opt/libffi/lib"
export CPPFLAGS="-I/usr/local/opt/libffi/include"

==> Summary
🍺 /usr/local/Cellar/libffi/3.4.2: 17 files, 599.8KB
==> `brew cleanup` has not been run in the last 30 days, running now...
Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).
Removing: /Users/user/Library/Caches/Homebrew/icu4c--69.1... (27.2MB)
Removing: /Users/user/Library/Caches/Homebrew/nkf--2.1.5... (167KB)
Removing: /Users/user/Library/Caches/Homebrew/nkf_bottle_manifest--2.1.5... (7.9KB)
Removing: /Users/user/Library/Logs/Homebrew/telnet... (64B)

インストールできました。しかし、再度cocoapodsをインストールしようとしても同じエラーで止まります。

エラーログを見てみると、ruby/config.hが見つからないということで、シンボリックリンクを作成してみました。

1
2
$ cd /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.1.sdk/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/include/ruby-2.6.0/ruby
$ sudo ln -sf ../../../../Headers/ruby/config.h

すると、内容は変わったのですが、いまだにエラーは出ます。

1
2
3
4
5
6
7
8
current directory: /Library/Ruby/Gems/2.6.0/gems/ffi-1.15.5/ext/ffi_c
make "DESTDIR="
make: *** No rule to make target `/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.1.sdk/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/include/ruby-2.6.0/universal-darwin20/ruby/config.h', needed by `AbstractMemory.o'. Stop.

make failed, exit code 2

Gem files will remain installed in /Library/Ruby/Gems/2.6.0/gems/ffi-1.15.5 for inspection.
Results logged to /Library/Ruby/Gems/2.6.0/extensions/universal-darwin-20/2.6.0/ffi-1.15.5/gem_make.out

よくみると、universal-darwin20と書かれています。Rubyのバージョンにもそのように書いてありました。しかし、実際に存在するディレクトリはuniversal-darwin21です。この記事を参考に、シンボリックリンクを作成します。

1
2
$ cd /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.1.sdk/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/include/ruby-2.6.0/
$ sudo ln -s universal-darwin21 universal-darwin20

再度試します

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
75
76
77
78
79
80
$ sudo gem install cocoapods
Password:
Building native extensions. This could take a while...
Successfully installed ffi-1.15.5
Successfully installed ethon-0.15.0
Successfully installed typhoeus-1.4.0
Successfully installed netrc-0.11.0
Successfully installed public_suffix-4.0.6
Successfully installed addressable-2.8.0
Successfully installed cocoapods-core-1.11.3
Successfully installed claide-1.1.0
Successfully installed cocoapods-deintegrate-1.0.5
Successfully installed cocoapods-downloader-1.5.1
Successfully installed cocoapods-plugins-1.0.0
Successfully installed cocoapods-search-1.0.1
Successfully installed cocoapods-trunk-1.6.0
Successfully installed cocoapods-try-1.2.0
Successfully installed molinillo-0.8.0
Successfully installed atomos-0.1.3
Successfully installed colored2-3.1.2
Successfully installed nanaimo-0.3.0
Successfully installed rexml-3.2.5
Successfully installed xcodeproj-1.21.0
Successfully installed escape-0.0.4
Successfully installed fourflusher-2.3.1
Successfully installed gh_inspector-1.1.3
Successfully installed ruby-macho-2.5.1
Successfully installed cocoapods-1.11.3
Parsing documentation for ffi-1.15.5
Installing ri documentation for ffi-1.15.5
Parsing documentation for ethon-0.15.0
Installing ri documentation for ethon-0.15.0
Parsing documentation for typhoeus-1.4.0
Installing ri documentation for typhoeus-1.4.0
Parsing documentation for netrc-0.11.0
Installing ri documentation for netrc-0.11.0
Parsing documentation for public_suffix-4.0.6
Installing ri documentation for public_suffix-4.0.6
Parsing documentation for addressable-2.8.0
Installing ri documentation for addressable-2.8.0
Parsing documentation for cocoapods-core-1.11.3
Installing ri documentation for cocoapods-core-1.11.3
Parsing documentation for claide-1.1.0
Installing ri documentation for claide-1.1.0
Parsing documentation for cocoapods-deintegrate-1.0.5
Installing ri documentation for cocoapods-deintegrate-1.0.5
Parsing documentation for cocoapods-downloader-1.5.1
Installing ri documentation for cocoapods-downloader-1.5.1
Parsing documentation for cocoapods-plugins-1.0.0
Installing ri documentation for cocoapods-plugins-1.0.0
Parsing documentation for cocoapods-search-1.0.1
Installing ri documentation for cocoapods-search-1.0.1
Parsing documentation for cocoapods-trunk-1.6.0
Installing ri documentation for cocoapods-trunk-1.6.0
Parsing documentation for cocoapods-try-1.2.0
Installing ri documentation for cocoapods-try-1.2.0
Parsing documentation for molinillo-0.8.0
Installing ri documentation for molinillo-0.8.0
Parsing documentation for atomos-0.1.3
Installing ri documentation for atomos-0.1.3
Parsing documentation for colored2-3.1.2
Installing ri documentation for colored2-3.1.2
Parsing documentation for nanaimo-0.3.0
Installing ri documentation for nanaimo-0.3.0
Parsing documentation for rexml-3.2.5
Installing ri documentation for rexml-3.2.5
Parsing documentation for xcodeproj-1.21.0
Installing ri documentation for xcodeproj-1.21.0
Parsing documentation for escape-0.0.4
Installing ri documentation for escape-0.0.4
Parsing documentation for fourflusher-2.3.1
Installing ri documentation for fourflusher-2.3.1
Parsing documentation for gh_inspector-1.1.3
Installing ri documentation for gh_inspector-1.1.3
Parsing documentation for ruby-macho-2.5.1
Installing ri documentation for ruby-macho-2.5.1
Parsing documentation for cocoapods-1.11.3
Installing ri documentation for cocoapods-1.11.3
Done installing documentation for ffi, ethon, typhoeus, netrc, public_suffix, addressable, cocoapods-core, claide, cocoapods-deintegrate, cocoapods-downloader, cocoapods-plugins, cocoapods-search, cocoapods-trunk, cocoapods-try, molinillo, atomos, colored2, nanaimo, rexml, xcodeproj, escape, fourflusher, gh_inspector, ruby-macho, cocoapods after 26 seconds
25 gems installed

無事インストールできました。

flutter doctorで確認します。

1
2
3
4
$ ./bin/flutter doctor
...(省略)...
[✓] Xcode - develop for iOS and macOS (Xcode 13.2.1)
...(省略)...

Xcodeはクリアしました。

Android toolchainのインストール

次にAndroid toolchainをインストールします。flutter doctorの出力にあるサイトからダウンロードしてインストールします。

ダウンロードしたdmgをダブルクリックし、アプリケーションアイコンをアプリケーションディレクトリにドラッグ&ドロップします。

インストールが終わったら、On first launch it will assist you in installing the Android SDK components.と書かれているので、Android Studioを起動します。

利用規約に同意してその他のコンポーネントをインストールします。

ここまで終わったらflutter doctorで確認します。

1
2
3
4
5
6
7
8
9
10
11
$ ./bin/flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 2.10.3, on macOS 11.6.4 20G417 darwin-x64, locale ja-JP)
[!] Android toolchain - develop for Android devices (Android SDK version 32.1.0-rc1)
✗ cmdline-tools component is missing
Run `path/to/sdkmanager --install "cmdline-tools;latest"`
See https://developer.android.com/studio/command-line for more details.
✗ Android license status unknown.
Run `flutter doctor --android-licenses` to accept the SDK licenses.
See https://flutter.dev/docs/get-started/install/macos#android-setup for more details.
...(省略)...

まだ足りないようでした。メッセージに従ってコマンドを実行していきます。

Android SDK Command-line Toolsのインストール

sdkmanagerのパスがわからなかったので、GUIで設定しました。

Android Studioを起動し、メニューのAndroid Studio->Preferenceを選択し、左メニューのAppearance & Behavior->System Setting->Android SDKを選択します。

右のタブでSDK Toolsを選択し、下に表示される一覧の中にAndroid SDK Command-line Tools (latest)にチェックを入れ、右下のApplyボタンをクリックします。

するとダウンロードとインストールが始まります。終わったらOKをクリックします。

ライセンスの同意

次にライセンスに同意します。メッセージに記載のあるコマンドを実行します。

1
2
3
4
5
6
7
8
9
10
11
12
13
$ ./bin/flutter doctor --android-licenses 
Warning: Failed to download any source lists! Fetch remote repository...
Warning: Still waiting for package manifests to be fetched remotely.
Warning: Still waiting for package manifests to be fetched remotely.y...
Warning: Still waiting for package manifests to be fetched remotely.y...
Warning: Still waiting for package manifests to be fetched remotely.y...
Warning: Still waiting for package manifests to be fetched remotely.y...
Warning: Still waiting for package manifests to be fetched remotely.y...
Warning: Still waiting for package manifests to be fetched remotely.y...
Warning: IO exception while downloading manifesttch remote repository...
Warning: IO exception while downloading manifest
Warning: Still waiting for package manifests to be fetched remotely.y...
All SDK package licenses accepted.======] 100% Computing updates...

だいぶ時間がかかりますし、うまくいってないようなメッセージが出ますが、結果的に全てのライセンスに同意できています。

それではflutter doctorで確認します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ ./bin/flutter doctor                   
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 2.10.3, 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.65.2)
[!] Proxy Configuration
! NO_PROXY does not contain 127.0.0.1
! NO_PROXY does not contain ::1
[✓] Connected device (1 available)
[!] HTTP Host Availability
✗ HTTP host https://maven.google.com/ is not reachable. Reason: An error occurred while checking the HTTP host: Operation timed out
✗ HTTP host https://pub.dev/ is not reachable. Reason: An error occurred while checking the HTTP host: Operation timed out
✗ HTTP host https://cloud.google.com/ is not reachable. Reason: An error occurred while checking the HTTP host: Operation timed out

! Doctor found issues in 2 categories.

もう少しですね…

NO_PROXYの設定

NO_PROXYの設定はコマンドを実行するシェルで環境変数を設定するのだろうと思うので設定してみます。

~/.zshrcNO_PROXYを設定します

1
NO_PROXY=localhost,127.0.0.1,::1

シェルを再起動します

1
$ exec $SHELL -l

環境変数を確認します

1
2
$ env | grep -i no_proxy
NO_PROXY=localhost,127.0.0.1,::1

設定できました。

HTTP Host Availabilityの対応

環境変数のPROXYの設定が悪さをしていて、外部へのアクセスができてなかったので、unsetコマンドで削除しました。

最後にflutter doctorで確認します。

1
2
3
4
5
6
7
8
9
10
11
$ ./bin/flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 2.10.3, on macOS 11.6.4 20G417 darwin-x64, locale ja-JP)
[!] Android toolchain - develop for Android devices (Android SDK version 32.1.0-rc1)
! Some Android licenses not accepted. To resolve this, run: flutter doctor --android-licenses
[✓] Xcode - develop for iOS and macOS (Xcode 13.2.1)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2021.1)
[✓] VS Code (version 1.65.2)
[✓] Connected device (1 available)
[✓] HTTP Host Availability

なぜかまたライセンスで!になっているので、コマンドを実行します。(やはり先程はエラーになっていたようです)

1
2
3
4
5
6
$ ./bin/flutter doctor --android-licenses
5 of 7 SDK package licenses not accepted. 100% Computing updates...
Review licenses that have not been accepted (y/N)? y

1/5: License android-googletv-license:
...(省略)...

ライセンスが表示されるのでyを押して同意します。

今度こそ最後にflutter doctorで確認します。

1
2
3
4
5
6
7
8
9
10
11
12
$ ./bin/flutter doctor                   
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 2.10.3, 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.65.2)
[✓] Connected device (1 available)
[✓] HTTP Host Availability

• No issues found!

すべてクリアできました!

まとめ

flutter doctorですべてOKになるところまで実施しました。

大きく時間がかかったのはcocoapodsのインストールだけでした。こちらもMacのバージョンを上げたりしていなければ(darwin20とdarwin21の違い)、スムーズに進んだと思います。

とりあえず環境構築はできました。次からはモバイルアプリの開発を進めていきたいと思います。

参考図書

「dartが悪質なソフトウェアかどうかをAppleでは確認できないため、このソフトウェアは開けません」の対処法

背景

会社の業務でスマートフォンアプリを開発することになったので、今のスマホアプリ開発ツールを調査してみました。すると、flutterというツールが見つかりました。

flutterはgoogleが開発しているクロスプラットフォームなスマートフォンアプリ開発ツールで、現在も活発に開発が行われているようです。同じような開発ツールでReactNativeがあるようですが、”flutter ReactNative 比較”と検索してみると、

Flutterはよりシンプルで、OS(オペレーティングシステム)の更新によって行われた変更に対して耐性があります。 React Nativeはデバイスのネイティブ要素に依存しており、システムが更新された場合に iOSアプリケーションと Androidアプリケーションに個別に追加の適応作業が必要があります。

とのことなので、flutterの利用が良さそうです。ということで早速試してみました。

flutterSDKのダウンロード

まず、flutterSDKをダウンロードします。

https://docs.flutter.dev/development/tools/sdk/releases?tab=macos

最新版が今日(2022/03/03)リリースされていました。

ダウンロードしたファイルを適当な場所に展開します。

flutter doctorの実行

Flutter実践入門のチュートリアルをみていると、flutter doctorというコマンドで状況が確認できるということです。早速実行してみます。

1
$ ./bin/flutter doctor

すると、ポップアップが表示されdartが悪質なソフトウェアかどうかをAppleでは確認できないため、このソフトウェアは開けません。と表示され、OKボタンをクリックするとコンソールに

1
/Users/user/flutter/bin/internal/shared.sh: line 223: 40699 Killed: 9               "$DART" --disable-dart-dev --packages="$FLUTTER_TOOLS_DIR/.packages" $FLUTTER_TOOL_ARGS "$SNAPSHOT_PATH" "$@"

と表示され終了してしまいます。

「ソフトウェアは開けません」が表示された時の対応方法

Appleのサポートページに回答が書いてありました。

https://support.apple.com/ja-jp/guide/mac-help/mchleab3a043/mac

ポップアップが表示された時に、「Finderで開く」を選択して、Finderで表示し、開くことができないアプリケーションのアイコンを右クリック->開くを選択して一回開けば、次回から警告は表示されなくなります。

まとめ

スマートフォンアプリの開発は10年ぶりくらいで、10年前はUnityを使っていました。flutterはとても便利そうですが、新しい分野を学ぶときは勉強することが多いので、成長を感じつつも学ぶ量に圧倒されています。

flutterはこれからどんどん試していくのでまた気づきがあれば掲載します。

参考図書

MySQLdb._exceptions.DataError (1406, Data too long for column 'name')の原因調査

背景

前回謎のエラーが発生し、マイグレーションはできているものの、なぜなのかがわからないままでした。

テストデータをテーブルに投入する際に、既存データの破棄のためロールバックを行ったのですが、また同じエラーが発生したので、詳しく調べてみました。

エラー内容の確認

エラーが起こったのはauthアプリケーションのロールバックでした

1
2
3
4
5
6
7
8
9
10
11
12
$ docker-compose run --rm web python manage.py migrate auth zero
...
(省略)
...
Unapplying auth.0002_alter_permission_name_max_length...
DEBUG ALTER TABLE `auth_permission` MODIFY `name` varchar(50) NOT NULL; (params [])
DEBUG (0.021) None; args=[]; alias=default
Traceback (most recent call last):
...
(省略)
...
django.db.utils.DataError: (1406, "Data too long for column 'name' at row 57")

今回はDEBUGメッセージを表示しているので実行されるSQLがわかります。auth_permissionテーブルの変更でエラーが発生していることがわかります。

レコードの確認

実際にauth_permissionテーブルを確認してみます。

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
$ docker-compose run --rm web python manage.py dbshell
Creating example_web_run ... done
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MySQL connection id is 92
Server version: 5.7.36 MySQL Community Server (GPL)

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [example]> select * from auth_permission;
+----+--------------------------------------------------------+-----------------+-----------------------------------------------+
| id | name | content_type_id | codename |
+----+--------------------------------------------------------+-----------------+-----------------------------------------------+
| 1 | Can add log entry | 1 | add_logentry |
| 2 | Can change log entry | 1 | change_logentry |
| 3 | Can delete log entry | 1 | delete_logentry |
...
(省略)
...
| 57 | 51文字の文字列 | 15 | add_長めのモデル名 |
...
(省略)

エラーメッセージは

1
django.db.utils.DataError: (1406, "Data too long for column 'name' at row 57")

なので、57行目を確認したところ、nameカラムの値が51文字ありました。

マイグレーションの内容確認

マイグレーションの内容を確認します。authアプリケーションのマイグレーションファイルはgithubで確認できました。

https://github.com/django/django/tree/main/django/contrib/auth/migrations

この中の

https://github.com/django/django/blob/main/django/contrib/auth/migrations/0002_alter_permission_name_max_length.py

でエラーが発生しています。ファイル名からしてすでに怪しいですね。

内容は以下になっています

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("auth", "0001_initial"),
]

operations = [
migrations.AlterField(
model_name="permission",
name="name",
field=models.CharField(max_length=255, verbose_name="name"),
),
]

最大長を255に変更しているのがわかります。だとすると元々の長さはどれくらいだったのでしょうか。それを知るためにはその前のマイグレーションフィアルである、0001_initial.pyを確認します。

確認すると以下のようになっていました。

1
("name", models.CharField(max_length=50, verbose_name="name")),

最大50文字でした…最大長255から最大長50に戻すには保存されているデータが大きすぎるということで、エラーになっているということですね。

ロールバックの順序で対応できるか

マイグレーションでデータをINSERTしているのであれば、ロールバックでデータをDELETEすれば、ALTER文でエラーが発生しなくなるのでは?と思い、INSERTしているマイグレーションのアプリケーションを探してみると、sessionsのマイグレーションでINSERTしていることがわかった。

しかし、sessionsのマイグレーションをロールバックしてもデータが削除されなかった…

まとめ

長い名前のモデルを作成すると、auth_permissionテーブルのnameカラムに長さが50を超えるデータが入ることがある。その状態でロールバックすると例外が発生してしまいます。

現状思いつく対応としては、auth_permissionテーブルのデータを全て削除してからロールバックを行うという方法です。スマートさはないですが、エラーの回避はできます。

しかし、テーブル作成した後にmax_length=255にするくらいなら、最初からmax_length=255で作成しておいてくれればいいのになと思いました。

参考図書

DjangoでRailsのようなログ出力設定をする

背景

Djangoでviewを実装し、ぽちぽち動かしているときに、どんなクエリが実行されているかを確認できるとうれしいと思いました。
Railsだとデフォルトでそのようになっているのですが、Djangoは設定が必要だったので設定してみました。

設定

設定内容は以下になります

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
}
},
'loggers': {
'django.db.backends': {
'level': 'DEBUG',
'handlers': ['console'],
},
}
}

設定内容を詳しくみていきます

設定の詳細

まず公式ドキュメントを確認します
ロギング | Django ドキュメント | Django
公式ドキュメントの内容に沿って確認していきます

LOGGING

公式ドキュメントには

n order to configure logging, you use LOGGING to define a dictionary of logging settings. These settings describe the loggers, handlers, filters and formatters that you want in your logging setup, and the log levels and other properties that you want those components to have.

と記載されており、LOGGINGというdictionaryの定数を定義することでロギングの設定ができることがわかります。

LOGGING内の要素

version

versionについて、公式ドキュメントには記載がありませんが、例に記載されているように記述しておきます。
試しにversionを削除したところ、

1
2
3
web_1  |   File "/usr/local/lib/python3.10/logging/config.py", line 498, in configure
web_1 | raise ValueError("dictionary doesn't specify a version")
web_1 | ValueError: dictionary doesn't specify a version

というエラーが出てしまいました。なので、記述しておきます。

disable_existing_loggers

disable_existing_loggersについて、公式ドキュメントには以下のように記載されています。

LOGGINGdisable_existing_loggers キーの値を True にすると、全てのデフォルトの設定が無効になります(キーが存在しない場合は dictConfig のデフォルトになります)。このため、 ’disable_existing_loggers’: True を使う場合は注意してください。 True を設定する必要は殆どないでしょう。 disable_existing_loggersFalse に設定して、デフォルトのロガーの一部、または全てを定義しなおすこともできます。あるいは、 LOGGING_CONFIGNone に設定して、 ロギングの設定を自分で行うこと も出来ます。

基本的にはFalseで良さそうですね。

handlers

[公式ドキュメント](ロギング | Django ドキュメント | Django)に記載があります。

Handlerはloggerから指定されます。今回の例で言うと、console
という名のハンドラを定義し、ログレベルと、ログを処理するハンドラクラスを指定します。

上記で指定しているlogging.StreamHandlerについてはPythonの公式ドキュメントに記載がありました
logging.handlers — ロギングハンドラ — Python 3.10.0b2 ドキュメント

StreamHandler クラスの新たなインスタンスを返します。 stream が指定された場合、インスタンスはログ出力先として指定されたストリームを使います; そうでない場合、 sys.stderr が使われます。

docker-compose upで動作させている時は、実行しているコンソールが標準出力なので、そのコンソールにログが出力されると言うことですね

loggers

[公式ドキュメント](ロギング | Django ドキュメント | Django)に記載があります

キー(今回でいうところのdjango.db.backends)は、パッケージのパスを指定します。そのパッケージ内で出力されたログは、ロガーで指定したログレベルと比較して同等以上の場合にロガーを通じてハンドラに渡されます。

以上で設定はすべて見終わりました。

詳細なログ設定

公式ドキュメントにあった詳細なログ設定も記載しておきます。パッケージごとに細分化されています。

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
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
'style': '{',
},
'simple': {
'format': '{levelname} {message}',
'style': '{',
},
},
'filters': {
'special': {
'()': 'project.logging.SpecialFilter',
'foo': 'bar',
},
'require_debug_true': {
'()': 'django.utils.log.RequireDebugTrue',
},
},
'handlers': {
'console': {
'level': 'INFO',
'filters': ['require_debug_true'],
'class': 'logging.StreamHandler',
'formatter': 'simple'
},
'mail_admins': {
'level': 'ERROR',
'class': 'django.utils.log.AdminEmailHandler',
'filters': ['special']
}
},
'loggers': {
'django': {
'handlers': ['console'],
'propagate': True,
},
'django.request': {
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': False,
},
'myproject.custom': {
'handlers': ['console', 'mail_admins'],
'level': 'INFO',
'filters': ['special']
}
}
}

フォーマッタは見やすくするために利用することもあるかもしれません。

動作確認

設定前と設定後でどのようにコンソールの出力が変わったか見てみます。

設定前

1
2
3
4
5
web_1  | Starting development server at http://0.0.0.0:8000/
web_1 | Quit the server with CONTROL-C.
web_1 | [08/Feb/2022 11:36:59] "GET /accounts/login/?next=/ HTTP/1.1" 200 1892
web_1 | [08/Feb/2022 11:37:05] "POST /accounts/login/?next=/ HTTP/1.1" 302 0
web_1 | [08/Feb/2022 11:37:05] "GET / HTTP/1.1" 200 1176

設定後

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
web_1  | Starting development server at http://0.0.0.0:8000/
web_1 | Quit the server with CONTROL-C.
web_1 | [08/Feb/2022 11:39:16] "GET /accounts/login/ HTTP/1.1" 200 1892
web_1 | (0.001)
web_1 | SELECT VERSION(),
web_1 | @@sql_mode,
web_1 | @@default_storage_engine,
web_1 | @@sql_auto_is_null,
web_1 | @@lower_case_table_names,
web_1 | CONVERT_TZ('2001-01-01 01:00:00', 'UTC', 'UTC') IS NOT NULL
web_1 | ; args=None; alias=default
web_1 | (0.000) SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; args=None; alias=default
web_1 | (0.001) SELECT `auth_user`.`id`, `auth_user`.`password`, `auth_user`.`last_login`, `auth_user`.`is_superuser`, `auth_user`.`username`, `auth_user`.`first_name`, `auth_user`.`last_name`, `auth_user`.`email`, `auth_user`.`is_staff`, `auth_user`.`is_active`, `auth_user`.`date_joined` FROM `auth_user` WHERE `auth_user`.`email` = 'test@example.com' LIMIT 21; args=('test@example.com',); alias=default
web_1 | (0.001) SELECT (1) AS `a` FROM `django_session` WHERE `django_session`.`session_key` = 'xxxxxxxxxxxxxxxxxxxxxxx' LIMIT 1; args=('xxxxxxxxxxxxxxxxxxxxxxx',); alias=default
web_1 | (0.000) INSERT INTO `django_session` (`session_key`, `session_data`, `expire_date`) VALUES ('xxxxxxxxxxxxxxxxxxxxxxx', 'yyyyyyyyyyyyyyyyyyyy', '2022-02-22 02:39:29.502823'); args=('xxxxxxxxxxxxxxxxxxxxxxx', 'yyyyyyyyyyyyyyyyyyyy', '2022-02-22 02:39:29.502823'); alias=default
web_1 | (0.004) UPDATE `auth_user` SET `last_login` = '2022-02-08 02:39:29.509067' WHERE `auth_user`.`id` = 1; args=('2022-02-08 02:39:29.509067', 1); alias=default
web_1 | (0.001) UPDATE `django_session` SET `session_data` = 'zzzzzzzzzzzzzzzzzzzzzzzz', `expire_date` = '2022-02-22 02:39:29.515114' WHERE `django_session`.`session_key` = 'xxxxxxxxxxxxxxxxxxxxxxx'; args=('zzzzzzzzzzzzzzzzzzzzzzzz', '2022-02-22 02:39:29.515114', 'xxxxxxxxxxxxxxxxxxxxxxx'); alias=default
web_1 | [08/Feb/2022 11:39:29] "POST /accounts/login/ HTTP/1.1" 302 0
web_1 | (0.001) SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; args=None; alias=default
web_1 | (0.001) SELECT `django_session`.`session_key`, `django_session`.`session_data`, `django_session`.`expire_date` FROM `django_session` WHERE (`django_session`.`expire_date` > '2022-02-08 02:39:29.536012' AND `django_session`.`session_key` = 'xxxxxxxxxxxxxxxxxxxxxxx') LIMIT 21; args=('2022-02-08 02:39:29.536012', 'xxxxxxxxxxxxxxxxxxxxxxx'); alias=default
web_1 | (0.001) SELECT `auth_user`.`id`, `auth_user`.`password`, `auth_user`.`last_login`, `auth_user`.`is_superuser`, `auth_user`.`username`, `auth_user`.`first_name`, `auth_user`.`last_name`, `auth_user`.`email`, `auth_user`.`is_staff`, `auth_user`.`is_active`, `auth_user`.`date_joined` FROM `auth_user` WHERE `auth_user`.`id` = 1 LIMIT 21; args=(1,); alias=default
web_1 | [08/Feb/2022 11:39:29] "GET / HTTP/1.1" 200 1176

実行されているSQLがログに表示されるようになりました!

ログの種類によって色をわける

どうしてもRailsをベースに考えてしまい、ログの種類によって色が変わるのがいいと思って調べてみると、colorlogというパッケージが見つかりました。

Poetryでインストール後、LOGGINGを以下のように書き換えます。

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
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'color': {
'()': 'colorlog.ColoredFormatter',
'format': '%(log_color)s%(levelname)-8s %(message)s',
'log_colors': {
'DEBUG': 'bold_black',
'INFO': 'white',
'WARNING': 'yellow',
'ERROR': 'red',
'CRITICAL': 'bold_red',
},
},
},
'handlers': {
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'color',
}
},
'loggers': {
'django.db.backends': {
'level': 'DEBUG',
'handlers': ['console'],
},
}
}

formattersで指定したlog_colorsは例に載っていたものをそのまま利用しました。ここをお好みで調整すると色が変わります。

まとめ

開発デバッグ作業を効率よく行うには情報を増やすことが必要です。LOGGINGの設定であれば、一度行ってしまえばそのままずっと使えるのでしっかりと理解して設定するのがよいと思いました。

参考図書

データベース名を変更したらマイグレーションエラー MySQLdb._exceptions.DataError (1406, Data too long for column 'name') が発生したときの対処法

背景

Djangoでモデル実装をして、マイグレーションを実行しました。
その後、テータベース名が適切ではないという話になり、データベース名を変更することになりました。

其の際に思わぬエラーが発生したので記しておきます。

設定ファイルのデータベース名の変更

設定ファイル内でデータベース名の記載があったのは二つでした。

  • docker-compose.yml
  • front/settings.py

それぞれ新しいデータベース名に変更します。(frontはアプリケーション名)

新規データベースの作成

データベースのrenameができないということと、個々のテーブルを別データベースの下にrenameするのは数が多くて大変だったので、新規でデータベースを作成し、権限を付与した後にマイグレーションすることにしました。

データベースコンテナを起動した状態で、アプリケーションコンテナにログインして、mysqlコマンドでデータベースに接続しました。

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
$ docker-compose run --rm web /bin/bash
Creating example_web_run ... done
root@ddad35e91809:/code# mysql -u root -p -h db
Enter password:
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MySQL connection id is 30
Server version: 5.7.36 MySQL Community Server (GPL)

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [(none)]> create database new_db;
Query OK, 1 row affected (0.002 sec)

MySQL [(none)]> grant all privileges on `new_db`.* to 'django'@'%';
Query OK, 0 rows affected (0.002 sec)

MySQL [(none)]> grant all privileges on `test_new_db`.* to 'django'@'%';
Query OK, 0 rows affected (0.002 sec)

MySQL [(none)]> exit
Bye
root@ddad35e91809:/code#
root@ddad35e91809:/code# exit
exit
$

マイグレーションでエラー発生

これでマイグレーションすれば大丈夫と思っていて、マイグレーションを実行しました。

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
75
76
77
78
$ docker-compose run --rm web python manage.py migrate front
Creating example_web_run ... done
Operations to perform:
Apply all migrations: front
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying front.0001_initial... OK
Traceback (most recent call last):
File "/usr/local/lib/python3.10/site-packages/django/db/backends/utils.py", line 85, in _execute
return self.cursor.execute(sql, params)
File "/usr/local/lib/python3.10/site-packages/django/db/backends/mysql/base.py", line 73, in execute
return self.cursor.execute(query, args)
File "/usr/local/lib/python3.10/site-packages/MySQLdb/cursors.py", line 206, in execute
res = self._query(query)
File "/usr/local/lib/python3.10/site-packages/MySQLdb/cursors.py", line 319, in _query
db.query(q)
File "/usr/local/lib/python3.10/site-packages/MySQLdb/connections.py", line 254, in query
_mysql.connection.query(self, query)
MySQLdb._exceptions.DataError: (1406, "Data too long for column 'name' at row 33")

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
File "/code/manage.py", line 22, in <module>
main()
File "/code/manage.py", line 18, in main
execute_from_command_line(sys.argv)
File "/usr/local/lib/python3.10/site-packages/django/core/management/__init__.py", line 425, in execute_from_command_line
utility.execute()
File "/usr/local/lib/python3.10/site-packages/django/core/management/__init__.py", line 419, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
File "/usr/local/lib/python3.10/site-packages/django/core/management/base.py", line 373, in run_from_argv
self.execute(*args, **cmd_options)
File "/usr/local/lib/python3.10/site-packages/django/core/management/base.py", line 417, in execute
output = self.handle(*args, **options)
File "/usr/local/lib/python3.10/site-packages/django/core/management/base.py", line 90, in wrapped
res = handle_func(*args, **kwargs)
File "/usr/local/lib/python3.10/site-packages/django/core/management/commands/migrate.py", line 277, in handle
emit_post_migrate_signal(
File "/usr/local/lib/python3.10/site-packages/django/core/management/sql.py", line 46, in emit_post_migrate_signal
models.signals.post_migrate.send(
File "/usr/local/lib/python3.10/site-packages/django/dispatch/dispatcher.py", line 170, in send
return [
File "/usr/local/lib/python3.10/site-packages/django/dispatch/dispatcher.py", line 171, in <listcomp>
(receiver, receiver(signal=self, sender=sender, **named))
File "/usr/local/lib/python3.10/site-packages/django/contrib/auth/management/__init__.py", line 83, in create_permissions
Permission.objects.using(using).bulk_create(perms)
File "/usr/local/lib/python3.10/site-packages/django/db/models/query.py", line 519, in bulk_create
returned_columns = self._batched_insert(
File "/usr/local/lib/python3.10/site-packages/django/db/models/query.py", line 1324, in _batched_insert
self._insert(item, fields=fields, using=self.db, ignore_conflicts=ignore_conflicts)
File "/usr/local/lib/python3.10/site-packages/django/db/models/query.py", line 1301, in _insert
return query.get_compiler(using=using).execute_sql(returning_fields)
File "/usr/local/lib/python3.10/site-packages/django/db/models/sql/compiler.py", line 1441, in execute_sql
cursor.execute(sql, params)
File "/usr/local/lib/python3.10/site-packages/django/db/backends/utils.py", line 99, in execute
return super().execute(sql, params)
File "/usr/local/lib/python3.10/site-packages/django/db/backends/utils.py", line 67, in execute
return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
File "/usr/local/lib/python3.10/site-packages/django/db/backends/utils.py", line 76, in _execute_with_wrappers
return executor(sql, params, many, context)
File "/usr/local/lib/python3.10/site-packages/django/db/backends/utils.py", line 80, in _execute
with self.db.wrap_database_errors:
File "/usr/local/lib/python3.10/site-packages/django/db/utils.py", line 90, in __exit__
raise dj_exc_value.with_traceback(traceback) from exc_value
File "/usr/local/lib/python3.10/site-packages/django/db/backends/utils.py", line 85, in _execute
return self.cursor.execute(sql, params)
File "/usr/local/lib/python3.10/site-packages/django/db/backends/mysql/base.py", line 73, in execute
return self.cursor.execute(query, args)
File "/usr/local/lib/python3.10/site-packages/MySQLdb/cursors.py", line 206, in execute
res = self._query(query)
File "/usr/local/lib/python3.10/site-packages/MySQLdb/cursors.py", line 319, in _query
db.query(q)
File "/usr/local/lib/python3.10/site-packages/MySQLdb/connections.py", line 254, in query
_mysql.connection.query(self, query)
django.db.utils.DataError: (1406, "Data too long for column 'name' at row 33")
ERROR: 1

長々とエラーメッセージが出力されましたが、ポイントは

1
django.db.utils.DataError: (1406, "Data too long for column 'name' at row 33")

と思いました

状況確認

エラーメッセージで調べても、データが溢れてるんじゃない?とか、データ溢れた場合にエラーが発生しないようにするにはsql_mode=’’にすればいいよという内容しか出てきませんでした。

nameカラムの確認

nameというカラムを持つテーブルが複数あり、max_lengthなどを確認しましたが、だいたい128が設定されていました。そもそもマイグレーションではデータを入れることはないので、調べる箇所が検討違いでした。

マイグレーションの状況

テーブルが正常に作成されているかどうかを確認したのですが、正常に作成されていました。

テスト

変えたのはDB名のみです。なので、データベース名の長さが問題なのかと思い、長いデータベース名や短いデータベース名を試してみましたが、どの場合でもエラーが発生しました。ただ唯一、以前のデータベース名のみエラーが発生しないという状況でした。

解決方法

一旦全てのテーブルをロールバックしてから行ってみようと思い、アプリケーションすべてをロールバックしました。

その後、マイグレーションを行うとエラーは発生せず、マイグレーションも行われていました。

エラーメッセージからnameというカラムに問題があるのかと思ったのですが、一旦全てきれいにしてからマイグレーションを行えばデータベース名の変更は問題なさそうです。

ですが、もともとデータベースは新しく作成しているので、各アプリケーションのテーブルが影響しているとは考えづらいですね…

まとめ

データベース名の変更は極力行わない。行う場合は全てのアプリケーションのテーブルをロールバックしてから再度マイグレーションを行う。

参考図書