🤌

【Flutter】GetXを用いたシンプルな状態管理入門

2023/03/26に公開

こんにちは、Saeです。
私は「さえないエンジニア」から「さえたエンジニア」へクラスチェンジするために、日々レベル上げに勤しんでます。

今日は「GetX」というPackageを用いた状態管理の方法について解説します。

また今回最後に紹介しているTODOアプリのサンプルコードはGitHubで公開しております。
参考になれば幸いです。
https://github.com/Sae-Eng/getx_sample_todo_app

忙しい人のための「GetXでの状態管理」

日々忙しさで忙殺されているエンジニアへ向けた「GetXでの状態管理」についての説明です。
ここだけ見れば、なんとなくわかります。

  • GetXでは、Observable変数GetBuilderObxGetXControllerを使って状態管理を行う
  • Observable変数: 値が変更された時に自動的にUIを更新
  • GetXController: 状態管理のためのクラス、リソースの解放も自動で行う
  • GetBuilder / Obx: GetXControllerで状態管理をし、状態が変わったときにUIを更新
  • GetView: ステートレスなウィジェットの拡張クラス。コントローラーに直接アクセスするために使用。
import 'package:flutter/material.dart';
import 'package:get/get.dart';

class CounterController extends GetxController {
  var count = 0.obs; // Observable変数

  void increment() {
    count.value++; // 値を増やす
  }
}

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    final controller = Get.put(CounterController());

    return Scaffold(
      appBar: AppBar(title: Text('GetX Counter')),
      body: Center(
        child: Obx(() => Text('Count: ${controller.count.value}')), // UI更新
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: controller.increment, // イベント処理
        child: Icon(Icons.add),
      ),
    );
  }
}

「GetX」の状態管理について

それではここから「GetXの状態管理」について詳しく説明していきます。

そもそも「GetX」とは?

そもそも「GetX」とは何なのでしょうか?

GetXとは、Flutterの開発を容易にするために設計されたオープンソースのライブラリであり、状態管理の他にも、ルーティング、依存性注入など、さまざまな機能を提供しているパッケージになります。

GetXの最大の特徴は、「シンプルで分かりやすく」、かつ「高速である」という点です。
記述方法も直感的でわかりやすく、またライブラリの内部で最適化が施されており、高速に動作するように設計されているため、フレームワーク全体のサイズは小さく、パフォーマンスも優れております。

「GetX」の状態管理方法

GetXの状態管理は、主に「Observable変数」「GetXController」「GetBuilder / Obx」「GetView」の4つの要素で構成されます。
以下では、それぞれについて詳しく説明していきます。

Observable変数

  • 値が変更された時に自動的にUIを更新するための変数(リアクティブな変数)
  • Observable変数を定義する方法は3つあります
  1. ‘.obs’を使う
final count = 0.obs; // int型のリアクティブな変数
final name = ''.obs; // String型のリアクティブな変数
final items = <String>[].obs; // List<String>型のリアクティブな変数
  1. Rx<T>を使う
final count = Rx<int>(0); // int型のリアクティブな変数
final name = Rx<String>(''); // String型のリアクティブな変数
final items = Rx<List<String>>([]); // List<String>型のリアクティブな変数
  1. ’Rx’のサブクラスを使う
final count = RxInt(0); // int型のリアクティブな変数
final name = RxString(''); // String型のリアクティブな変数
final items = RxList<String>([]); // List<String>型のリアクティブな変数

GetXController

  • 状態管理のための基本クラス。リソースも自動で解放。
// カウンターコントローラー
class CounterController extends GetxController {
  var count = 0.obs; // Observable変数

  void increment() {
    count.value++; // 値を増やす
  }
}

GetBuilder / Obx

  • GetXControllerで状態管理をし、状態が変わったときにUIを更新するためのクラス

GetBuilder

  • コントローラーの更新時にUIを再描画する
  • コントローラーでupdate()メソッドを呼び出すことで再描画をトリガーする
  • 再描画される範囲は、GetBuilder配下にある要素が再描画されます
class CounterController extends GetxController {
  int count = 0;

  void increment() {
    count++;
    update(); // UIを更新するために呼び出す
  }
}

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return GetBuilder<CounterController>(
      init: CounterController(),
      builder: (controller) {
        return Scaffold(
          body: Center(
            child: Text('Count: ${controller.count}'),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: controller.increment,
            child: Icon(Icons.add),
          ),
        );
      },
    );
  }
}

Obx

  • リアクティブな変数(.obsRx<T>)の変更にリアクションしてUIを再描画する。
  • 再描画の範囲は、Obxで囲んでいる部分だけが更新されます
class CounterController extends GetxController {
  var count = 0.obs;

  void increment() {
    count.value++;
  }
}

// メインウィジェット
class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    final controller = Get.put(CounterController());

    return Scaffold(
      appBar: AppBar(title: Text('GetX Counter')),
      body: Center(
        child: Obx(() => Text('Count: ${controller.count.value}')), // UI更新
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: controller.increment, // イベント処理
        child: Icon(Icons.add),
      ),
    );
  }
}

GetView

  • StatelessWidgetの拡張クラス。コントローラーに直接アクセスするために使用。
  • GetView内では、コントローラーを扱うために暗黙的に定義されている「controller」という変数を使用することができ、これにより明示的なインスタンスの生成等が省略できます。
  • GetViewを使っても、ウィジェットは自動的にリビルドされません。
  • リアクティブな変数を使用する場合は、ObxGetBuilderなどのウィジェットを使ってリビルドを制御する必要があります。
class MyController extends GetxController {
  final count = 0.obs;

  void increment() {
    count.value++;
  }
}

class MyHomePage extends GetView<MyController> {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('GetX GetView Example')),
      body: Center(
        child: Obx(
          () => Text(
            'Counter: ${controller.count.value}',
            style: TextStyle(fontSize: 24),
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: controller.increment,
        child: Icon(Icons.add),
      ),
    );
  }
}

GetXの状態管理を用いたシンプルなTODOアプリの実装

GetXの状態管理の使い方は分かりましたが、「実際のアプリでの使い方はどうなの?」と思われる方も多いでしょう。

そこで、以下のようなシンプルなTODOアプリを実装する過程で、実際どのように使うのか見ていきましょう。

1. GetX コントローラーを作成

まず、GetXコントローラーを作成します。
このコントローラーは、TODOリストを管理し、新しいTODOアイテムを追加するメソッドと、完了状態を変更するメソッドを提供します。
また「TodoItem」というTODOリスト用のデータクラスも作成します。

import 'package:get/get.dart';

class TodoController extends GetxController {
  final todos = RxList<TodoItem>([]); // TODOリスト

  // TODOリストを追加
  void addTodo(String title) {
    todos.add(TodoItem(title: title));
  }

  // 完了状態を変更
  void toggleDone(int index) {
    todos[index].isDone.toggle();
  }
}

class TodoItem {
  String title;
  RxBool isDone;

  TodoItem({required this.title, bool isDone = false}) : isDone = RxBool(isDone);
}

2. Viewを作成

次に、GetViewを継承したウィジェットを作成します。
このウィジェットは、新しいTODOアイテムを追加するTextField、TODOリストを表示するListView、完了状態を示すCheckBoxを含みます。

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

import 'todo_controller.dart';

class TodoApp extends GetView<TodoController> {
  const TodoApp({super.key});

  
  Widget build(BuildContext context) {
    final TextEditingController textController = TextEditingController();

    return Scaffold(
      appBar: AppBar(title: const Text('GetX Todo App')),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
            child: TextField(
              controller: textController,
              decoration: const InputDecoration(
                labelText: 'タスクを入力してください。',
                border: OutlineInputBorder(),
              ),
            ),
          ),
          Expanded(
            // リスト全体を監視
            child: Obx(
              () => ListView.builder(
                itemCount: controller.todos.length,
                itemBuilder: (context, index) {
                  return ListTile(
                    leading: Obx(
                      // チェックボックスの状態を監視
                      () => Checkbox(
                        value: controller.todos[index].isDone.value,
                        onChanged: (value) {
                          controller.toggleDone(index);
                        },
                      ),
                    ),
                    title: Obx(
                      // テキストの状態を監視
                      () => Text(
                        controller.todos[index].title,
                        style: controller.todos[index].isDone.value
                            ? const TextStyle(
                                decoration: TextDecoration.lineThrough,
                              )
                            : null,
                      ),
                    ),
                  );
                },
              ),
            ),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          controller.addTodo(textController.text);
          textController.clear();
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}

3. main.dartを作成

最後に、アプリを実行するためのmain.dartファイルを作成します。
ここでは、Get.put(TodoController())をし、コントローラーの依存性注入を行い、コントローラーを使用できるにしております。(依存性注入については次回の記事で解説予定です。)

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

import 'todo_app.dart';
import 'todo_controller.dart';

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

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

  
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: 'GetX Todo App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const TodoApp(),
      initialBinding: BindingsBuilder(() => {Get.put(TodoController())}),
    );
  }
}

まとめ

いかがでしたでしょうか。
GetXを使えばシンプルに状態管理を実装できることがわかったと思います。

みなさんもGetXを活用し、素敵なFlutterライフをお過ごしくださいませ。では〜。

Discussion