
普段Python系の記事を書いている筆者がモバイルアプリを開発するためにFlutterを学びます。Flutterはクロスプラットフォームな開発フレームワークです。ここではPythonに慣れている人が簡単なアプリを作成し、動作テストするところまでをまとめます。
こんにちは。wat(@watlablog)です。今回はFlutterをはじめてみます!
はじめに
Flutterを学ぶモチベーション
普段Pythonしか触っていない筆者がFlutterに興味が出た理由を最初に書いておきます。ただ、モバイルアプリ開発には昔から興味がありSwiftを使って次のような記事を書いたこともあります。
・SwiftUIで録音した音声を平均化FFTするアプリをつくってみた
一応やりたいことはできるようになったのですが、この時はSwiftの中でPythonでやっていたことを再実装していました。つまり高速フーリエ変換のアルゴリズムから自分で書いていたので、非常に面倒でした。そしてSwiftで書いたものをAndroid端末で使うことはできないので、なかなかアプリを公開する気も起こらなかったという背景があります。
そこで最近耳に入ってきた情報として、「TorchScriptを使えばPython(PyTorch)で書いた資産がその他のアプリでも使える」や「Flutterを使えばiOSでもAndroidでも使えるアプリになる」というものがありました。これらは過去の不満を解決する可能性があり、モバイルアプリ開発をもう一度やってみようと思うきっかけに十分です。
TorchScriptについては以下の記事で少し試してみました。機械学習モデルも中間表現(IR)にすることで他のアプリで呼び出すことができます。さらにPyTorchが持っている関数であればある程度はIRに落とせるらしいので、Pythonistaの知見をモバイルアプリにも投入できるという夢のようなコラボができそうという期待もあります。
・PyTorchモデルをTorchScriptに変換する方法
自分で作ったFFTで音声分析したり、Pythonでつくった機械学習モデルをスマホで活用できたらなんかかっこよさそう!程度のモチベーションです。
Flutterとは?
Flutterとは、Googleが開発したマルチプラットフォーム(iOS/Android/Web/Windows/macOS/Linux)の開発フレームワークです。Flutterはフレームワークの名称で、内部で使われている言語はDartです。
ウィジェットベースの設計でiOS風、Android風のUIも簡単に構築ができ、状態管理やアニメーションも豊富とのことです。さらにネイティブコードにコンパイルされるので非常に高速な動作が期待できます。また、Firebaseとの連携も容易で、認証・データベース・クラウド機能との接続がしやすいメリットもあります。
Flutterはオープンソースなフレームワークです。2017年にリリースされたフレームワークですが、エンジニア同士のコミュニティも活発です。
・GitHub Flutter:https://github.com/flutter
それでは早速Flutterの開発環境からはじめましょう!
環境
筆者のPC環境を参考までに次の表に示します。基本的にはモバイル端末にiPhoneを使っているので、M3 Macbook Airで開発をしていきます。
Mac | OS | macOS Sonoma 14.3 |
---|---|---|
チップ | Apple M3 | |
CPU | 1.4[GHz] | |
メモリ | 16[GB] |
既にHomebrewやVSCodeは入っていることを前提として説明を行います。このPCの環境構築は以下の記事で解説していますので、必要に応じて参照してください。
・M3 Macでvenv/VSCodeによるPython環境を構築するときの備忘録
Flutter開発環境構築
Flutterのインストール
Flutterは次の brewコマンドでインストールします(Homebrewがインストールされていない人はこちら)。筆者環境では5分くらいかかりましたが、終了すると「flutter was successfully installed!」という文章が表示されているはずです。
1 |
brew install --cask flutter |
以下の flutterコマンドでバージョン情報が表示されればインストールはOKです。
1 |
flutter --version |
iOS開発環境の構築
Xcodeのインストール
iOSアプリの開発にはXcodeが必要です。XcodeはApp StoreからGUI操作でインストールを行います。以下の記事の「Xcodeをインストールする」という項目にあるように検索してインストールをしましょう。サイズが大きいのでかなり時間がかかると思います。
・PythonアプリをiOSで実機テスト:kivy-iosの使い方

Xcodeがインストールされたら、LaunchPadから初回起動して設定をしておきます。また、次のコマンドを実行しておきます。このコマンドを実行しておかないと、Flutterが「Xcodeが見つかりません」というエラーを出すことがあります。
1 |
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer |
そして次のコマンドも実行します。これは後でビルド時にxcodebuildに関するエラーが出ないように初期化処理等をするコマンドです。
1 |
sudo xcodebuild -runFirstLaunch |
Xcodeのコマンドが通るか確認してみましょう。次のコマンドを実行してバージョン情報が表示されればOKです。筆者の環境では16.2が表示されました。
1 |
xcodebuild -version |
必要なシミュレータもインストールしておきましょう。Xcodeを起動して、メニューからSettingを選択→Componentsタブを表示→iOS Simulatorの「Get」ボタンをクリック。

CocoaPodsのインストール
iOSでFlutterを使う場合はCocoaPodsもインストールする必要があります。CocoaPodsとは、iOSアプリで使うライブラリやフレームワークを簡単に管理・導入できるパッケージマネージャーです。FlutterでiOSアプリをビルドする際、内部的にCocoaPodsを使って依存ライブラリ(DartとiOSをつなぐための)を導入します。
CocoaPodsは次のコマンドでインストールしましょう。
1 |
brew install cocoapods |
インストールができているか確認します。次のコマンドでバージョンが表示されればOKです。
1 |
pod --version |
podコマンドでエラーが出る
筆者の環境では pod --versionコマンドで ActiveSupport::LoggerThreadSafeLevel::Logger (NameError)が出ました。どうやらこれはActiveSupportのバージョン依存関係がうまくいっていないようで、別環境のRubyが参照されている可能性があります。以下のコマンドで podコマンドがどの環境のRubyに参照されているか確認しましょう。
1 |
which pod |
このコマンドの結果 rbenv shell systemと返ってくれば、Homebrew環境ではなくrbenv環境のRubyで参照されていることになります。その場合は以下のコマンドでHomebrew環境のRubyに切り替えます。
1 2 3 4 5 6 7 8 |
# カレントシェルだけ rbenv をオフ rbenv shell system # もし gem の cocoapods が残っていればアンインストール(念のため) sudo gem uninstall cocoapods # shim を再生成 rbenv rehash |
筆者の環境ではRedmine等をインストールした時にRubyがインストールされていましたが、この操作を行なった後にRedmineは正常に起動できることを確認しました。ただし、読者の環境で重要なプロジェクトにRubyを使っている場合は自己責任でコマンド操作をお願いします。
Android開発環境の構築
Android Studioのインストール
Androidアプリを開発するためにはAndroid Studioが必要です。次のURLから自分の環境に適したパッケージを選択してダウンロードしてください。
・https://developer.android.com/studio
macOSはIntel版のチップ用とApple Silicon版のチップ用があります。今回筆者はM3チップ(Apple Silicon)なので、ARMを選びました。

ダウンロードが完了したら .dmgを開き、アプリケーションフォルダへドラッグ&ドロップしてインストールします。
Android Studioを起動し、セットアップウィザードを進めます。Standardを選択しNextをクリックします。

ライセンス条項を読み、問題なければAcceptにチェックをつけてFinishします。この時、android-sdk-licenseとandroid-sdk-arm-dbt-licenseの2つにそれぞれAcceptをつける必要があります。

ここで一度SDKの内容を確認します。Android StudioのWelcome画面でMore Actions→SDK Managerを選択、以下の項目にチェックが入っているか確認し、入っていなければチェックを入れます。今回筆者はCommand-line Toolsに新しくチェックを入れました。
- Android SDK Build-Tools
- Android SDK Command-line Tools
- Android Emulator
- Android SDK Platform-Tools

シェル設定ファイルの編集
設定ファイルに以下の内容を追記します。VSCodeで編集する場合は code ~/.zshrcを実行してください。以下のコードを追記して保存します。
1 2 3 4 5 |
export ANDROID_SDK_ROOT="$HOME/Library/Android/sdk" export PATH="$PATH:$ANDROID_SDK_ROOT/emulator" export PATH="$PATH:$ANDROID_SDK_ROOT/tools" export PATH="$PATH:$ANDROID_SDK_ROOT/tools/bin" export PATH="$PATH:$ANDROID_SDK_ROOT/platform-tools" |
そして次のコマンドで設定を反映させます。
1 |
source ~/.zshrc |
FlutterへAndroid SDKを認識させる
次のコマンドでFlutterがAndroid SDKを認識できるようにします。licenseのコマンドではいくつか yを入力して内容に承諾する必要があります。
1 2 |
flutter config --android-sdk "$HOME/Library/Android/sdk" flutter doctor --android-licenses |
Flutter環境をチェックする
これまで実施してきた環境構築の結果が正しいかどうか、以下のコマンドでチェックしてみましょう。
1 |
flutter doctor |
このコマンドを実行すると、各種チェッカーが走ります。次の画像のようにすべての項目に✅がつけばOKです。

結構長かった!
何かエラーが出てもChatGPTとかに聞くとすぐ解決するから良い時代になりましたね!
Flutterアプリの作成方法と実行方法
環境構築はこれでできました!いよいよFlutterでアプリを作ってみます!
任意フォルダでFlutterプロジェクトを作成する
Flutterのアプリは任意のフォルダで開発できます。まずは適当なフォルダを作成し、
cdコマンドでその中に移動しましょう。そして、次のコードを実行してFlutterプロジェクトを作成します。
ここでは
my_appという名前のプロジェクトを作成しましたが、この名前は任意で結構です。
1 |
flutter create my_app |
VSCodeを起動する
作成したプロジェクトフォルダ(ここでは
my_app)に
cdコマンドで移動し、VSCodeを
code .コマンドで起動します。VSCodeをターミナルから開く方法は以下の記事にまとめていますので参考にしてください。
・M3 Macでvenv/VSCodeによるPython環境を構築するときの備忘録
1 2 |
cd my_app code . |
VSCodeが立ち上がれば成功です。 flutter createでプロジェクトを作成すると、あらかじめアプリ開発に必要な材料がすべて揃ったフォルダ・ファイル構成になっています。これから主に作業するのは libフォルダにある main.dartというDart言語のファイルです。

初回のみ、ExtensionsからFlutterの拡張機能(プラグイン)をインストールしておきましょう。Flutterと検索すると上位に出てくるはずです。

iOSシミュレータを起動する
筆者はiPhoneユーザーなので、開発は最初iOS向けにつくります。次はiOSシミュレータを以下のコマンドで起動してみましょう。
1 |
open -a Simulator |
iPhoneの画面がでてきたら成功です。

アプリをシミュレータで実行する
main.dartにはデフォルトでカウンターアプリのコードが書かれています。次のコマンドを実行すると、シミュレータ上で main.dartに書かれた内容が実行されます。
1 |
flutter run |
ターミナル上でXCodeのビルドが走り、以下の画面が表示されます。「+」ボタンをマウスでクリックすると数字がカウントされていきます。

Xcode画面を開かなくてもビルドができるのは便利!
デフォルトのアプリを編集する
ここまではプロジェクト作成時に用意されたデフォルトのアプリを起動しただけです。次は自分でアプリを編集してみましょう。
画面にテキストを配置する
次のコードは画面にHello World!というテキストを配置するだけのコードです。 main.dartにデフォルトで書かれているコードを一度全て削除し、このコードをペーストします。
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 |
import 'package:flutter/material.dart'; // アプリ全体をMyAppウィジェットで包む void main() { runApp(const MyApp()); } // アプリのルートウィジェット(Stateless=状態を持たない) class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'MyApp', home: Scaffold( appBar: AppBar( title: const Text('Hello World!'), ), body: const Center( child: Text( 'Hello World!', style: TextStyle(fontSize: 28), ), ), ), ); } } |
このような画面になります。 flutter runが実行されている間はホットリロードが働き、 .dartファイルを保存したら自動的にシミュレータの画面も更新されます。もし更新されていない場合は何かVSCode側で問題がある可能性があります(ホットリロードと連動していない)が、ターミナル側で rを入力すると強制的にホットリロードが開始されます。

Flutterアプリの構造
Flutterアプリはウィジェットのツリー構造(親子関係)でできています。以下のコードで main()はアプリのスタート地点(Dart言語の基本)、 runApp()はFlutterアプリ全体のルートウィジェットを起動する関数、 MyApp()が最初のルートウィジェットです。
1 2 3 |
void main() { runApp(const MyApp()); } |
以下は簡易的な構造イメージです。このようにウィジェットを入れ子にしていくことでUIを作っていきます。各ウィジェットは「親→子」関係で画面に構成されます。
1 2 3 4 5 6 7 8 9 |
runApp( MyApp ← StatelessWidget └─ MaterialApp ← Flutter提供の"全体の枠" └─ Scaffold ← 土台レイアウト(AppBarやbodyを含む) ├─ AppBar ← 上のバー └─ Body(中心にText) └─ Center └─ Text("Hello World!") ) |
用語 | 意味と役割 |
---|---|
MaterialApp |
アプリ全体のテーマやナビゲーション設定などを管理。Googleの「Material Design」ベースUI。 |
Scaffold |
土台レイアウト。AppBar, Body, Drawer, BottomNavigation などの枠組みを提供 |
AppBar |
上部に表示されるバー(タイトルや戻るボタンなどを置ける) |
body |
メイン画面に表示する中身。通常は Center , Column , ListView などが使われる |
Center |
子ウィジェットを中央に配置するためのウィジェット |
Text |
テキストを表示する最も基本的なウィジェット |
StatelessWidget |
状態を持たないウィジェット(表示する内容は常に同じ) |
StatefulWidget |
状態を持ち、ユーザー操作などで変化する UI を作れるウィジェット |
@overrideとは?
Pythonでも一緒だと思いますが、Dartでも親クラスのメソッドを上書きするときに @overrideを使います。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 |
import 'package:flutter/material.dart'; // アプリ全体をMyAppウィジェットで包む void main() { runApp(const MyApp()); } // アプリのルートウィジェット(Stateless=状態を持たない) class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'MyApp', home: Scaffold( appBar: AppBar( title: const Text('Hello World!'), ), body: const Center( child: Text( 'Hello World!', style: TextStyle(fontSize: 28), ), ), floatingActionButton: FloatingActionButton( onPressed: (){ // 空の関数 }, child: const Icon(Icons.touch_app), // ボタンの中身にアイコンを表示 ) ), ); } } |
右下にボタンが配置されました。まだボタンの関数は空の状態であるので、クリックしても何も起こりません。

アイコン一覧
Icons.***でFlutter Iconsで用意されたアイコンを使えます。一覧は次の公式ページで確認できますので参考にしてください。
・https://api.flutter.dev/flutter/material/Icons-class.html
めちゃくちゃ種類がある!…のでよく使いそうなアイコンを次の表にまとめます!
アイコン | コード例 | 意味 |
---|---|---|
➕ | Icons.add |
追加 |
⭐️ | Icons.star |
お気に入り |
🔍 | Icons.search |
検索 |
👤 | Icons.person |
ユーザー |
✉️ | Icons.mail |
メール |
💬 | Icons.chat |
チャット |
🏠 | Icons.home |
ホーム画面 |
⚙️ | Icons.settings |
設定 |
ボタンをタップしたらテキストが変わるようにする
ちょっとコード内のコメントがやかましいかもしれませんが、ハイライト部分を変更することでボタンにイベントを設定しました。解説はコメントをご確認ください。
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 |
import 'package:flutter/material.dart'; // アプリ全体をMyAppウィジェットで包む void main() { runApp(const MyApp()); } // StatelessWidget → StatefulWidget に変更 class MyApp extends StatefulWidget { // ← ここを変更 const MyApp({super.key}); @override State<MyApp> createState() => _MyAppState(); // ← ここを追加 } // 状態クラスを追加 class _MyAppState extends State<MyApp> { bool _isHelloWorld = true; // 現在のテキスト状態を保持 void _toggleText() { setState(() { _isHelloWorld = !_isHelloWorld; // 状態を切り替える }); } @override Widget build(BuildContext context) { return MaterialApp( title: 'MyApp', home: Scaffold( appBar: AppBar( title: const Text('Hello World!'), ), body: Center( // ← const を削除(状態に応じて変化するので) child: Text( _isHelloWorld ? 'Hello World!' : 'Hello Flutter!', // ← ここを変更 style: const TextStyle(fontSize: 28), ), ), floatingActionButton: FloatingActionButton( onPressed: _toggleText, // ← イベントを接続 child: const Icon(Icons.touch_app), ), ), ); } } |
挙動を動画にしてみました。ボタンを押すってなんかいいですよね(?)。
ChatGPTにお願いして装飾をしてもった結果も動画にしてみました。
もはやGUIの設計や装飾は生成AIで良い気がしてきました…。
参考までに、装飾を施した場合のコードを以下に示します。ただし、完全に蛇足なので以下の▽マークをクリックし、開いて表示させてください。
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 107 108 109 110 111 112 113 114 |
import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({super.key}); @override State<MyApp> createState() => _MyAppState(); } class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin { bool _isHelloWorld = true; // ← late を外して null許容型に変更し、? を使って安全にアクセスするように AnimationController? _iconRotationController; @override void initState() { super.initState(); _iconRotationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 500), ); } @override void dispose() { _iconRotationController?.dispose(); super.dispose(); } void _toggleText() { if (_iconRotationController != null) { if (_iconRotationController!.status == AnimationStatus.completed) { _iconRotationController!.reverse(); } else { _iconRotationController!.forward(); } } setState(() { _isHelloWorld = !_isHelloWorld; }); } @override Widget build(BuildContext context) { return MaterialApp( title: 'Hello World!', home: Scaffold( body: Container( decoration: const BoxDecoration( gradient: LinearGradient( colors: [Colors.purple, Colors.blueAccent], begin: Alignment.topLeft, end: Alignment.bottomRight, ), ), child: Center( child: AnimatedSwitcher( duration: const Duration(milliseconds: 800), transitionBuilder: (Widget child, Animation<double> animation) { return SlideTransition( position: Tween<Offset>( begin: const Offset(0.0, 0.5), end: Offset.zero, ).animate(animation), child: FadeTransition(opacity: animation, child: child), ); }, child: Text( _isHelloWorld ? 'Hello World!' : 'Hello Flutter!', key: ValueKey<bool>(_isHelloWorld), style: TextStyle( fontSize: 40, fontWeight: FontWeight.bold, color: Colors.white, letterSpacing: 2, shadows: [ Shadow( offset: Offset(-2, -2), color: Colors.cyanAccent.withOpacity(0.8), blurRadius: 4, ), Shadow( offset: Offset(2, 2), color: Colors.deepPurpleAccent.withOpacity(0.8), blurRadius: 4, ), ], ), ), ), ), ), floatingActionButton: RotationTransition( // ← nullチェックを入れることで build時エラーを防ぐ turns: _iconRotationController != null ? Tween(begin: 0.0, end: 1.0) .animate(_iconRotationController!) : const AlwaysStoppedAnimation(0.0), child: FloatingActionButton( onPressed: _toggleText, backgroundColor: Colors.pinkAccent, child: const Icon(Icons.bolt), ), ), ), ); } } |
まとめ
FlutterはPythonで書いた資産を使えること、クロスプラットフォームでアプリを作れることがわかりました。そのため今回はFlutter開発環境の構築とサンプルアプリ制作を通してFlutterの概要を把握するところまで行いました。iOSやAndroidの環境構築を最初に行っておく必要があり、手順がかなりありましたがまずは動いてよかったと思います。
まだまだ実機端末によるテストやアプリ公開までの道のりはありますが、とりあえずFlutterを使ってアプリをテストする段階までは到達したと思います。今後はWATLABブログらしく音声系のIOを学んだり、機械学習モデルを読み込んで使ってみたりといった方向を探求していこうと思います。
モバイルアプリ制作はやっぱり楽しい!
Xでも関連情報をつぶやいているので、wat(@watlablog)のフォローお待ちしています!