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 状態管理入門:setState と Provider を TODOアプリで比較する

0
Posted at

はじめに

Flutterを学び始めると、最初に直面するのが「状態管理」です。
この記事では、TODOアプリを題材に setState → Provider の順で実装し、それぞれの違いと使い分けを解説します。

対象読者

  • Flutter の Widget 基礎を学んだ方
  • setState は使えるが、Provider がよくわからない方

状態管理とは?

「状態(State)」とは、UIに影響を与えるデータのことです。

TODOアプリでいえば:

  • TODOのリスト
  • 各TODOの完了フラグ
  • 入力欄のテキスト

これらが変化するたびにUIを更新する仕組みが「状態管理」です。


① setState

仕組み

StatefulWidgetState クラス内で setState() を呼ぶと、build() が再実行されUIが更新されます。

void _addTodo(Todo todo) {
  setState(() {
    _todos.add(todo);  // ← setState内で状態を変更
  });
}

データフロー

ポイント

setState を呼ぶのは「状態を変えるとき」だけ。読むだけの処理は setState 不要です。

// ✅ 状態を変える → setState必要
void _addTodo(Todo todo) {
  setState(() {
    _todos.add(todo);
    _sortTodos(); // ← ソートもsetState内で
  });
}

// ✅ 読むだけ → setState不要
int get _doneCount => _todos.where((t) => t.isCompleted).length;

向いているケース

  • 単一 Widget 内で完結する状態
  • チェックボックス、スイッチなど小さな UI パーツ

限界

複数の Widget で状態を共有したいとき、setState だけでは親から子へ何度もデータを受け渡す必要があり、管理が複雑になります。


② Provider

仕組み

ChangeNotifier を継承したクラスに状態とロジックをまとめ、notifyListeners() で変更をUIに通知します。

セットアップ

# pubspec.yaml
dependencies:
  provider: ^6.1.2

TodoNotifier(状態・ロジック)

class TodoNotifier extends ChangeNotifier {
  final List<Todo> _todos = [];
  List<Todo> get todos => _todos; // 読み取り専用で公開

  void addTodo(Todo todo) {
    _todos.add(todo);
    _sortTodo();
    notifyListeners(); // ← setStateの代わり
  }

  void delete(int index) {
    _todos.removeAt(index);
    notifyListeners();
  }

  void toggle(Todo todo) {
    todo.isCompleted = !todo.isCompleted;
    _sortTodo();
    notifyListeners();
  }

  void _sortTodo() {
    _todos.sort(
      (a, b) => a.isCompleted.toString().compareTo(b.isCompleted.toString()),
    );
  }
}

main.dart(Providerを登録)

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => TodoNotifier(),
      child: const MyApp(),
    ),
  );
}

TodoPage(UI)

UIはロジックを持たず、Notifier のメソッドを呼ぶだけになります。

class _TodoPageState extends State<TodoPage> {
  // ControllerのためだけにStatefulWidget
  final _titleController = TextEditingController();

  @override
  void dispose() {
    _titleController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    // Notifierを購読(変化があればbuildが再実行される)
    final notifier = context.watch<TodoNotifier>();
    final todos = notifier.todos;

    return Scaffold(
      body: Column(
        children: [
          TextField(controller: _titleController),
          ElevatedButton(
            onPressed: () {
              notifier.addTodo(Todo(title: _titleController.text));
              _titleController.clear();
            },
            child: const Text('追加'),
          ),
          Expanded(
            child: ListView.builder(
              itemCount: todos.length,
              itemBuilder: (context, index) => ListTile(
                title: Text(todos[index].title),
                leading: IconButton(
                  icon: Icon(todos[index].isCompleted
                      ? Icons.check_box
                      : Icons.check_box_outline_blank),
                  onPressed: () => notifier.toggle(todos[index]),
                ),
                trailing: IconButton(
                  icon: const Icon(Icons.delete),
                  onPressed: () => notifier.delete(index),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

setState vs Provider 比較

setState Provider
状態の場所 StatefulWidget の State 内 ChangeNotifier クラス
UI更新の合図 setState() notifyListeners()
ロジックの場所 UIと同じファイル 別クラスに分離
複数Widgetで共有 困難(props drilling) 簡単
向いている規模 小〜中 中〜大

よくある疑問

Q. StatelessWidget で dispose() は使えない?

StatelessWidget にはライフサイクルがないため dispose() は呼ばれません。
TextEditingController を使う場合は StatefulWidget にする必要があります。

// ❌ StatelessWidgetにdispose → 呼ばれない
class TodoPage extends StatelessWidget {
  final _controller = TextEditingController();

  @override
  void dispose() { ... } // ← 無視される
}

// ✅ StatefulWidgetにする
class _TodoPageState extends State<TodoPage> {
  final _controller = TextEditingController();

  @override
  void dispose() {
    _controller.dispose(); // ← ちゃんと呼ばれる
    super.dispose();
  }
}

Q. setStateを使わなくても StatefulWidget にすべき?

はい。判断基準は「setState を使うか」ではなく 「ライフサイクルが必要か」 です。

TextEditingControllerAnimationController を使う場合は dispose() が必要なので、StatefulWidget にします。


まとめ

  • setState → シンプル。Widget 内で完結する状態に使う
  • Provider → 状態とロジックをUIから分離できる。複数Widgetで共有したいときに使う
  • Provider を使っても TextEditingController のために StatefulWidget にすることはある

次のステップとして Riverpod を学ぶと、Provider のより型安全な書き方ができるようになります。

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?