【初心者向け】Flutterアプリを作ってStatefulWidgetを学ぶ

前回作成した「Hello World」アプリではStatelessWidgetを継承して作成していました。

Flutterで”Hello World”アプリを実行するまでと解説

StatelessWidgetとは状態を持たないウィジェットです。

一度実行すると変化することがない、変える必要がある場合は再度実行する必要があります。

今回は状態を持つウィジェット、StatefulWidgetを継承したアプリを作成してみます。

StatefulWidgetとは?

StatefulWidgetとは状態を持つウィジェットです。

状態を持つウィジェットを実装するには、少なくとも2つのクラスが必要です。

  • StatefulWidgetクラス
  • Stateクラス

インスタンスを生成するStatefulWidgetクラス自体はStatelessWidgetと同様で状態を保持しておらず、Stateクラスが状態の保持や複雑な処理を行います。

サンプルアプリを作ってみる

アプリの概要

「Hello World」アプリを拡張して、次のステップでアプリを作ってみます。

  1. ランダム文字列を生成するライブラリをインストール
  2. StatefulWidgetの雛形を作成する
  3. 「Hello World」の代わりにランダム文字列を表示する

実装

‘Hello World’をランダム文字列に変更

始めに事前準備として、ランダム文字列を生成するライブラリをインストールします。

pubspec.yamlファイルを開き、english_words: ^3.1.5を追記します。


dependencies:
  flutter:
    sdk: flutter
  english_words: ^3.1.5 # 追記

ファイルを保存したら、「Android Studio」上部にあるPub getリンクをクリックすると、english_wordsライブラリのインストールが始まります。

main.dartファイルを開き、english_wordsをimportしたら準備完了です。


import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart'; // 追記

次にRandomWordsというStatefulWidgetを追加します。


class RandomWords extends StatefulWidget {
  @override
  _RandomWordsState createState() => _RandomWordsState();
}

class _RandomWordsState extends State<RandomWords> {
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

「Andoroid Studio」では「stful」と入力した状態でEnterを押すと簡単にStatefulWidgetの雛形が作成できます。

_RandomWordsStateクラスのbuild内にランダム文字列を生成する処理を追記します。


class _RandomWordsState extends State<RandomWords> {
  @override
  Widget build(BuildContext context) {
    final wordPair = WordPair.random();
    return Text(wordPair.asPascalCase);
  }
}

body部分の’Hello World’のTextをRandomWordsウィジェットに置き換えると完了です。


body: Center(
  // child: Text('Hello World'),
  child: RandomWords(),
),

以下がmain.dartの全文になります。


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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Welcome to Flutter',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Welcome to Flutter'),
        ),
        body: Center(
          child: RandomWords(),
        ),
      ),
    );
  }
}

class RandomWords extends StatefulWidget {
  @override
  _RandomWordsState createState() => _RandomWordsState();
}

class _RandomWordsState extends State<RandomWords> {
  @override
  Widget build(BuildContext context) {
    final wordPair = WordPair.random();
    return Text(wordPair.asPascalCase);
  }
}

この状態で実行すると’Hello World’の変わりにランダムな文字列が表示されます。

RandoWordsウィジェットをリファクタリング

今のままだとリスタートしなければ文字列が切り替わらないので、タップすると文字列が切り替わるボタンを追加します。

その前にRandoWordsウィジェットを拡張するためにリファクタリングを先に行います。


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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Welcome to Flutter',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Welcome to Flutter'),
        ),
        body: RandomWords(),
      ),
    );
  }
}

class RandomWords extends StatefulWidget {
  @override
  _RandomWordsState createState() => _RandomWordsState();
}

class _RandomWordsState extends State<RandomWords> {
  String randomWord;

  _RandomWordsState() {
    generateRandomWord();
  }

  generateRandomWord() {
    final wordPair = WordPair.random();
    randomWord = wordPair.asPascalCase;
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Text(randomWord),
        ],
      ),
    );
  }
}

body部分のウィジェットを_RandomWordsStateウィジェットに移動し、Centerウィジェットの中にColumnウィジェットを追加しました。

Columnウィジェットはchildrenに複数のウィジェットを指定することができ、指定されたウィジェットは縦に並んで表示されます。

Columnウィジェットはデフォルト状態だと画面の最大高さまで広がってしまうので、mainAxisSize: MainAxisSize.minで縦方向のサイズを最小に指定します。

また、ランダム文字列の生成は複数回実行されることになるので、generateRandomWordメソッドを作成し、そこに生成処理を移行しています。

generateRandomWordメソッドは呼び出されるたびにrandomWordにランダムな文字列が代入されるようになっています。

実行時は初期化のためにコンストラクタ(_RandomWordsState)で1度だけ呼び出されます。

ランダム文字列を生成ボタンを追加

次にボタンを設置しますが、今回はRaisedButtonウィジェットを利用します。

RaisedButton class – material library – Dart API

childrenにRaisedButtonウィジェットを追加します。


children: [
  Text(randomWord),
  RaisedButton(
    onPressed: () {},
    child: Text('generate'),
  ),
],

これでボタンが追加された状態になりますが、ボタンをタップしても何も起こりません。

最後にボタンをタップしたときの処理をonPressedに加えます。


children: [
  Text(randomWord),
  RaisedButton(
    onPressed: () {
      setState(() {
        generateRandomWord();
      });
    },
    child: Text('generate'),
  ),
],

onPressed内の処理はgenerateRandomWordを実行するだけですが、setStateメソッドで囲われています。

setStateメソッドはオブジェクトの状態が更新されたことを通知する役割を担っています。

setStateメソッドを介さないとrandomWordへの代入処理が反映されないので注意しましょう。

以下が完成したmain.dartの全文です。


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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Welcome to Flutter',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Welcome to Flutter'),
        ),
        body: RandomWords(),
      ),
    );
  }
}

class RandomWords extends StatefulWidget {
  @override
  _RandomWordsState createState() => _RandomWordsState();
}

class _RandomWordsState extends State<RandomWords> {
  String randomWord;

  _RandomWordsState() {
    generateRandomWord();
  }

  generateRandomWord() {
    final wordPair = WordPair.random();
    randomWord = wordPair.asPascalCase;
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Text(randomWord),
          RaisedButton(
            onPressed: () {
              setState(() {
                generateRandomWord();
              });
            },
            child: Text('generate'),
          ),
        ],
      ),
    );
  }
}

実行

これでボタンをタップするたびに文字列が生成されるので、実行してみます。

無事に文字列が切り替われば成功です。

ABOUTこの記事をかいた人

Yusuke Ito

■バックエンド開発(TypeScript、PHP etc)をメインに活動し、2020年からFlutterによるアプリ開発案件に参画し、多様なニーズに応えれるエンジニアを目指す。 ■エンジニア目線でより良い提案、目的に合わせた仕様の提案が得意です。 ■Flutterに関する技術、個人アプリの開発、キャリア、書評をメインに発信しています。 ■筋トレ、ゴルフ、読書が趣味