0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Flutterでパフォーマンス最適化:クラス分けとconstの活用術

Last updated at Posted at 2024-10-23

はじめに

Flutterでは、setState()を使うと、build()メソッドが再実行され、ウィジェットツリー内のすべてのウィジェットが再生成されます。アプリが複雑になると、こうした無駄な再ビルドがパフォーマンス低下の原因になります。

そこで、この記事では、「クラス分け」と「constの活用」を使って、無駄な再ビルドを防ぐ最適化テクニックを解説します。


1. setState()による再ビルドの仕組み

setState()が呼ばれると、そのStateクラスのbuild()メソッドが再実行され、ツリー内のすべてのウィジェットが再生成されます。

例えば、次のようにカウンターの値が変わるたびに、CounterPagebuild()が再実行されます。

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

このような無駄な再ビルドを減らすことが、Flutterアプリのパフォーマンスを向上させるために重要です。


2. constの役割と制限

2.1 constの役割

constを使うと、Flutterは「このウィジェットは変わらない」と判断し、再生成をスキップします。

親ウィジェットのbuild()が再実行されても、「このウィジェットは前回と同じ」と判断できれば、再描画がスキップされます


2.2 constの制限

ただし、constは状態に依存しないウィジェットでしか使えません。

例えば、_counterのような変数が含まれるウィジェットにはconstを付けられません。

// ❌ エラーになります
const Text('$_counter');  // 状態に依存するためconst不可

また、同じbuild()内にconstを使っても、Flutterは毎回「このウィジェットが同じかどうか」を判断しなければなりません。これが、「クラス分けが必要な理由」です。

以下に詳細をまとめました。


3. なぜクラス分けが必要なのか?

クラス分けをすることで、Flutterが「このウィジェットは変わらない」という判断を1回だけで済ませられるようになります。

具体的には、クラスごとにconstを付けることで、親ウィジェットのsetState()が呼ばれても、そのクラス全体を再生成しなくて済むようになります。

クラス分けのメリット

  • クラス分けをしない場合:
    • setState()が呼ばれるたびに、build()内のウィジェットが再生成されます。
    • constを付けていても、毎回「同じかどうか」の判断コストが発生します。
  • クラス分けをした場合:
    • const付きのクラスはFlutterが1回だけ「同じ」と判断し、再描画も再生成も完全にスキップします。

4. コード例:クラス分けによる最適化

改善前:クラス分けしていない場合

class CounterPage extends StatefulWidget {
  @override
  _CounterPageState createState() => _CounterPageState();
}

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

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

  @override
  Widget build(BuildContext context) {
    print('CounterPage build');
    return Scaffold(
      appBar: AppBar(title: const Text('Counter')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('$_counter', style: const TextStyle(fontSize: 48)),
            const SizedBox(height: 20),
            GestureDetector(
              onTap: _incrementCounter,
              child: Container(
                padding: const EdgeInsets.all(16.0),
                color: Colors.blue,
                child: const Text('Tap to Increment'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

問題点:

  • setState()を呼ぶたびに、build()内のウィジェットがすべて再生成されます。
  • constを付けていても、毎回「同じか」を比較する処理が発生します。

改善後:クラス分けして最適化する場合

class CounterPage extends StatefulWidget {
  @override
  _CounterPageState createState() => _CounterPageState();
}

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

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

  @override
  Widget build(BuildContext context) {
    print('CounterPage build');
    return Scaffold(
      appBar: AppBar(title: const Text('Counter')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            CounterDisplay(counter: _counter),  // 状態に依存する部分
            const SizedBox(height: 20),  // 再利用される
            const IncrementArea(),  // 再利用される
          ],
        ),
      ),
    );
  }
}

class CounterDisplay extends StatelessWidget {
  final int counter;
  const CounterDisplay({required this.counter});

  @override
  Widget build(BuildContext context) {
    print('CounterDisplay build');
    return Text('$counter', style: const TextStyle(fontSize: 48));
  }
}

class IncrementArea extends StatelessWidget {
  const IncrementArea();

  @override
  Widget build(BuildContext context) {
    print('IncrementArea build');
    return GestureDetector(
      onTap: () => print('Tapped!'),
      child: Container(
        padding: const EdgeInsets.all(16.0),
        color: Colors.blue,
        child: const Text('Tap to Increment'),
      ),
    );
  }
}

改善点:

  • CounterDisplayだけが_counterの変化に応じて再ビルドされます
  • IncrementAreaSizedBoxconstなので、1度だけ判断され、以降は再利用されます。

5. まとめ

  • クラス分けと**constの活用**により、Flutterの「このウィジェットは変わらない」という判断を1回だけにできます。
  • クラス分けせず、同じbuild()内にconstを持たせると、毎回「同じかどうか」を判断する手間がかかります。
  • この最適化により、パフォーマンスが向上し、よりスムーズなアプリを提供できます。

おわりに

Flutterでの開発では、「状態に依存する部分」と「依存しない部分」をしっかり分けることが重要です。

この最適化手法を活用して、無駄な再ビルドを減らし、より効率的なアプリ開発を目指しましょう!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?