🐬

Flutter の初期コードを1から理解する

2021/12/08に公開

はじめに

  • flutter初学者が初期で生成されるコード(カウントアプリ)を理解するだけの内容です。
    理解不足による間違った表現が記述されている可能性があります、その際は優しくご教示頂けますと幸いです。

  • 以下の初期コードを最小コードに分解し、各部品を付け足していく形で理解を深めます。

  • コード全体が長くなってしまうため、途中は変更した部分のみを載せています。
    実際に手を動かして確認したい方は、最小コード、初期コードと照らし合わせてどこが変化したのかを確認するとわかりやすいかもしれません。

初期コード(イメージ)

初期コード(main.dart)

main.dart(全体コード)
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

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

  final String title;

  
  _MyHomePageState createState() => _MyHomePageState();
}

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

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), 
    );
  }
}

STEP0(最小コード)

まずは、最小コードから見て行くこととします。
下記の最小コードを実行してみると、以下のような真っ黒な画面にHelloWorldが表示されます。

実際に、以下のコードが最小コードの全体像になります。

main.dart(全体コード)
// materialデザインが使えるようになるライブラリのインポート
import 'package:flutter/material.dart';

// メイン関数(最初に呼び出される関数, 1つのプログラムに1つ)
void main() {
// runAppでアプリの実行を行う(runAppには引数となるWidgetが必ず必要となる)
  runApp(
  // Centerクラス(Widgetを中心に配置させることができる)
    Center(
    // childプロパティ(単一のウィジェット), 今回はtextというWidgetを配置
      child: Text(
        'Hello, world!',
	// textDurectionプロパティ(childをどのように配置するかを指定),TextDirection.ltr: 左から順番に配置
        textDirection: TextDirection.ltr,
      ),
    ),
  );
}
  • main関数はファイル実行時に最初に呼び出されるエントリーポイントであるため、必ず必要となる。また、runAppには必ず引数となるWidgetが必要である。

  • この時に、テキストがどの方向に記入されているかを示すDirectionaly widgetがないとエラーを吐いてしまうため、気をつけないといけない。MateriAppを親に指定することでもDirectionを指定できるらしい。

  • textDirection: TextDirection.ltr : 左から順に配置

  • textDirection: TextDirection.rtl : 右から順に配置

https://flutternyumon.com/how-to-use-textdirection/#index_id2

STEP1(MyAppクラスを用いる)

先ほどは、main関数内に全てのWidgetを記述していたが、次はMyAppクラスを作成して、その中でrunApp関数に引数となるWidgetを渡していく。少し見た目が変わっているが、デフォルトのText表示がこのような見た目になっている為、前と内容自体に相違はない。これに関しては、Textのスタイルを指定することで解決ができる。

main.dart(全体コード)
import 'package:flutter/material.dart';

void main() {
  runApp(
    MyApp()
  );
}
// StatelessWidgetクラスという親クラスをMyAppに継承している
class MyApp extends StatelessWidget {
  //StatelessWidgetは、build関数をoverride(上書き)することで、MaterialApp内のWidgetにもStatelessな特性を持たせている
  
  // build関数: Widgetを実際に描画するためのメソッド
  Widget build(BuildContext context) {
    // MaterialApp: materialデザインを使用するためのWidget, mainに返される
    return MaterialApp(
      // home: アプリのホーム画面を形成, Centerで真ん中に以下を表示
      home: Center(
        // Text: テキストを表示
        child: Text(
          'Hello, world!',
        ),
      ),
    );
  }
}

override

オーバーライドとは、親クラスで定義されたメソッドを一部分だけ上書きをすることで子クラスで受け継いだメソッドを再定義することである
https://zenn.dev/semapho/articles/3e6b417b88187f

https://wa3.i-3-i.info/word138.html

StatefulWidgetとStatelessWidget

  • StatelessWidget: 状態を持たないWidget (text, iconなど)
  • StatefulWidget: 状態を持つWidget (buttonなど, 今回だとcounter部分)

https://tech-rise.net/difference-between-stateless-widget-and-stateful-widget/

STEP2(基本コードとScaffold)

Scaffoldを使用することできちんとした(?)「HelloWorld」を表示することができます。
また、ThemaData.dark()でダークモードとの切り替えも可能です。

main.dart(変更部分)
    return MaterialApp(
      // アプリの全体的なテーマ(色合い)を指定, ThemeData.dark()でダークモードにできる
      theme: ThemeData(),
      // Scaffold: アプリの基盤となるレイアウトを指定
      home: Scaffold(
        // Scaffoldの基本プロパティ, 他にはappBarやbackgroundColorなどがある
        // 今回はbodyのみであることから全体部分を指す
        body: Center(
          child: Text(
            'Hello, world!',
          ),
        ),
      ),
    );

Scaffold

今回の初期コードでは以下の基本プロパティが用いられている

  • appBar: トップバーを指定
  • body: コンテンツとなるWidgetを指定
  • floatingActionButton: その画面で行われる一般的な動作をボタンで指定

今回は使われていないが、よく使うので記載しておく

  • backgroundColor: body, または指定されたwidgetの背景色を指定

https://flutternyumon.com/how-to-use-scaffold/

STEP2.5(Scaffold)

先ほど説明したappBarを使用してみる
(floatingActionButtonはstatefulWidgetであるため後で実装する)

main.dart(変更部分)
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter Demo Home Page'),
        ),
      ),

STEP3(StatefulWidget)

StatefulWidgetとは、State(状態)を持つWidgetのことである。StatefulWidgetを継承したMyHomePageと、Stateを継承した_MyHomePageStateの2種類から構成されている。

https://tech-rise.net/why-stateful-widget-separates-two-classes/

https://tech-rise.net/what-is-lifecycle-of-state/#index_id0

AndroidStudioを使用している場合、stfと入力すると自動生成される以下のコードがstatefulwidgetの基本部分となる。

sample
// Statefulを継承することで、Stateが扱える
class MyHomePage extends StatefulWidget {
  // コンストラクター(インスタンス生成時に実行されるメソッド), 親Widgetから渡された値を変数に指定
  const MyHomePage({Key? key}) : super(key: key);
  // createState関数をオーバーライドすることで、Statefulな特性を持たせている
  
  _MyHomePageState createState() => _MyHomePageState();
}

// Stateを継承
class _MyHomePageState extends State<MyHomePage> {
  // オーバーライドすることでWidgetにStateを持たせている
  
  Widget build(BuildContext context) {
    return Container();
  }
}

実際に上記のStatefulWidgetを用いて、STEP2.5までのコードを書いていく

main.dart(全体コード)
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      // ホームをMyHomePage()としている, titleは引数
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  // コンストラクター, 親Widgetから渡されたtitleを変数に指定
  const MyHomePage({Key? key, required this.title}) : super(key: key);
  // 定数を宣言(受け取った文字列)
  final String title;
  
  _MyHomePageState createState() => _MyHomePageState();
}

// Stateを継承
class _MyHomePageState extends State<MyHomePage> {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        // widget.title: 受け取ったtitleを表示
        title: Text(widget.title),
      ),
      body: Center(
        child: Text(
          'You have pushed the button this many times:',
        ),
      ),
    );
  }
}

STEP3.5(State)

State: 可変する値を持つデータの状態を指す
上記のままだと可変部分はなく、Statelessなコードであるため、次はCounterを作っていく

main.dart(変更部分)
class _MyHomePageState extends State<MyHomePage> {
  // データの宣言( _: private変数を指す)
  int _counter = 0;

  // ボタンを押した時に呼び出されるメソッドを定義
  void _incrementCounter() {
    // setState: 値を変更するときに呼び出す(中身の_counterが変更される度に再buildされる)
    setState(() {
      // _counter = _counter + 1
      _counter++;
    });
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        // Column: Widgetを一列に縦に並べる
        child: Column(
          // MainAxisAlignment.center: y軸(縦)にかつ、真ん中(高さが中心になるよう)に並べる
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              // $変数 とすることで変数を文字列補間することができる
              '$_counter',
              // スタイルを指定, THeme.of(context)で初めに宣言したThemeDataを適用する
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      // floatingActionButton: その画面で行われる一般的な動作(今回はカウンター)をボタンで指定
      floatingActionButton: FloatingActionButton(
        // _incrementCounter関数を呼び出す
        onPressed: _incrementCounter,
        // アイコン等を長押しした際に表示されるテキストメッセージ
        tooltip: 'Increment',
        // アイコンの見た目部分
        child: Icon(Icons.add),
      ),
    );
  }
}

ColumnとRow

複数のWidgetを表示したい際にColumn、Rowを用いて一列に綺麗に並べることができる

  • Column: Widgetを縦に並べる
  • Row: Widgetを横に並べる

https://dev.classmethod.jp/articles/flutter_column_row_1/

https://www.eda-inc.jp/post-3866/

mainAxisAlignment

また、ColumnとRowを使う際には、mainAxisAlignmentを指定することが多い
Widgetをどこから、どういった間隔で配置するといったところを指定することができる

https://qiita.com/ikemura23/items/67af19b6cbf16fb0251a#mainaxisalignmentとは

さいごに

flutterを触り始めてみたけど、理解せずに書いている・初期コードの理解から始めたいという人向けに説明を書いてみました。これを書くにあたって、参考にしたサイト等は随時載せているので、そちらも参考に読んでいただけるとわかりやすいのかなと思います。
また、Javaを触ったことがある人であればDartは触れやすい言語なのかなと感じました。
逆に触ったことがない人であれば、言語の概念から理解をすると良いかなと思います。
https://poko-everyday.com/オブジェクト指向言語の特徴/

これを機会にflutterにより深く触れて、スマホアプリ開発をしてみたいと思います。

GitHubで編集を提案

Discussion