【Flutter】StatelessWidgetとStatefulWidgetの違い|ウィジェットを自作

今回はStatelessWidgetStatefulWidgetの違いについて紹介します。

またStatelessWidgetStatefulWidgetを使ってWidgetを自作する方法も紹介していきます。

\ 世界最大級のオンライン学習サービス /

目次

WidgetはStatelessまたはStatefulに分けられる

StatelessWidgetStatefulWidgetの違いを理解していく前に知っておきたいのが、Widgetは「状態を持たないWidget(Stateless)」と「状態をもつWidget(Stateful)」のいずれかに分けられる点です。

状態(State)とはWidgetが変化するか表すもので、Widgetの描画がUI(アプリのスクリーン上)で変化しないのが「Stateless」、変化するのが「Stateful」です。

よって描画が変わらないTextElevatedButtonは状態を持たないWidget(Stateless)で、描画が変わるTextFieldCheckboxは状態を持つWidget(Stateful)になります。

A widget is either stateful or stateless. If a widget can change—when a user interacts with it, for example—it’s stateful.


stateless widget never changes. IconIconButton, and Text are examples of stateless widgets. Stateless widgets subclass StatelessWidget.


stateful widget is dynamic: for example, it can change its appearance in response to events triggered by user interactions or when it receives data. CheckboxRadioSliderInkWellForm, and TextField are examples of stateful widgets. Stateful widgets subclass StatefulWidget.

引用元:Flutter|Stateful and stateless widgets

StatelessWidgetとStatelessWidgetの違い

先ほど述べた点を踏まえStatelessWidgetStatefulWidgetの違いをまとめると、状態が変化しないのがStatelessWidget、状態が変化するのがStatefulWidgetです。

Textなどの状態を持たないWidgetもStatefulになり得る

StatelessWidgetを作る際に注意したいのが、Textなどの状態を持たないWidgetも状態をもつWidgetになり得る点です。

下記コードではTextの文字列に変数textが使用されており、ElevatedButtonがタップされると変数のデフォルト値「Hello World」に新しい値「I am Flutter」が代入されます。

ElevatedButtonがタップされるとprintによりコンソール上で「I am Flutter」が出力されますがUI(アプリのスクリーン)では変化がありません。

なぜなら状態を持たないStatelessWidgetを使用しているからです。Textなどの状態を持たないWidgetでも今回のように状態が変化してしまう場合はStatefulWidgetを使用する必要があります。

class MyApp extends StatelessWidget {
  var text = 'Hello World';
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('FlutterZero')),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text(text, style: TextStyle(fontSize: 30)),
              SizedBox(height: 20),
              ElevatedButton(
                onPressed: () {
                  text = 'I am Flutter';
                  print(text);
                },
                child: Text('Click'),
              )
            ],
          ),
        ),
      ),
    );
  }
}

StatelessWidgetの作り方|ウィジェットを自作

StatelessWidgetの作り方はシンプルです。

Widget(クラス)を作成する際にStatelessWidgetを継承し、@overridebuildを上書きするだけです。StatelessWidgetでは状態が変化しないので「変数」は全て「定数」として定義します。

class MyApp extends StatelessWidget {
  final text = 'Hello World';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('FlutterZero')),
        body: Center(
          child: Text(text, style: TextStyle(fontSize: 30)),
        ),
      ),
    );
  }
}

StatelessWidgetの例外|プロバイダの使用

ここまでStatelessWidgetは状態が変化しないWidgetと述べてきましたが例外もあります。

ProviderRiverpodなどの状態管理Widgetを使用することでStatelessWidgetでもStatefulWidgetのような状態を変化できるWidgetを作ることが可能です。

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

class CounterNotifier extends ChangeNotifier {
  int _count = 0;

  void add() {
    _count++;
    notifyListeners();
  }

  void subtract() {
    _count--;
    notifyListeners();
  }
}

final counterProvider = ChangeNotifierProvider(
  (ref) => CounterNotifier(),
);

void main() => runApp(ProviderScope(child: MyApp()));

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('FlutterZero')),
        body: Center(
          child: Consumer(
            builder: (context, ref, child) {
              final count = ref.watch(counterProvider);
              return Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  IconButton(
                    icon: Icon(Icons.remove),
                    onPressed: () => count.subtract(),
                  ),
                  Text(
                    count._count.toString(),
                    style: TextStyle(fontSize: 30),
                  ),
                  IconButton(
                    icon: Icon(Icons.add),
                    onPressed: () => count.add(),
                  ),
                ],
              );
            },
          ),
        ),
      )
    );
  }
}

StatefulWidgetの作り方|ウィジェットを自作

StatefulWidgetを作るには、StatefulWidgetを継承したクラスとStateを継承したクラスの2つのクラスが必要です。

1つ目のクラスの定義|StatefulWidgetがサブクラス

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

  @override
  State<MyApp> createState() => _MyAppState();
}

StatefulWidgetを継承したクラスではcreateStateStateを継承したクラスを返り値に渡し、状態の変化をUIに反映させます。

上記コードの場合_MyAppStateで状態が変化する度にUIに反映されます。

2つ目のクラスの定義|Stateがサブクラス

class _MyAppState extends State<MyApp> {
  String text = 'Hello World';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('FlutterZero')),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text(text, style: TextStyle(fontSize: 30)),
              SizedBox(height: 20),
              ElevatedButton(
                onPressed: () => setState(() {
                  text = 'I am Flutter';
                  print(text);
                }),
                child: Text('Click'),
              )
            ],
          ),
        ),
      ),
    );
  }
}

Stateを継承するクラスはStatelessWidgetと同様に定義できます。

一方でStateを継承するクラスでは、状態が変化する処理をsetState内に書くことで処理が実行された後、再度ビルドし状態を変化できます。

一緒に読みたい

参考

  • URLをコピーしました!
目次