フューチャー技術ブログ

Flutterの使いかた、環境構築から実装、ビルドまで

TIGの伊藤真彦です。

フロントエンドアプリケーションの開発においてVue、Reactと触ってきましたがFlutterの開発も始めました。モバイルアプリ、WEBアプリ、更にはデスクトップアプリへの高いポータビリティが期待できる、Adobe XDでのデザインを高速にアプリケーションに反映できる、といった期待値が高いことが選定の理由です。

フューチャーではDart/Flutter連載で様々なネタを取り上げましたが、汎用的なハウツー記事が意外と少なかったため書いてみました。

Flutterとは

FlutterはDart言語で実装されたアプリケーションフレームワークです。

元々はAndroid、iOS向けのモバイルアプリケーションを実装するためのSDKでしたが、Flutter on the webFlutter on Desktopの公開により、Flutterで作っておけばどのようなプラットフォームであってもアプリケーションをリリースできる、という環境が整備されました。

執筆時点の2021年末ではFlutter on Desktopはまだリリースから日が浅いですが、モバイルでもPCでも使える共通コンポーネントを作りたい、というニーズを強力にサポートしてくれる事が期待できます。

Flutterのインストール

Flutterは公式サイトからSDKをインストールすることでflutterコマンドが利用できるようになります。flutterコマンドはアプリケーションの初期構築、起動、依存モジュールの管理を行うことができます、npmコマンド+任意のフレームワークのCLIコマンドのような存在ですね。

インストールページから各OS向けのSDKがダウンロードできます。2021年現在ではリッチなインストーラではなく、ダウンロードしたファイル一式を解凍後、任意の場所に配置し、自力でパスを通す必要があります。

下記のようなコマンドを.bash_profile等のファイルに記載してください。

export PATH="$PATH:[flutterフォルダが格納されているディレクトリ]/flutter/bin"

ファイルの中身は単一のバイナリではなく、サンプルコードやREADMEなど一式揃っており一瞬驚きますが、それら全てが必要です。

パスを通すとflutterコマンドが利用できるようになります。

$ flutter --version
Flutter 2.5.3 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 18116933e7 (9 weeks ago) • 2021-10-15 10:46:35 -0700
Engine • revision d3ea636dc5
Tools • Dart 2.14.4

doctorコマンドの利用

Flutterにはdoctorコマンドというものが存在します。これは各プラットフォームにおける環境構築が終わっているかを確認できるコマンドです。

Flutterでモバイルアプリケーションを開発する場合、iOSではXcode、AndroidではAndroid Studioを利用してアプリケーションをビルド、デバッグすることになります。これら必要な開発ツールが揃っているかを確認し、何が必要かを教えてくれるのがdoctorコマンドです。

flutterのインストールが終わったら、doctorコマンドを実行し、開発したいプラットフォーム向けの準備が終わっているかを確認しましょう。私はAndroid, iOS向けアプリケーションの開発経験が既にあったため、Android StudioのConfigを少し変更するだけで下記のように全ての準備が終わりました。

$ flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 2.5.3, on Mac OS X 10.15.7 19H15 darwin-x64, locale ja-JP)
[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
[✓] Xcode - develop for iOS and macOS
[✓] Chrome - develop for the web
[✓] Android Studio (version 4.2)
[✓] VS Code (version 1.62.3)
[✓] Proxy Configuration
[✓] Connected device (1 available)

アプリケーションを新規作成する

環境構築が終わったら、アプリケーションを作成します。

どのような開発経験の方でも手早く試せるため、Flutter on the Webでの開発前提で説明します。Flutterはflutter createコマンドで、アプリケーションの雛形を生成することができます。
Flutterのプロジェクト名は-を含めることができません。

Androidアプリケーションとして公開する際のapplicationIdの命名規約に抵触することから-が利用できないようです。多くのflutter向けOSSパッケージは_区切りのスネークケースなので、それに倣うのが良いでしょう。

flutter create hello_world

コマンドを実行すると様々なファイルが自動生成され、下記のような案内が表示されます。

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

$ cd hello_world
$ flutter run

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

指示通りにコマンドを実行するとサンプルアプリケーションが起動します。
Flutter on the Webが存在するおかげで、Xcode、Android Studioが無い状態でもとりあえず動かすことは可能です。

$ cd hello_world
$ flutter run

よくある公式ロゴ入りのHello Worldページではなく、ボタンを押すとカウンタの数が増えるというインタラクティブなデモアプリが起動するのが特徴的です。

サンプルアプリ画像

VueやReactに慣れているとlocalhost:3000localhost:8080で起動しないのが違和感を覚えますが、Flutter on the Webの場合空いている適当なポートで起動します。

ポート番号を指定することも可能です。

flutter run --web-port 8080

モバイル、デスクトップの開発環境が整備されている場合、-dオプションで起動するプラットフォームを選択できます。allで実行可能な全てのプラットフォーム向けに同時起動できます、楽しいですね。

flutter run -d all

アプリケーションを開発するための知識

アプリケーションを起動することができたら、デモアプリケーションを編集し、任意のアプリケーションを開発していきます。

lib/main.dartを編集することでアプリケーションを開発できます。

生成されたアプリケーションのmain.dartを見てみます。

main.dart
import 'package:flutter/material.dart';

void main() {
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);

// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);

// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.

// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".

final String title;

@override
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;

void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}

@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column(
// Column is also a layout widget. It takes a list of children and
// arranges them vertically. By default, it sizes itself to fit its
// children horizontally, and tries to be as tall as its parent.
//
// Invoke "debug painting" (press "p" in the console, choose the
// "Toggle Debug Paint" action from the Flutter Inspector in Android
// Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
// to see the wireframe for each widget.
//
// Column has various properties to control how it sizes itself and
// how it positions its children. Here we use mainAxisAlignment to
// center the children vertically; the main axis here is the vertical
// axis because Columns are vertical (the cross axis would be
// horizontal).
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}

Flutterアプリケーションは最初に説明した通りDartで実装されたアプリケーションであり、更にはそれがFlutterアプリとして抽象化されているため、慣れるまでは少し時間がかかるかもしれません。

まずは雰囲気で最初に表示された画面のコンポーネントと、画面の状態を管理するステート、ボタンを操作した際にカウンタをインクリメントする関数が書いてあることを感じてみるくらいの所からスタートすることになるかなと思います。

ここからDartの文法、Flutterのお作法を学んでいきましょう。

Widgetについて

Flutterアプリケーションは画面のコンポーネントをWidgetという単位で開発します。

モバイルアプリが出自であるため、Vue、ReactなどHTMLを意識したコンポーネントの書き方に慣れたWEBフロントエンジニア出身の人よりはSwiftのようなモバイルアプリ開発に慣れている方の方が親しみを覚えやすい書き方をする事になります。

Flutter公式ドキュメントからどのようなWidgetが存在しているのかを一通り眺めておくとやりたい事と出来る事のイメージが掴みやすいです。BasicsLayoutTextあたりから攻めてみて、サーバーサイドから取得した情報を取得したい、といった要望に応えるためにAsync Widgetsあたりに早期に触れるような流れが自然でしょうか。

Flutterレイアウト入門Flutter ウィジェットテスト入門も合わせてお読みください。

Stateについて

他のフレームワークでのフロントエンドアプリケーションの開発で馴染みがある人がいるかもしれませんが、Stateは日本語にすると「状態」を意味します。WidgetのStateとは文字通りWidgetが持っている状態の変化を管理するための概念です。ユーザーの操作によって変更が起こりうる要素、外部APIから取得したデータなど、動的に変化する情報をStateとして扱い、管理します。

デモアプリではユーザーがボタンを押した時に画面中央のカウントが更新される部分が該当します。int _counter = 0;で宣言した変数_counterにカウント情報を保存し、状態を更新し、画面を再描画するために、setState()関数の内部で変数の値を更新する関数、_incrementCounter()が実装されています。

Reactなど近年のフロントエンド フレームワークでは、画面の状態を管理することで、状態が更新された場合コンポーネント単位での再描画を行う、更新のないコンポーネントは再描画しない、という必要最小限の画面更新を行う仕組みでアプリケーションのパフォーマンスが高くなるように設計されています。Flutterもその仕組みを採用しており、状態管理を行っているということですね。FlutterではStateを持たないStatelessWidget、State管理の仕組みを備えたStatefulWidgetの2種類のWidgetが存在します。

状態管理については武田さんのアドベントカレンダー記事に詳しいです。

パッケージ管理について

デモアプリから発展して高度なアプリケーションを開発するにあたり、何らかのライブラリを導入する事になるでしょう。

FlutterではFlutterに向けて作られたものは勿論、Dart向けのライブラリをインポートできます。Flutterアプリでは依存モジュールをpubspec.yamlというファイルで管理しています。Node.jsでいうところのpackage.json、Goでいうところのgo.modのようなファイルです。ライブラリを追加、削除するにはflutter pubコマンドを利用します。

例えばFlutterでは拡大、縮小可能な画像を表示するphoto_viewパッケージがあります。インポートするだけで画像をカッコよく表示するWidgetが使えるようになります。

flutter pub addコマンドでパッケージをインストールします。Dartパッケージのサイトでは簡単なコマンドであってもインストールの手法実装サンプルが整備されています。

サンプルだけでなく、APIリファレンスも見れば使い方は概ね分かると思います。

$ flutter pub add photo_view

パッケージをインストールし、import行を追加することで、パッケージの機能やWidgetが使えるようになります。

import 'package:photo_view/photo_view.dart';

photo_viewをimportするとWidgetとしてPhotoView()を利用できるようになります。

main.dart
 @override
Widget build(BuildContext context) {
return PhotoView(imageProvider: AssetImage("assets/large-image.jpg"));
}

実際にはレイアウトを調整するため他のWidgetと組み合わせて使うことになると思いますが、Flutterではこのような形でサードパーティのモジュールを簡単に導入することができます。

導入したパッケージを削除する場合はremoveコマンドが利用できます。

dart pub remove photo_view

他の人が作成したFlutterアプリケーションをgit cloneするなどの形で開発する場合は、flutter pub getコマンドで依存モジュールをダウンロードできます。

flutter pub get

アプリケーションのビルド

アプリケーションが完成したら、対象のプラットフォーム向けにビルドします。

ビルドにおいてもスマートフォンアプリとしてビルドする場合はXcode, Android Studioでの環境構築が必要です。ここでもFlutter on the Webでの例を記載します。

アプリケーションのビルドはflutter buildコマンドで行います。

flutter build web

対象プラットフォームを指定してビルドコマンドを実行すると、ビルドが開始されます。

$ flutter build web

💪 Building with sound null safety 💪

Compiling lib/main.dart for the Web...

コマンド実行が成功するとbuildフォルダに成果物がビルドされます。

image.png

webフォルダの中身にはindex.htmlをはじめ各種ファイルが生成されています。

image.png

これら一式をFirebaseGitHub Pages静的サイトとしてホスティングできるサービスにそのまま配置するような使い方でFlutterアプリをデプロイできます。

ビルドされたindex.htmlをそのままダブルクリックしてブラウザで開いても正常に動作しません。これはmain.dart.jsがサーバーでホストされている前提で動こうとするためです。

実際にデプロイしてみても良いですが、環境を用意するのが面倒な場合は、Goのサーバーの管理画面をFlutter Webで作ってみるための調査の「Goのアプリケーションに組み込む」のようにlocalhostでホスティングするような手法で試すこともできます。

まとめ

Flutterの環境構築から開発手法、ビルドまでの流れを紹介しました。

高度なアプリケーション開発のtipsやAndroid、 iOS向け、更には各OSのデスクトップアプリでの環境構築、ビルド、リリース手法など深堀りする余地はたくさんありますが、ひとまず流れとしては以上になります。