第5章

ボタンを押して画面を変える

ボタンを押すと数字が増えるアプリを作り、onPressedとsetStateで画面を更新する方法を学びます。

この章で学ぶこと

  • ボタンを押したときに処理を実行する方法
  • 画面に表示する値を「状態」として持つ方法
  • 状態を変えて画面を更新する方法

事前準備

先に「Flutterアプリの見た目を編集する」の章まで進め、作成した hello_app プロジェクトを開いてください。この章では、追加のパッケージや画像は使いません。

前の章では、ボタンを押すと debugPrint でコンソールに文字を表示しました。今回は、ボタンを押すたびに画面の数字が 0 → 1 → 2 と増えるアプリを作ります。

動くアプリを作る

lib/main.dart の内容を、次のコードにすべて置き換えて保存します。

import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: CounterPage(),
    );
  }
}

class CounterPage extends StatefulWidget {
  const CounterPage({super.key});

  @override
  State<CounterPage> createState() => _CounterPageState();
}

class _CounterPageState extends State<CounterPage> {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('ボタンで画面を変える'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('ボタンを押した回数'),
            Text(
              '$count 回',
              style: const TextStyle(fontSize: 40),
            ),
            const SizedBox(height: 24),
            ElevatedButton(
              onPressed: () {
                setState(() {
                  count = count + 1;
                });
              },
              child: const Text('1増やす'),
            ),
          ],
        ),
      ),
    );
  }
}

hello_app フォルダのターミナルで、次のコマンドを実行します。

flutter run -d chrome

すでにアプリを実行している場合は、今回はWidgetの構成を変えたので、ターミナルで大文字の R を入力してホットリスタートしてください。終了してから再実行しても構いません。

次の動作を確認しましょう。

  1. 最初は「0 回」と表示される
  2. 「1増やす」を押すと「1 回」になる
  3. もう一度押すと「2 回」になる

onPressedで押したときの処理を書く

ElevatedButton の onPressed には、ボタンを押したときに呼び出してほしい関数を渡します。このように、あとで呼び出される関数を コールバック と呼びます。

onPressed: () {
  setState(() {
    count = count + 1;
  });
},

() { ... } は、名前のない関数の書き方です。ここではボタンを押すたびに、中の処理が実行されます。count = count + 1; は、現在の count に1を足し、その結果を count に入れ直すという意味です。

なお、onPressed: null にするとボタンは無効になり、押せなくなります。

変化する値をStateに持たせる

画面に表示する回数のように、操作によって変わる値を 状態(state) と呼びます。今回の状態は、次の変数です。

int count = 0;

int は整数を表す型で、0 が初期値です。この変数は build の中ではなく、_CounterPageState の中に置いています。build の中で毎回 0 を代入すると、画面を作り直すたびに値が戻ってしまいます。

今回のコードでは、役割を次のように分けています。

  • MyApp:自分自身では変化する値を持たないので、StatelessWidget を使う
  • CounterPage:変化する値を扱う画面なので、StatefulWidget を使う
  • _CounterPageState:count を保持し、その値に応じた画面を build で作る

createState() は、CounterPage に対応する _CounterPageState を作るためのメソッドです。クラス名の先頭の _ は、Dartでは同じライブラリの中だけで使う名前であることを表します。

setStateで画面の更新を知らせる

変数に新しい値を代入するだけでは、その変更を理由に画面が更新されるとは限りません。setState の中で値を変えると、Flutterに状態の変化を知らせ、もう一度 build を実行するように依頼できます。

今回の流れは次のとおりです。

  1. ボタンを押すと onPressed の処理が呼ばれる
  2. setState の中で count が増える
  3. Flutterが build を再実行し、新しい値を使って表示を更新する

表示側の '$count 回' は、文字列に count の値を埋め込む書き方です。count が 3 なら「3 回」と表示されます。

Text(
  '$count 回',
  style: const TextStyle(fontSize: 40),
)

この Text は実行中に内容が変わるので、const Text(...) にはできません。一方、文字サイズなど固定の設定には const を付けられます。

ここで扱う状態はアプリの実行中に保持する値です。ホットリロードでは通常その値が残りますが、ホットリスタートやアプリの再起動では 0 に戻ります。終了後も値を残すには、別途保存する仕組みが必要です。

やってみよう:リセットボタンを追加する

「1増やす」ボタンの下に、数字を 0 に戻すボタンを追加しましょう。先ほどの Column の children 内で、ElevatedButton(...) の直後に次のコードを追加します。

const SizedBox(height: 12),
ElevatedButton(
  onPressed: () {
    setState(() {
      count = 0;
    });
  },
  child: const Text('リセット'),
),

保存したら、実行中のターミナルで小文字の r を入力してホットリロードします。「1増やす」を3回押してから「リセット」を押し、「0 回」に戻ることを確認してください。その後「1増やす」を押すと、再び「1 回」になります。

余裕があれば、次の変更にも挑戦してみましょう。

  • 1回押すごとに2増えるようにする。ボタンの文字も「2増やす」に変える
  • 回数の下に、5回以上なら「たくさん押しました!」、それ未満なら「押してみよう」と表示する

2つ目のヒントは、条件によって値を選ぶ 条件 ? 条件を満たすときの値 : 満たさないときの値 という書き方です。children に次の Text を追加できます。

Text(count >= 5 ? 'たくさん押しました!' : '押してみよう'),

うまく動かないとき

  • ボタンが押せない:onPressed が null になっていないか確認する
  • 数字が変わらない:setState の中で count を変更しているか確認する
  • 数字が元に戻る:int count = 0; を build の中に置いていないか確認する
  • const に関するエラーが出る:count を使う Text や、その親の Column などに const を付けていないか確認する
  • Widgetの種類を変更したあとにエラーが出る:ホットリスタートするか、アプリを終了して再実行する

まとめ

ボタンを押したときの処理は onPressed に書きます。画面に使う変化する値は State に持たせ、setState の中で更新します。同じ仕組みで、数字だけでなくメッセージや色なども操作に応じて変えられます。

参考資料