🚀

【Flutter】色んな状態管理で作ってみよう ⑦GetX編

2022/02/11に公開

※こちらの記事は【Flutter】色んな状態管理手法でカウンターアプリを作ってみるの一部として作成された記事です

勢い旺盛なニューカマー GetX

今回は賛否ありつつもProviderに勝る人気のフレームワークGetXの状態管理を使ってカウンターアプリを作っていきます

使用するPackage:

概要

  • GetX自体は状態管理手法というよりFlutterのフレームワーク
  • 状態管理、ルーティング、依存関係の注入に対する機能を提供
  • Jonny Borges氏が開発
  • GithubのLIKE数では既にProviderを超える人気
  • しかし作者が以前に自身のライブラリのパフォーマンスを誇張したり、他のライブラリに対して否定的なコメントを繰り返した為、一部のFlutter開発者からは嫌われている等論争の的になっているライブラリの様です
  • 初版リリースは2019年11月

全体像

  • 前述の通り、GetX自体はFlutter用のフレームワーク
  • その中の状態管理と依存関係の注入機能を今回は使います

特徴としては、

  • contextを使わず、どこからでも状態管理クラスにアクセス可能
  • 状態変数自体をリアクティブなクラスでラップする

実際に例で見た方が早いかもしれません。

キーとなるクラスやメソッド

  • GetxControllerクラス:状態管理クラスが継承するクラス
  • Rx<T>クラス:状態変数をラップし、監視可能なリアクティブなオブジェクトにする
  • .obs:オブジェクトをRxオブジェクトでラップし、インスタンス化する
  • Get.put(<GetxController>)メソッド:依存関係を注入するメソッド
  • Get.find()メソッド:注入されたGetxControllerへアクセスするメソッド
  • Obxクラス:状態変数の変化に反応し、ラップしたwidgetを再描画します

準備

カウンターアプリを例に実際見ていきましょう。
サンプルコードはこちら

1. Stateクラス

  • 今回はCountフィールドを持つCounterObjクラスを定義。
  • RxIntint型の状態変数を持つRxオブジェクトです。
  • 0.obsとする事で0を状態変数に持つRxオブジェクトをインスタンス化しています。
class CounterObj {
  CounterObj() : count = 0.obs;
  RxInt count;
}

2. 状態管理クラス(GetXController)

  • GetXControllerクラスを継承する状態管理クラスを定義します。
  • 状態変数として管理するのは先程定義したCounterObjクラスです。
  • その為、CounterObjCouterObj.obsとする事で監視可能なRxオブジェクトとしてインスタンス化します。
class GetXCounterController extends GetxController {
  Rx<CounterObj> _counter = CounterObj().obs;
  Rx<CounterObj> get counter => _counter;

  void incrementCounter() => _counter.value.count++;

  void decrementCounter() => _counter.value.count--;

  void resetCounter() => _counter.value.count.value = 0;
}
  • 状態変数の変更ではRxインスタンスのvalueプロパティに状態変数が格納されているので、それを直接操作します。

3. 依存関係の注入

  • 依存関係の注入にはGet.putメソッドを使います。
  • 以下のようにGet.put(<GetXController>)メソッドを実行する事でそのwidgetツリー以下のwidgetでGet.findメソッドで引数に渡したGetXControllerにアクセスする事が出来ます。
  
  Widget build(BuildContext context) {
    Get.put(GetXCounterController());
    return const _GetXCounterPage();
  }

状態へのアクセス

  • 状態管理クラスへのアクセスはGet.findメソッドを使います
    final GetXCounterController c = Get.find();

    ...
    FloatingActionButton(
        onPressed: c.incrementCounter,
        tooltip: 'Increment',
        heroTag: 'Increment',
        child: Icon(Icons.add),
    ),
    ...

状態変数の変更に伴った再描画

  • Obxクラスでラップする事で、状態変数に変更があった際に再描画させる事ができます。
    Obx(
        () => Text(
        '${c.counter.value.count}',
        style: Theme.of(context).textTheme.headline4,
        ),
    ),

全体

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:state_management_examples/widgets/main_appbar.dart';


// 状態クラス
class CounterObj {
  CounterObj() : count = 0.obs;
  RxInt count;
}

// 状態管理クラス
class GetXCounterController extends GetxController {
  Rx<CounterObj> _counter = CounterObj().obs;
  Rx<CounterObj> get counter => _counter;

  void incrementCounter() => _counter.value.count++;

  void decrementCounter() => _counter.value.count--;

  void resetCounter() => _counter.value.count.value = 0;
}

// 依存関係を注入
class GetXCounterPage extends StatelessWidget {
  const GetXCounterPage({Key key}) : super(key: key);

  
  Widget build(BuildContext context) {
    Get.put(GetXCounterController());
    return const _GetXCounterPage();
  }
}

// カウンター本体
class _GetXCounterPage extends StatelessWidget {
  const _GetXCounterPage({Key key}) : super(key: key);

  
  Widget build(BuildContext context) {
    print('rebuild!');
    final GetXCounterController c = Get.find();

    return Scaffold(
      appBar: MainAppBar(
        title: 'GetX',
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Obx(
              () => Text(
                '${c.counter.value.count}',
                style: Theme.of(context).textTheme.headline4,
              ),
            ),
          ],
        ),
      ),
      floatingActionButton: FittedBox(
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: <Widget>[
            FloatingActionButton(
              onPressed: c.incrementCounter,
              tooltip: 'Increment',
              heroTag: 'Increment',
              child: Icon(Icons.add),
            ),
            const SizedBox(width: 16),
            FloatingActionButton(
              onPressed: c.decrementCounter,
              tooltip: 'Decrement',
              heroTag: 'Decrement',
              child: Icon(Icons.remove),
            ),
            const SizedBox(width: 16),
            FloatingActionButton.extended(
              onPressed: c.resetCounter,
              tooltip: 'Reset',
              heroTag: 'Reset',
              label: Text('RESET'),
            ),
          ],
        ),
      ),
    );
  }
}

以上でした

どうでしょうか、GetXには他にも様々な機能があり、だいぶ端折った部分もありますが、非常に直感的で学習コストが低いと感じます。

大規模なプロジェクトで状態管理が複雑になる場合に適正かは分かりませんが、個人開発レベルであまり複雑な状態管理が発生しないプロジェクトであれば、非常に強力なツールになると感じます。

他にも多くの機能があるので別途GetXだけを記事にしたいと思います。

参考

Discussion