FlutterのStatelessWidget・StatefulWidgetについて解説!

ブログ

当プロジェクトではスマートフォンでゲーム開発を手掛けていますが、使用技術にFlutterを採用しています。

理由としては、マルチプラットフォーム対応であるというところが大きいですね。

ただ、Flutterで開発するにあたり最初に躓くポイントがWidgetの種類、特にStateless WidgetとStateless Widgetの違いだと思います。

今回はそんなわかりにくいと思われがちなこの2つのWidgetの違いについて解説してみようと思います。

StatelessWidget

flutter createしてできた最初のソースコードの抜粋を載せておきます。

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      ......)}

見ていただいてわかる通り、StatelessWidgetは単体のクラスで構成されており、以下のメソッドでWidgetをビルドします。

 Widget build(BuildContext context) {
    return MaterialApp(
      ......)}

StatelessWidgetはその名前にある通り、「状態を持たないウィジェット」であるとよく説明されます。

ですが、例えば以下のようにメンバ変数を持つことはできるわけです。

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  final String title; // ←タイトルを表すメンバ変数を追加してみる
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      ......)} 

じゃあ、状態を持っているじゃん!ってなりそうですよね(というか自分がそうなった)。

というわけで、StatefulWidgetを見てみましょう。

StatefulWidget

StatefulWidgetは以下のように2クラスが必ずセットになります。

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

  // 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++;
    });
  }

(参考になると思ったので、あえてコメントを残しておきます)

構成されているクラスは以下の2つになります。

  • MyHomePage:StatefulWidgetクラスを継承
  • _MyHomePageState:Stateクラスを継承

ちなみに、_(アンダースコア)はアクセス修飾子(public・privateとか記述してその要素のアクセス可否を制御する)で、この修飾子をつけると今記述しているファイル外からのアクセスができなくなります。

そしてStatefulWidget(今回のケースはMyHomePage)ですが、以下のコメントがかなりわかりやすい。

// 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".

こちらのコメントを要約すると、

  • このクラスは状態オブジェクト(今回のケースは_MyHomePageState)を持つことができる
  • 状態オブジェクトは、アプリの見映えに関わる変数を持つことができる
  • このクラスは状態オブジェクトの設定を行う。
  • 親ウィジェットから受け取った値を持つことができ、この値は必ずfinal(=再代入できない)となる。

この説明から分かる通り、StatefulWidget(とStatelessWidget)にあるようなfinalで定義されるメンバ変数(final String title;)はあくまでも状態ではなく、親ウィジェットから受け取った値であることがわかります。

また、finalがついていることからこのメンバ変数は再代入ができないです。自分が考えていた「状態」とは少しイメージが違いましたね。

あくまで「状態」とはStateクラスを継承したクラスについていうもののようです。

State

では、「状態」を表すState(を継承した)クラスを見てみましょう。

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

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

ここでもコメントがついており、わかりやすいと思ったので解説してみます。

      // 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.

こちらも要約すると以下のようになります。

  • このクラス内にあるメソッドのsetStateはFlutterのフレームワークにクラス内に何か変化があったことを通知する
  • 通知を受けたFlutterのフレームワークはbuildメソッドを再度走らせることでリビルドして見た目を更新する。
  • setState以外で値を更新しても、再ビルドしないため見た目は変化しない。

つまり、_counterをsetStateで変更したら再度画面をリビルドし状態の変更を反映しているようです。

まとめ

上記の説明から分かる通り、StatelessWidgetとStatefulWidgetは以下の違いがあるようです。

  • StatelessWidget:親ウィジェットから値を受け取ることはあるが、画面の再ビルドを行わない。
  • StatefulWidget:setStateメソッドを介して、画面の再ビルドを行うことができる。

このようにStatefulWidgetは一口に「状態を管理する」と言っても意味合いは「状態を持って、更新したら画面の再ビルドを行う」が実際の意味のようです。

コメント