こんにちは。
アウトプット太郎です。
今日はFlutterで誰もが見たことのあるデモアプリのソースコードを自分の言葉で説明しながら、知見を深めていきたいと思います。
ソースコード全体
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
まずはこの部分。
import 'package:flutter/material.dart';
これはライブラリのインポートをしています。
dartファイルはそのファイル内に書かれていないことはimportを介して取得し、使用します。
今回の場合、その実態はFlutterSDK内部にあるmaterial.dartファイルになります。
material.dartをインポートすることでmaterial.dartが依存している他のライブラリも一緒にインポートされ、例えば後述するrunApp関数も使えるようになります。
お次はこれ。
void main() {
runApp(const MyApp());
}
main関数はプロジェクトに1つだけ存在し、プログラムはここから始まります。
runApp関数は引数として受け取ったWidgetをルートウィジェットとして初期化します。
続いてこちら。
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
先ほどrunAppでルートウィジェットとして設定されたMyAppクラスを定義しています。
このクラスはStatelessWidgetを継承しているため、内部のプロパティやウィジェットは不変(イミュータブル)です。
const MyApp({super.key});
この部分はMyAppのコンストラクタを定義しています。
コンストラクタは初期化関数で、dartではクラス名と同じ関数名であることが条件です。
(+戻り値の型が記載されていないこと)
名前付き引数としてsuperクラス(つまり継承元であるStatelessWidgetクラス)のkeyを渡しており、keyを使うことでそのウィジェットを一意のものとして識別できます。
@override
キーワードは継承元の関数の上書きを明示的にするアノテーションです。
これをつけておくことで、コンパイル時にチェックが走り、例えば継承元に同じ名称の関数がなければエラーになるため、リリース前に気づくことができます。
そして何を上書きしているかというとbuild関数を上書きしています。
こちらは引数にcontext(BuildContext型)を受け取り、Widget型を返す関数です。
contextととは、ざっくりと言えば、これまでの歴史を渡していると認識しています。
そうすることでどんなページから遷移してきたかや、前ページで持っていたデータに簡単にアクセスできるようになります。
(ここは別記事でもっと詳しくまとめたいと思います。)
返すWidgetはMaterialAppです。
こちらは先ほど述べた通り、MaterialAppクラスのコンストラクタを呼び出しています。
そして初期化する際にタイトルやテーマカラー、ホーム画面のプロパティを渡しています。
なおホーム画面はconstがついているので、MyHomePageインスタンスは不変になります。
どんどんいきましょう。お次はこの部分。
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
今度はMyAppで呼び出されるMyHomePageクラスを定義しています。
そしてこのクラスはStatefulWidgetを継承しています。
StatefulWidgetとは状態をもつWidget。
つまりプロパティや関数がユーザー操作や他のイベントによってで動的に変わる可能性があるWidgetです。
そして状態が変わる部分はStateオブジェクトとして持ちます。
逆にいうとStateオブジェクト以外の部分、プロパティやWidgetの構成は不変です。
要は、StatefulWidgetが出て来たら、状態が変わる部分は対になるStateオブジェクトで持つということです。今回の場合、MyHomePageクラス専用の状態を持つStateオブジェクトになります。
そしてStateはcreateState()というメソッドを持ち、それが実行されると、今度は_MyHomePageStateクラスのコンストラクタが呼び出されています。
createState()メソッドはStatefulWidgetの必須メソッドです。
では最後のブロック
_MyHomePageStateのクラスです。
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
extendsキーワードからMyHomePageウィジェット専用のStateクラスであることを示しています。
_(アンダースコア)をつけることでprivateクラスであることも分かります。
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
そしてこの部分でint型の_counterプロパティと、
void型のprivateメソッドを定義しています。
void型とは戻り値がないことを示す型です。
メソッド内では、更にsetStateメソッドを呼び出し、
その中で_counterプロパティの値を増加させています。
setStateメソッドはStateクラスが提供するメソッドで、状態が変更されたことをトリガーにFlutterフレームワークに通知を行い、変更されたStateが関わる範囲のbuildメソッドを再度実行し、再描画を行います。
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
では描画に関わるbuildメソッドですが、
returnとしてScaffoldというWidgetを返しています。
ScaffoldはMaterial Designの基本的なレイアウト構造を提供するウィジェットで、アプリケーションの標準的なビジュアル構造を構築するために使用されます。
またScaffoldはappBarやbodyなどのレイアウトを構築するプロパティを持ち、
今回であればappBarはヘッダー、bodyはメインエリア、floatingActionButtonは浮いているように見えるボタンを表示する内容が記載されています。
細かく1つずつは省略しますが、
FloatingActionButtonで定義されている
onPressed: _incrementCounter,
こちらによって、ボタンが押された時に_incrementCounterが呼び出され、setState内で状態が変更され、更にその通知がFlutterフレームワークに伝わることによって、buildメソッドが再描画されています。
いかがでしょうか。
たった1ページの初期アプリでも説明していくと色々な要素が詰まっていますね。
私的には自分の言葉で説明するために色々調べることになり、非常に勉強になりました。
今後はこのアプリに機能を追加していく形で色々な記事を書いていきたいと思います!
最後まで読んでいただきありがとうございました!