今回はFlutterのmain.dart
を見ていこうと思います
main.dart
を見ることで一連の流れやどこに何を書いていけばいいのか理解出来ると思ったので、まとめてみようと思いました
早速、見てみましょう!!
main.dart
は下記です
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { // 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: MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key key, 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 _MyHomePageState 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>[ Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.display1, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), // This trailing comma makes auto-formatting nicer for build methods. ); } }
大まかに3〜4箇所に分けられるので、それぞれ説明できればと思います
main
void main() => runApp(MyApp());
main()
はアプリケーションのエントリーポイント(最初に続行されるプログラム)となる関数です
Javaを触ったことある人なら馴染みがあるかもしれません
runApp()
は実装されているWidgetを画面に描画します(Widgetは後で補足します)
MyApp()はWidgetを返却するクラス。これはアプリケーションコードとして自動生成されるので、Widget型を返却すれば名前は何でも大丈夫です
Widget
次はWidget
についてです
class MyApp extends StatelessWidget { // 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: MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key key, 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 _MyHomePageState 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++; }); }
その前にWidget
とは何かというところですが、簡単に言うとアプリの「見た目に関わる部分」を構成する部品(パーツ)です
そのWidget
のなかにStatelessWidget
とStatefulWidget
の2種類がありますがこの違いはなんでしょうか?
StatelessWidget
で扱う値はすべて不変的でありプロパティを変更することはできず、すべての値はfinalな値となります
StatefulWidget
ではWidget
の生存期間中に変更される値を維持することができます
なので一例ですが、StatelessWidget
ではボタンの固定の文言を定義して、StatefulWidget
では場合によって変わる文言を定義したりします
固定文言等の状態を持たないものはStatelessWidget
が継承されている箇所に実装すれば良いです
一方の状態を保持するものはStateクラスのインスタンスを作成するStatefulWidgetクラス
とStateクラス
の2種類が必要になります
今回のmain.dart
では、StatefulWidget
はStatefulWidgetを継承したMyHomePageと、Stateを継承した_MyHomePageStateの2種類から構成されています
2種類で構成されているのは、StatefulWidget
は不変であるWidget
を継承していて、StatefulWidget
自体は不変である必要があるからです
しかし、StatefulWidget
は可変である必要があるため、State
に可変の要素を持たせています
これを元にStatefulWidget
にコメントを加えたいと思います
class MyHomePage extends StatefulWidget { // コンストラクター。クラス名({this.変数名})とすることで自動的に渡された親Widgetから渡された値を変数に設定する MyHomePage({Key key, this.title}) : super(key: key); // 親Widgetから不変の値を受け取る場合、finalを宣言する final String title; // ステートクラスをインスタンス化する。 @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int _counter = 0; // 変数の値を変更する際、setStateを必ず呼ぶ 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++; }); }
ここから、StatefulWidget
の特徴は以下の5つがあることがわかります
①親Widgetから不変の値を受け取る場合、finalを宣言する
②コンストラクターで変数に値を設定
➂Stateクラスをインスタンス化する
➃Stateクラスで変数の値を更新する場合は、setStateを呼ぶ
➄親Widgetで受け取った変数にアクセスする場合、widget.変数名を用いる
また、State<MyHomePage>
とすることでMyHomePageの変数にアクセス出来るようになっています
build
最後に、build関数は描画するWidgetを返します
@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>[ Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.display1, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), // This trailing comma makes auto-formatting nicer for build methods. ); } }
コメントを見るとわかるようにsetState
が呼ばれるたびにbuild関数が実行されます(StatefulWidget
の場合)
全体の更新ではなく、更新する部分だけの更新でパフォーマンスの向上を図っているようです
StatelessWidget
でも描画するWidgetを返す場合はbuild関数を使います
Scaffold
は色々なWidgetを受け取り、それをマテリアルデザインに準拠したレイアウトで配置します
親子関係
最後に何回か親Widgetという表現が出てきていますが、これは一体どういうことなんでしょうか?
それは先ほどのbuildのコードを見るとわかります
@override Widget build(BuildContext context) { 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>[ Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.display1, ), ], ), ),
ソースをざっと見るとbodyのchildがColumnになっていると思います
なので、bodyが親でColumnが子の関係にあります
また、そこからさらにchildren:
このようにWidgetは親子関係になり普通に実装すると、ネストが深くなります
まとめ
main.dart
を見るだけでもFlutterのルールなどがわかります
また、Reactなどが似たような思想を持っている部分もあるので、勉強してみるのもいいかもしれないです
何より「全てはWidget」と言われるほど、Widgetの考えは根幹になると思うので、理解するのが第一だと感じました