目次
概要
Flutterで状態を管理するためにはStatefulWidget
やInheritedWidget
を使う必要があります。しかし、複雑な状態を管理するためにはBloc
パタンやProvider
ようなパッケージを使います。
今回のブログポストではFlutterで状態管理に一番使われてるパッケージであるGetX
について紹介します。このブログで紹介するソースコードは下記のリンクで確認できます。
GetX
GetX
は状態管理パッケージで有名ですが、実際はこれ以外の機能も持っています。FlutterでGetXを使うと状態管理だけではなく、Route、多言語、画面のサイズ、APIコールなど色んな機能を使えます。
- [GetX] 状態管理
- [GetX] ルート管理
- [GetX] ディペンデンシー管理
- [GetX] 多言語
- [GetX] テーマ
- [GetX] BottomSheet
- [GetX] Dialog
- [GetX] スナックバー
- [GetX] プラットフォームやデバイス情報
今回のブログポストではこの中で状態管理について説明する予定です。
GetXのインストール
FlutterでGetXの使い方を確認するため次のコマンドを実行してFlutterの新いプロジェクトを生成します。
flutter create state_management
その後次のコマンドを実行してGetX
パッケージをインストールします。
flutter pub add get
今回のブログポストではFlutterコマンドで生成したサンプルプロジェクトをGetXを使ってリファクタリングをしてGetxを使った状態管理を説明します。
GetXの設定
FltterでGetX
を使うためにはMaterialApp
の代わりにGetMaterialApp
を使う必要があります。これを確認するためにはlib/main.dart
ファイルを開いて下記のように修正します。
import 'package:get/get.dart';
...
class MyApp extends StatelessWidget {
...
@override
Widget build(BuildContext context) {
return GetMaterialApp(
...
);
}
}
状態管理
GetXは次のように2つ状態管理方法を提供してます。
- 単純状態管理(Simple state management)
- レスポンシブ状態管理(Responsive state management)
単純状態管理
GetXを使って単純状態管理をするため、lib/controller/count_controller.dart
ファイルを生成して次のように修正します。
import 'package:get/get.dart';
class CountController extends GetxController {
int count = 0;
void increment() {
count++;
update();
}
}
GetXで状態管理をするためクラスを生成する時にはGetxController
を相続します。
class CountController extends GetxController {
...
}
そして管理する状態変数を宣言します。
class CountController extends GetxController {
int count = 0;
...
}
最後に状態変数をアップデートする関数を宣言します。
class CountController extends GetxController {
...
void increment() {
count++;
update();
}
}
単純状態管理を使う場合、状態値を変更した後、update()
関数を使って状態が変更されたことをお知らせする必要があります。update()
を使わないと、状態値は変更できるが、画面の更新が行われなくなり、状態が更新されない画面が確認できます。
このように生成したGetX
コントローラーを使ってみましょう。lib/main.dart
ファイルを開いて次のように修正します。
import 'package:get/get.dart';
import 'controller/count_controller.dart';
...
class MyHomePage extends StatelessWidget {
final String title;
const MyHomePage({Key? key, required this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
final controller = Get.put(CountController());
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
GetBuilder<CountController>(builder: (controller) {
return Text(
'${controller.count}',
style: Theme.of(context).textTheme.headline4,
);
}),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: controller.increment,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
今から私たちはGetXで状態管理をする予定なので、StatefulWidget
ウィジェットを使う必要はないです。したがって、次のようにMyHomePage
ウィジェットをStatelessWidget
ウィジェットに変更します。
class MyHomePage extends StatelessWidget {
final String title;
const MyHomePage({Key? key, required this.title}) : super(key: key);
...
}
GetXで状態管理をするためにはGetXで作ったコントローラーを次のようにGet.put
を使って登録(Register)する必要があります。このようにコントローラーを登録したら、登録した後のコードではコントローラーを使って状態管理ができるようになります。
class MyHomePage extends StatelessWidget {
...
@override
Widget build(BuildContext context) {
final controller = Get.put(CountController());
...
}
}
単純状態管理で状態の変化を検出して、変更された値を取得するためには次のようにGetBuilder
を使う必要があります。GetBuilder
を使わないと、状態が変更されたことが検出できなくて、変更された値を画面に反映することができません。
GetBuilder<CountController>(builder: (controller) {
return Text(
'${controller.count}',
style: Theme.of(context).textTheme.headline4,
);
}),
GetXで生成した状態値を変更するためには私たちが作ったincrement
関数をコールする必要があります。increment
関数をコールするためには、次のようにFloatingActionButton
のonPressed
イベントに連結します。
floatingActionButton: FloatingActionButton(
onPressed: controller.increment,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
今度はプロジェクトを実行して、アクションボタンを押したら、画面が上手く更新されることが確認できます。このようにGetXの単純状態管理はupdate()
関数を使って、状態を画面に反映するタイミングを決めることができます。
レスポンシブ状態管理
レスポンシブ状態管理はupdate
関数を使って状態を直接通知する単純状態管理とは違って、内部ロジックで値の状態変化を検出して画面に反映します。
これを確認するためlib/controller/count_controller.dart
ファイルを次のように修正します。
import 'package:get/get.dart';
class CountController extends GetxController {
final count = 0.obs;
void increment() {
count.value++;
// count(count.value + 1);
}
}
単純状態管理とは違って状態変数を生成する時、.obs
を使って生成します。このように生成した変数は単純なタイプではなくRxInt
ようにレスポンシブ状態変数になります。
レスポンシブ状態変数は次のように2つの方法で値を変更することができます。
count.value++;
// or
count(count.value + 1);
次はこのように生成したレスポンシブ状態管理を使うため、lib/main.dart
ファイルを次のように修正します。
class MyHomePage extends StatelessWidget {
final String title;
const MyHomePage({Key? key, required this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
final controller = Get.put(CountController());
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Obx(
() => Text(
"${controller.count.value}",
style: Theme.of(context).textTheme.headline4,
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: controller.increment,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
レスポンシブ状態管理ではGetBuilder
の代わりにObx
を使って状態の変更を検出します。
Obx(
() => Text(
"${controller.count.value}",
style: Theme.of(context).textTheme.headline4,
),
),
このようにプログラムを実行してアクションのボタンを押したら、以前と同じようにカウントが上手く上がってることが確認できます。
LifeCycle
StatefulWidgetを使うとウィジェットのライプサイクル関数を使うことができます。これと同じようにGetxController
を使うと次のようなライプサイクル関数を使うことができます。
class CountController extends GetxController {
@override
void onInit() {
super.onInit();
}
@override
void onClose() {
super.onClose();
}
}
- onInit: コントローラーが生成される時、コールされます。
- onClose: コントローラーが要らなくなってメモリから消される時コールされます。
Worker
Workerはレスポンシブ状態値の変化が発生した時、これを検出して特定なコルバック関数をコールさせることができます。
ever(count, (_) => print("called every update"));
once(count, (_) => print("called once"));
debounce(count, (_) => print("called after 1 second after last change"), time: Duration(seconds: 1));
interval(count, (_) => print("called every second during the value is changed."), time: Duration(seconds: 1));
- ever: レスポンシブ状態値が変更されるたびにコールされます。
- once レスポンシブ状態値が最初変更される時1回コールされます。
- debounce:
debounce
と同じように動作します。最後変更の後、特定した時間の間変更がない場合コールされます。 - interval:
interval
と同じように動作します。レスポンシブ状態値が変更される間、一定の間隔でコールされます。
Workerはコントローラーまたはクラスが生成される時だけ使えます。つまり、コントローラーのonInit、クラスのコンストラクタ、StatefulWidgetのinitState中で定義する必要があります。 안에서 호출해야 합니다.
Get.find
今までの例を見るとGetXの状態値を使うためGet.put
を使ってコントローラーを生成して使いました。
final controller = Get.put(CountController());
もし、チャイルドウィジェットでこのコントローラーを使って状態値を変更したり、状態値を使う場合はどうすれば良いでしょうか?もちろん次のように生成したコントローラーをパラメータで渡して使うこともできます。
CustomWidget(controller: controller)
しかし、GetXではGet.find
を提供して次のようにもっと簡単に生成したコントローラーにアクセスすることができます。
Get.find<CountController>()
これを確認するためlib/main.dart
ファイルを次のように修正します。
class MyHomePage extends StatelessWidget {
final String title;
const MyHomePage({Key? key, required this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
Get.put(CountController());
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Obx(
() => Text(
"${Get.find<CountController>().count.value}",
style: Theme.of(context).textTheme.headline4,
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: Get.find<CountController>().increment,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
伊是はGet.put(CountController());
を使って生成したcontrollerの変数を使って状態値にアクセスしましたが、現在の例題では次のようにGet.find<CountController>()
を使って状態値にアクセスしてることが分かります。
Obx(
() => Text(
"${Get.find<CountController>().count.value}",
style: Theme.of(context).textTheme.headline4,
),
),
また、関数を実行する時にも次のようにGet.find
を使ってることが分かります。
floatingActionButton: FloatingActionButton(
onPressed: Get.find<CountController>().increment,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
Get.find
を使うとGet.put
で登録したコントローラーをどこでもアクセスすることができます。今回の例題では同じファイル中で使いましたが、チャイルドウィジェットでも使うことができます。重要なことはGet.put
を使って先にコントローラーを登録した後、使うことです。もし、登録されてないコントローラーにアクセスするとエラーが発生します。
Get.isRegistered
Get.find
はGet.put
で登録されたコントローラーだけ使うことができて、登録されてないコントローラーを使うとエラーが発生します。このような問題を解決するためには次のようにGet.isRegistered
を使って使いたいコントローラーが登録されたか確認することができます。
Get.isRegistered<CountController>()
もし、コントローラーが登録されたらtrue
が返されて、登録されたない場合はfalse
が返されます。
static get to
GetXで状態管理をすると、Get.find
を使ってよく状態値にアクセスします。それで、GetX
では次のようにstatic
を使うパタンをよく使います。
static CountController get to => Get.find<CountController>();
これを確認するためlib/controller/count_controller.dart
ファイルを次のように修正します。
import 'package:get/get.dart';
class CountController extends GetxController {
static CountController get to => Get.find<CountController>();
final count = 0.obs;
void increment() {
count.value++;
// count(count.value + 1);
}
}
次はlib/main.dart
ファイルを次のように修正します。
class MyHomePage extends StatelessWidget {
final String title;
const MyHomePage({Key? key, required this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
Get.put(CountController());
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Obx(
() => Text(
"${CountController.to.count.value}",
style: Theme.of(context).textTheme.headline4,
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: CountController.to.increment,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
以前はcontroller
変数を使って状態値にアクセスする方法を次のようにstatic
を使う方法で変更しました。
Obx(
() => Text(
"${CountController.to.count.value}",
style: Theme.of(context).textTheme.headline4,
),
),
...
floatingActionButton: FloatingActionButton(
onPressed: CountController.to.increment,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
GetXではよく使うパタンなので、よく覚えておきましょう。
完了
これでFlutterでGetX
を使って状態管理をする方法についてみてみました。今度からは皆さんも、StatefulWidgetで状態管理をすることをやめて、GetXを使って状態管理をしてみてください。
私のブログが役に立ちましたか?下にコメントを残してください。それは私にとって大きな大きな力になります!
アプリ広報
Deku
が開発したアプリを使ってみてください。Deku
が開発したアプリはFlutterで開発されています。興味がある方はアプリをダウンロードしてアプリを使ってくれると本当に助かります。