Jonny Borges
Committed by GitHub

Merge pull request #1887 from toshi-kuji/translate-readme-tojapanese

Translated readme and documents into Japanese
![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/get.png)
[![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get)
[![likes](https://badges.bar/get/likes)](https://pub.dev/packages/get/score)
![building](https://github.com/jonataslaw/get/workflows/build/badge.svg)
[![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart)
[![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N)
[![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://communityinviter.com/apps/getxworkspace/getx)
[![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g)
<a href="https://github.com/Solido/awesome-flutter">
<img alt="Awesome Flutter" src="https://img.shields.io/badge/Awesome-Flutter-blue.svg?longCache=true&style=flat-square" />
</a>
<a href="https://www.buymeacoffee.com/jonataslaw" target="_blank"><img src="https://i.imgur.com/aV6DDA7.png" alt="Buy Me A Coffee" style="height: 41px !important;width: 174px !important; box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" > </a>
![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/getx.png)
<div align="center">
**言語**
[![英語](https://img.shields.io/badge/Language-English-blueviolet?style=for-the-badge)](README.md)
[![ベトナム語](https://img.shields.io/badge/Language-Vietnamese-blueviolet?style=for-the-badge)](README-vi.md)
[![インドネシア語](https://img.shields.io/badge/Language-Indonesian-blueviolet?style=for-the-badge)](README.id-ID.md)
[![ウルドゥー語](https://img.shields.io/badge/Language-Urdu-blueviolet?style=for-the-badge)](README.ur-PK.md)
[![中国語](https://img.shields.io/badge/Language-Chinese-blueviolet?style=for-the-badge)](README.zh-cn.md)
[![ポルトガル語](https://img.shields.io/badge/Language-Portuguese-blueviolet?style=for-the-badge)](README.pt-br.md)
[![スペイン語](https://img.shields.io/badge/Language-Spanish-blueviolet?style=for-the-badge)](README-es.md)
[![ロシア語](https://img.shields.io/badge/Language-Russian-blueviolet?style=for-the-badge)](README.ru.md)
[![ポーランド語](https://img.shields.io/badge/Language-Polish-blueviolet?style=for-the-badge)](README.pl.md)
[![韓国語](https://img.shields.io/badge/Language-Korean-blueviolet?style=for-the-badge)](README.ko-kr.md)
[![フランス語](https://img.shields.io/badge/Language-French-blueviolet?style=for-the-badge)](README-fr.md)
[![日本語](https://img.shields.io/badge/Language-Japanese-blueviolet?style=for-the-badge)](README-ja.md)
</div>
- [Getとは](#Getとは)
- [インストール方法](#インストール方法)
- [GetXによるカウンターアプリ](#GetXによるカウンターアプリ)
- [三本柱](#三本柱)
- [状態管理](#状態管理)
- [リアクティブな状態管理](#リアクティブな状態管理)
- [状態管理に関する詳細ドキュメント](#状態管理に関する詳細ドキュメント)
- [Route管理](#Route管理)
- [Route管理に関する詳細ドキュメント](#Route管理に関する詳細ドキュメント)
- [依存オブジェクト管理](#依存オブジェクト管理)
- [依存オブジェクト管理に関する詳細ドキュメント](#依存オブジェクト管理に関する詳細ドキュメント)
- [ユーティリティ](#ユーティリティ)
- [多言語対応](#多言語対応)
- [翻訳ファイル](#翻訳ファイル)
- [翻訳ファイルの利用](#翻訳ファイルの利用)
- [ロケール](#ロケール)
- [ロケールの変更](#ロケールの変更)
- [システムのロケールを読み込む](#システムのロケールを読み込む)
- [Themeの変更](#Themeの変更)
- [GetConnect](#getconnect)
- [デフォルト設定](#デフォルト設定)
- [カスタム設定](#カスタム設定)
- [GetPageにミドルウェアを設定](#GetPageにミドルウェアを設定)
- [実行優先度](#実行優先度)
- [redirect](#redirect)
- [onPageCalled](#onpagecalled)
- [onBindingsStart](#onbindingsstart)
- [onPageBuildStart](#onpagebuildstart)
- [onPageBuilt](#onpagebuilt)
- [onPageDispose](#onpagedispose)
- [その他API](#その他API)
- [オプションのグローバル設定と手動設定](#オプションのグローバル設定と手動設定)
- [ローカルステートWidget](#ローカルステートWidget)
- [ValueBuilder](#valuebuilder)
- [ObxValue](#obxvalue)
- [お役立ちTIPS](#お役立ちTIPS)
- [StateMixin](#statemixin)
- [GetView](#getview)
- [GetResponsiveView](#getresponsiveview)
- [使い方](#使い方])
- [GetWidget](#getwidget)
- [GetxService](#getxservice)
- [テストの実行](#テストの実行)
- [mockitoやmocktailを使う場合](#mockitoやmocktailを使う場合)
- [Get.reset()](#Get.reset())
- [Get.testMode](#Get.testMode)
- [バージョン2.0からの破壊的変更](#バージョン2.0からの破壊的変更)
- [なぜGetXなのか](#なぜGetXなのか)
- [コミュニティ](#コミュニティ)
- [コミュニティチャンネル](#コミュニティチャンネル)
- [コントリビュート方法](#コントリビュート方法)
- [GetXに関する記事と動画](#GetXに関する記事と動画)
# Getとは
- GetXはFlutterのための超軽量でパワフルなソリューションです。高パフォーマンスな状態管理機能、インテリジェントな依存オブジェクト管理機能、そしてRoute管理機能の三本柱を軽量かつ実用的な形で組み合わせています。
- GetXは3つの基本原則を念頭に開発されています。 **【生産性、パフォーマンス、コードの分離性】** これらはライブラリ内のすべてのリソースに優先適用されている原則です。
- **パフォーマンス:** GetXは高いパフォーマンスと最小限のリソース消費を目標にしています。GetXはでは Stream および ChangeNotifier を利用しなくて済みます。
- **生産性:** GetXはシンプルで使い心地のいいシンタックスを採用しています。あなたの実現したい機能がどんなものであれ、GetXを使えばより簡単に実現できる方法が見つかるでしょう。開発にかかる時間を短縮し、あなたのアプリケーションのパフォーマンスを最大限引き出してくれます。
開発者はメモリリソースの管理に気を配るのが常です。しかしGetXでは、リソースが使用されていないときはメモリから削除されるのがデフォルト動作のため、過度に気にかける必要はありません。(逆にメモリに残しておきたい場合は、依存オブジェクトをインスタンス化するメソッドを使う際に「permanent: true」と宣言してください)これにより時間が節約できますし、不要な依存オブジェクトがメモリ上に残るリスクも少なくなります。メモリへの読み込みについてもデフォルトは遅延読み込みであり、使用するときに初めてメモリ上に読み込まれます。
- **コードの分離性:** GetXを使うと、ビュー、プレゼンテーションロジック、ビジネスロジック、依存オブジェクトの注入、およびナビゲーション周りのコードを書き分けやすくなります。Routeのナビゲーションにはcontextを必要としないため、Widgetツリーに依存することはありません。ロジックについてもInheritedWidget経由でController/BLoCにアクセスする際のcontextは必要ありません。プレゼンテーションロジックとビジネスロジックをUIクラスから完全に切り離すことができます。また、Controller/モデル/BLoCのクラスを、`MultiProvider`を使ってWidgetツリーに注入する必要もありません。GetXでは独自の依存オブジェクト注入機能を使用し、ビュークラスからビューとは無関係なコードをなくすことができるのです。
GetXを使うことでアプリケーションの各機能がどこにあるのかがわかりやすくなり、自然と見やすいコードになります。メンテナンスが容易になるだけでなく、それまでのFlutterでは考えられなかったモジュール共有が簡単に実現できるようになりました。
BLoCはこの分野におけるFlutterの出発点と言えるものでしたが、GetXはこれを正統進化させており、ビジネスロジックのみならずプレゼンテーションロジックも分離することができます。そのほかデータレイヤーはもちろん、依存オブジェクトやRouteの注入に関するコードも。どこに何が配置されているのか全体の見通しがしやすくなり、Hello Worldを表示させるかのように簡単にアプリの機能を利用できるようになるでしょう。
Flutterアプリを作るならGetXは最も簡単で実用的、かつスケーラブルなソリューションです。強力なエコシステムも存在があるため、初心者にはわかりやすさ、プロには正確性を提供することができます。そしてFlutter SDKにはない幅広い種類のAPIを提供し、セキュアで安定的な環境を構築します。
- GetXは肥大化したライブラリではありません。何も気にせずすぐに開発を始められるよう多数の機能を標準で備えていますが、それぞれの機能は個別にコンテナに入っており、使用してはじめて起動します。状態管理機能しか利用していない場合はその機能だけがコンパイルされます。Route管理機能だけを利用していれば、状態管理機能がコンパイルされることはありません。
- GetXには巨大なエコシステム、コミュニティ、コラボレーターの存在があるため、Flutterが存在する限りメンテナンスされ続けます。またGetXもFlutterと同様にAndroid、iOS、Web、Mac、Linux、Windows、そしてあなたのサーバー上で、単一のコードから実行することができます。
**[Get Server](https://github.com/jonataslaw/get_server)を使うことで、フロントエンドで作成したコードをバックエンドで再利用することが可能です。**
**さらに、[Get CLI](https://github.com/jonataslaw/get_cli)を使えば、サーバー側でもフロントエンド側でも開発プロセス全体を自動化することができます。**
**また、生産性をさらに高めるためのツールとして、[VSCode用の拡張機能](https://marketplace.visualstudio.com/items?itemName=get-snippets.get-snippets) と [Android Studio/Intellij用の拡張機能](https://plugins.jetbrains.com/plugin/14975-getx-snippets)があります。**
# インストール方法
Getパッケージを pubspec.yaml に追加します:
```yaml
dependencies:
get:
```
使用するときはこのようにインポートしてください:
```dart
import 'package:get/get.dart';
```
# GetXによるカウンターアプリ
Flutterで新規プロジェクトを作成する際に表示されるカウンターアプリは、コメントを含めると100行以上あります。Getの実力を示すため、このカウンターアプリを可読性を重視した形で、コメントを含めてわずか26行のコードで作成する方法を紹介します。
- ステップ1:
MaterialAppの前に「Get」を足して、GetMaterialAppにします。
```dart
void main() => runApp(GetMaterialApp(home: Home()));
```
- 注1: GetMaterialAppはFlutterのMaterialAppに手を加えたものではありません。MaterialAppをchildに持ち、諸々の追加設定をしてくれるWidgetに過ぎません。この設定は手動でも可能ですが、その必要はありません。GetMaterialAppは、Routeの作成・注入、言語翻訳の注入など、ナビゲーションに必要なものをすべて注入してくれます。Getを状態管理や依存オブジェクト管理に限定して使用する場合は、GetMaterialAppを使用する必要はありません。GetMaterialAppは、Route、SnackBar、多言語対応、BottomSheet、Dialog、contextなしの高レベルAPIを利用する場合に必要です。
- 注2: このステップは、Route管理機能(`Get.to()`や`Get.back()`など)を使用しない場合は、必要ありません。
- ステップ2:
ビジネスロジッククラスを作成し、そこに必要な変数、メソッド、コントローラをすべて配置します。
変数に ".obs" を付け足すことで、その変数の値の変化を監視することが可能になります。
```dart
class Controller extends GetxController{
var count = 0.obs;
increment() => count++;
}
```
- ステップ3:
ビューを作成します。StatelessWidgetを使用することでRAMが節約できます。GetではStatefulWidgetを使用する必要がなくなるかもしれません。
```dart
class Home extends StatelessWidget {
@override
Widget build(context) {
// Get.put()を使ってクラスをインスタンス化することですべての子Routeで利用できるようになります。
final Controller c = Get.put(Controller());
return Scaffold(
// countが変わるたびにTextを更新するにはObx(()=>)を使ってください。
appBar: AppBar(title: Obx(() => Text("Clicks: ${c.count}"))),
// 8行使っていたNavigator.pushの代わりに短い Get.to()を使ってください。context不要です。
body: Center(child: ElevatedButton(
child: Text("Go to Other"), onPressed: () => Get.to(Other()))),
floatingActionButton:
FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment));
}
}
class Other extends StatelessWidget {
// 他のページで使われているコントローラーを見つけてきてくれます。
final Controller c = Get.find();
@override
Widget build(context){
// 最新のcount変数の値にアクセス
return Scaffold(body: Center(child: Text("${c.count}")));
}
}
```
Result:
![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/counter-app-gif.gif)
これはシンプルな例ですが、すでにGetがいかに強力であるかがわかると思います。プロジェクトが大きければ大きいほど、この差はもっと開くでしょう。
Getはチームでの作業を想定して設計されていますが、個人開発者の仕事もシンプルにしてくれます。
パフォーマンスを落とさず納期までにすべて納品。Getはすべての人に向いているわけではありませんが、このフレーズにぴんと来た人には確実に向いています!
# 三本柱
## 状態管理
Getの状態管理には、非リアクティブ(GetBuilder)と、リアクティブ(GetX/Obx)の2つのアプローチがあります。
### リアクティブな状態管理
リアクティブプログラミングは複雑であると言われ、多くの人に敬遠されています。GetXは、リアクティブプログラミングをシンプルなものに変えます:
* StreamControllerを作る必要はありません。
* 変数ごとにStreamBuilderをセットする必要はありません。
* 状態ごとにクラスを作る必要はありません。
* 初期値のためにgetを準備する必要はありません。
- コードの自動生成をする必要がありません。
GetにおけるリアクティブプログラミングはsetStateと同じように簡単です。
例えば、名前の変数があって、それを変更するたびに、その名前を使っているすべてのWidgetを自動で更新したい場合。
```dart
var name = 'Jonatas Borges';
```
このnameをObservable(監視可能)にするには, ".obs"を値の末尾に付けるだけです。
```dart
var name = 'Jonatas Borges'.obs;
```
UIでその値を表示し、値が変わるたびに内容を更新したい場合は次のようにします。
```dart
Obx(() => Text("${controller.name}"));
```
以上です。こんなに簡単なんですよ。
### 状態管理に関する詳細ドキュメント
**状態管理に関するより詳細な説明を知りたい方は[こちらの日本語ドキュメント](./documentation/ja_JP/state_management.md)をご覧ください。多くの事例や、非リアクティブな状態管理とリアクティブな状態管理の違いについても説明されています。**
GetXパワーがもたらす利点をより理解していただけると思います。
## Route管理
GetXはcontextなしでRoute/SnackBar/Dialog/BottomSheetを使用することができます。具体的に見ていきましょう。
いつものMaterialAppの前に「Get」を付け足して、GetMaterialAppにしましょう。
```dart
GetMaterialApp( // MaterialApp の前に Get
home: MyHome(),
)
```
新しいRouteに画面遷移するにはこのシンタックス。
```dart
Get.to(NextScreen());
```
名前付きRouteに画面遷移するにはこのシンタックス。名前付きRouteの詳細は[こちらの日本語ドキュメント](./documentation/ja_JP/route_management.md#navigation-with-named-routes)
```dart
Get.toNamed('/details');
```
SnackBar、Dialog、BottomSheetなど、Navigator.pop(context)で閉じられるRouteはこれで閉じます。
```dart
Get.back();
```
次の画面に移動した後、前の画面に戻れないようにする場合(スプラッシュスクリーンやログイン画面など)はこちら。
```dart
Get.off(NextScreen());
```
次の画面に進み、前のRouteをすべてキャンセルする場合(ショッピングカート、アンケート、テストなど)はこちら。
```dart
Get.offAll(NextScreen());
```
以上、contextを一度も使わなかったことに気付きましたか?これがGetでRoute管理を行う最大のメリットのひとつです。contextを使わないので、たとえばcontrollerクラスの中でも、これらのメソッドを実行することができます。
### Route管理に関する詳細ドキュメント
**Getは名前付きRouteでも動作し、Routeの下位レベルの制御も可能です。詳細なドキュメントは[こちらの日本語ドキュメント](./documentation/ja_JP/route_management.md)にあります。**
## 依存オブジェクト管理
Getにはシンプルで強力な依存オブジェクト注入機能があります。わずか1行のコードで、Provider contextやinheritedWidgetも使わず、BLoCやControllerのようなクラスのインスタンスを取得することができます。
```dart
Controller controller = Get.put(Controller()); // controller = Controller() とする代わりに
```
- 注: Getの状態管理機能を使用している場合は、Bindings APIにもご注目を。BindingsはビューとControllerを結びつけるのをより便利にしてくれます。
一つのクラスの中でControllerクラスをインスタンス化するのではなく、Getインスタンスの中でインスタンス化することで、アプリ全体でControllerが利用できるようになります。
**ヒント:** Getの依存オブジェクト注入機能の部分は、パッケージ全体の中でも他の部分と切り離されているので、たとえば、あなたのアプリがすでに状態管理機能を一部で使用していたとしても、それらを書き直す必要はなく、この依存オブジェクト注入機能をそのまま使用することができます。
```dart
controller.fetchApi();
```
色々なRouteを行き来した後に、あるControllerクラスのデータにアクセスする必要が生じたとしましょう。ProviderやGet_itなら再びそのクラスに依存オブジェクトを注入する必要がありますよね?Getの場合は違います。Getでは「find」と依頼するだけで、追加の依存オブジェクトの注入は必要ありません。
```dart
Controller controller = Get.find();
//マジックみたいですね。Getは正しいcontrollerをきちんと探してきてくれますよ。100万のcontrollerのインスタンスがあっても、Getは必ず正しいcontrollerを探し当てます。
```
そして、findで取得したコントローラーのデータをこのように呼び出すことができます。
```dart
Text(controller.textFromApi);
```
### 依存オブジェクト管理に関する詳細ドキュメント
**依存オブジェクト管理に関するより詳細な説明は[こちらの日本語ドキュメント](./documentation/ja_JP/dependency_management.md)をご覧ください。**
# ユーティリティ
## 多言語対応
### 翻訳ファイル
翻訳ファイルはシンプルなキーと値のMapとして保持されます。
翻訳を追加するには、クラスを作成して `Translations` を継承します。
```dart
import 'package:get/get.dart';
class Messages extends Translations {
@override
Map<String, Map<String, String>> get keys => {
'en_US': {
'hello': 'Hello World',
},
'de_DE': {
'hello': 'Hallo Welt',
}
};
}
```
#### 翻訳ファイルの利用
指定されたキーに `.tr` (translateのtr)を追加するだけで、`Get.locale` と `Get.fallbackLocale` の現在の値をに沿って適切な言語に翻訳されます。
```dart
Text('title'.tr);
```
#### 単数系と複数形に対応
```dart
var products = [];
Text('singularKey'.trPlural('pluralKey', products.length, Args));
```
#### パラメーターに対応
```dart
import 'package:get/get.dart';
Map<String, Map<String, String>> get keys => {
'en_US': {
'logged_in': 'logged in as @name with email @email',
},
'es_ES': {
'logged_in': 'iniciado sesión como @name con e-mail @email',
}
};
Text('logged_in'.trParams({
'name': 'Jhon',
'email': 'jhon@example.com'
}));
```
### ロケール
ロケールと翻訳を定義するため、`GetMaterialApp`にパラメータを渡します。
```dart
return GetMaterialApp(
translations: Messages(), // Translationsを継承したクラスのインスタンス
locale: Locale('en', 'US'), // このロケール設定に沿って翻訳が表示される
fallbackLocale: Locale('en', 'UK'), // 無効なロケールだったときのフォールバックを指定
);
```
#### ロケールの変更
ロケールを変更するには、`Get.updateLocale(locale)`を呼び出します。翻訳は新しいロケールに沿ってなされます。
```dart
var locale = Locale('en', 'US');
Get.updateLocale(locale);
```
#### システムのロケールを読み込む
システムのロケールを読み込むには、`Get.deviceLocale`を使用します。
```dart
return GetMaterialApp(
locale: Get.deviceLocale,
);
```
## Themeの変更
`GetMaterialApp`より上位のWidgetを使ってThemeを変更しないでください。Keyの重複を引き起こす可能性があります。アプリのThemeを変更するためには「ThemeProvider」Widgetを作成するという前時代的なアプローチが採られることが多いですが、**GetX™**ではこのようなことは必要ありません。
カスタムのThemeDataを作成したら、それを`Get.changeTheme`内に追加するだけです。
```dart
Get.changeTheme(ThemeData.light());
```
もし、`onTap`でThemeを変更するボタンを作りたいのであれば、以下の2つの**GetX™** APIを組み合わせることができます。
- Dark Theme が使われているかどうかをチェックするAPI
- Theme を変えるAPI(ボタンの`onPressed`の中に設置できます)
```dart
Get.changeTheme(Get.isDarkMode? ThemeData.light(): ThemeData.dark());
```
Darkモードが有効であれば、_light theme_に切り替わり、Lightモードが有効なら、_dark theme_に切り替わります。
## GetConnect
GetConnect は、http または websocket を使用してバックエンドとフロントエンド間の通信を行う機能です。
### デフォルト設定
GetConnectを拡張することで、GET/POST/PUT/DELETE/SOCKETメソッドを使用して、Rest APIやウェブソケットと通信することができます。
```dart
class UserProvider extends GetConnect {
// Get リクエスト
Future<Response> getUser(int id) => get('http://youapi/users/$id');
// Post リクエスト
Future<Response> postUser(Map data) => post('http://youapi/users', body: data);
// File付き Post リクエスト
Future<Response<CasesModel>> postCases(List<int> image) {
final form = FormData({
'file': MultipartFile(image, filename: 'avatar.png'),
'otherFile': MultipartFile(image, filename: 'cover.png'),
});
return post('http://youapi/users/upload', form);
}
GetSocket userMessages() {
return socket('https://yourapi/users/socket');
}
}
```
### カスタム設定
GetConnect は高度なカスタマイズが可能です。ベースUrlの定義はもちろん、リクエストヘッダーを足したり、レスポンスボディに変更を加えたり、認証情報を追加したり、認証回数の制限を設けたりすることができるほか、リクエストをModelに変換するデコーダを定義することもできます。
```dart
class HomeProvider extends GetConnect {
@override
void onInit() {
// デフォルトデコーダーをセット
httpClient.defaultDecoder = CasesModel.fromJson;
httpClient.baseUrl = 'https://api.covid19api.com';
// baseUrlをセット
// リクエストヘッダーに 'apikey' プロパティを付け足しています。
httpClient.addRequestModifier((request) {
request.headers['apikey'] = '12345678';
return request;
});
// サーバーが"Brazil"を含むデータを送ってきてもユーザーに表示されることはありません。
// レスポンスがUIレイヤーに届けられる前にデータが取り除かれているからです。
httpClient.addResponseModifier<CasesModel>((request, response) {
CasesModel model = response.body;
if (model.countries.contains('Brazil')) {
model.countries.remove('Brazilll');
}
});
httpClient.addAuthenticator((request) async {
final response = await get("http://yourapi/token");
final token = response.body['token'];
// ヘッダーをセット
request.headers['Authorization'] = "$token";
return request;
});
// HttpStatus が HttpStatus.unauthorized である限り、
// 3回まで認証が試みられます。
httpClient.maxAuthRetries = 3;
}
}
@override
Future<Response<CasesModel>> getCases(String path) => get(path);
}
```
## GetPageにミドルウェアを設定
GetPageに新しいプロパティが追加され、GetMiddleWareのListを設定することができるようになりました。GetMiddleWareは設定した任意の順序で実行されます。
**注**: GetPageにミドルウェアを設定すると、そのページの子ページはすべて同じミドルウェアを自動的に持つことになります。
### 実行優先度
GetMiddlewareに設定したpriority(優先度)の若い順にミドルウェアが実行されます。
```dart
final middlewares = [
GetMiddleware(priority: 2),
GetMiddleware(priority: 5),
GetMiddleware(priority: 4),
GetMiddleware(priority: -8),
];
```
この場合の実行順序は **-8 => 2 => 4 => 5**
### redirect
redirect関数は、Routeを呼び出してページが検索されると実行されます。リダイレクト先のRouteSettingsが戻り値となります。もしくはnullを与えれば、リダイレクトは行われません。
```dart
RouteSettings redirect(String route) {
final authService = Get.find<AuthService>();
return authService.authed.value ? null : RouteSettings(name: '/login')
}
```
### onPageCalled
onPageCalled関数は、ページが呼び出された直後に実行されます。
この関数を使ってページの内容を変更したり、新しいページを作成したりすることができます。
```dart
GetPage onPageCalled(GetPage page) {
final authService = Get.find<AuthService>();
return page.copyWith(title: 'Welcome ${authService.UserName}');
}
```
### onBindingsStart
onBindingsStart関数は、Bindingsが初期化される直前に実行されます。
たとえば、ページのBindingsを変更することもできます。
```dart
List<Bindings> onBindingsStart(List<Bindings> bindings) {
final authService = Get.find<AuthService>();
if (authService.isAdmin) {
bindings.add(AdminBinding());
}
return bindings;
}
```
### onPageBuildStart
onPageBuildStart関数は、Bindingsが初期化された直後、ページWidgetが作成される前に実行されます。
```dart
GetPageBuilder onPageBuildStart(GetPageBuilder page) {
print('bindings are ready');
return page;
}
```
### onPageBuilt
onPageBuilt関数は、GetPage.page(ページのビルダー)が呼び出された直後に実行され、表示されるWidgetを結果として受け取ることができます。
### onPageDispose
onPageDispose関数は、ページに関するすべてのオブジェクト(Controller、ビューなど)が破棄された直後に実行されます。
## その他API
```dart
// 現在の画面に渡されているargs(引数)を取得
Get.arguments
// 直前のRouteの名前("/" など)を取得
Get.previousRoute
// 現在のRouteオブジェクトを取得
Get.rawRoute
// GetObserverからRoutingを取得
Get.routing
// SnackBarが開いているかチェック
Get.isSnackbarOpen
// Dialogが開いているかチェック
Get.isDialogOpen
// BottomSheetが開いているかチェック
Get.isBottomSheetOpen
// Routeを削除
Get.removeRoute()
// 引数のRoutePredicateがtrueを返すまで画面を戻る
Get.until()
// 引数で指定したRouteに進み、RoutePredicateがtrueを返すまで画面を戻る
Get.offUntil()
// 引数で指定した名前付きRouteに進み、RoutePredicateがtrueを返すまで画面を戻る
Get.offNamedUntil()
// アプリがどのプラットフォームで実行されているかのチェック
GetPlatform.isAndroid
GetPlatform.isIOS
GetPlatform.isMacOS
GetPlatform.isWindows
GetPlatform.isLinux
GetPlatform.isFuchsia
// アプリがどのデバイスで実行されているかのチェック
GetPlatform.isMobile
GetPlatform.isDesktop
// プラットフォームとデバイスのチェックは独立
// 同じOSでもウェブで実行されているのか、ネイティブで実行されているのか区別
GetPlatform.isWeb
// MediaQuery.of(context).size.height と同じ
// ただしimmutable
Get.height
Get.width
// Navigatorの現在のcontextを取得
Get.context
// SnackBar/Dialog/BottomSheet などフォアグラウンドのcontextを取得
Get.overlayContext
// 注: 以降のメソッドはcontextの拡張メソッドです。
// contextと同じくUIのどこからでもアクセスできます。
// ウィンドウサイズの変更などに合わせて変わる height/width を取得
context.width
context.height
// 画面の半分のサイズ,1/3のサイズなどを取得
// レスポンシブなデザインの場合に便利
// オプションのパラメーター dividedBy で割る数を指定
// オプションのパラメーター reducedBy でパーセンテージを指定
context.heightTransformer()
context.widthTransformer()
/// MediaQuery.of(context).size とほぼ同じ
context.mediaQuerySize()
/// MediaQuery.of(context).padding とほぼ同じ
context.mediaQueryPadding()
/// MediaQuery.of(context).viewPadding とほぼ同じ
context.mediaQueryViewPadding()
/// MediaQuery.of(context).viewInsets とほぼ同じ
context.mediaQueryViewInsets()
/// MediaQuery.of(context).orientation とほぼ同じ
context.orientation()
/// デバイスがランドスケープ(横長)モードかどうかチェック
context.isLandscape()
/// デバイスがポートレート(縦長)モードかどうかチェック
context.isPortrait()
/// MediaQuery.of(context).devicePixelRatio とほぼ同じ
context.devicePixelRatio()
/// MediaQuery.of(context).textScaleFactor とほぼ同じ
context.textScaleFactor()
/// 画面の短辺の長さを取得
context.mediaQueryShortestSide()
/// 画面の横幅が800より大きい場合にtrueを返す
context.showNavbar()
/// 画面の短辺が600より小さい場合にtrueを返す
context.isPhone()
/// 画面の短辺が600より小さい場合にtrueを返す
context.isSmallTablet()
/// 画面の短辺が720より大きい場合にtrueを返す
context.isLargeTablet()
/// デバイスがタブレットの場合にtrueを返す
context.isTablet()
/// 画面サイズに合わせて value<T> を返す
/// たとえば:
/// 短辺が300より小さい → watchパラメーターの値を返す
/// 短辺が600より小さい → mobileパラメーターの値を返す
/// 短辺が1200より小さい → tabletパラメーターの値を返す
/// 横幅が1200より大きい → desktopパラメーターの値を返す
context.responsiveValue<T>()
```
### オプションのグローバル設定と手動設定
GetMaterialApp はすべてあなたの代わりに設定してくれますが、手動で設定を施したい場合は MaterialApp の navigatorKey と navigatorObservers の値を指定してください。
```dart
MaterialApp(
navigatorKey: Get.key,
navigatorObservers: [GetObserver()],
);
```
`GetObserver`内で独自のミドルウェアを使用することもできます。これは他に影響を及ぼすことはありません。
```dart
MaterialApp(
navigatorKey: Get.key,
navigatorObservers: [
GetObserver(MiddleWare.observer) // ここ
],
);
```
`Get` クラスに_グローバル設定_を施すことができます。Routeをプッシュする前のコードに `Get.config` を追加するだけです。もしくは、`GetMaterialApp` 内で直接設定することもできます。
```dart
GetMaterialApp(
enableLog: true,
defaultTransition: Transition.fade,
opaqueRoute: Get.isOpaqueRouteDefault,
popGesture: Get.isPopGestureEnable,
transitionDuration: Get.defaultDurationTransition,
defaultGlobalState: Get.defaultGlobalState,
);
Get.config(
enableLog = true,
defaultPopGesture = true,
defaultTransition = Transitions.cupertino
)
```
オプションで、すべてのログメッセージを `Get` からリダイレクトさせることができます。
お好みのロギングパッケージを使ってログを取得したい場合はこのようにしてください。
```dart
GetMaterialApp(
enableLog: true,
logWriterCallback: localLogWriter,
);
void localLogWriter(String text, {bool isError = false}) {
// ここでお好みのロギングパッケージにメッセージを渡してください
// enableLog: false にしても、ログメッセージはこのコールバックでプッシュされる点ご注意を
// ログが有効かどうかのチェックは Get.isLogEnable で可能
}
```
### ローカルステートWidget
ローカルステートWidgetは、1つの変数の状態を一時的かつローカルに管理したい場合に便利です。
シンプルなValueBuilderとリアクティブなObxValueの2種類があります。
たとえば、`TextField` Widgetの obscureText プロパティを切り替えたり、折りたたみ可能なパネルをカスタムで作成したり、`BottomNavigation` の現在のインデックス値を変更して内容を変更したりといった用途に最適です。
#### ValueBuilder
setStateでお馴染みの `StatefulWidget` をシンプルにしたビルダーWidgetです。
```dart
ValueBuilder<bool>(
initialValue: false,
builder: (value, updateFn) => Switch(
value: value,
onChanged: updateFn, // ( newValue ) => updateFn( newValue ) も可
),
// builderメソッドの外で何か実行する場合
onUpdate: (value) => print("Value updated: $value"),
onDispose: () => print("Widget unmounted"),
),
```
#### ObxValue
[`ValueBuilder`](#valuebuilder)に似ていますが、これはそのリアクティブバージョンです。Rxインスタンス(.obsを付けたときに戻る値です)を渡すと自動で更新されます。すごいでしょ?
```dart
ObxValue((data) => Switch(
value: data.value,
onChanged: data,
// Rxには_呼び出し可能な_関数が備わっているのでこれだけでOK
// (flag) => data.value = flag も可能
),
false.obs,
),
```
## お役立ちTIPS
`.obs`が付いた型(_Rx_型とも呼ばれる)には、さまざまな内部メソッドや演算子が用意されています。
> `.obs`が付いたプロパティが **実際の値** だと信じてしまいがちですが...間違えないように!
> 我々がcontrollerにおける変数の型宣言を省略してvarとしているのはDartのコンパイラが賢い上に、
> そのほうがコードがすっきる見えるからですが…
```dart
var message = 'Hello world'.obs;
print( 'Message "$message" has Type ${message.runtimeType}');
```
`message`を _print_ することで実際の文字列が取り出されはしますが、型は **RxString** です!
そのため `message.substring( 0, 4 )` などといったことはできません。
Stringのメソッドにアクセスするには _observable_ の中にある実際の値 `value` にアクセスします。
アクセスには `.value`を使うのが通常ですが、他の方法もあるのでご紹介します。
```dart
final name = 'GetX'.obs;
// 新しい値が現在のものと異なる場合のみ Stream が更新されます。
name.value = 'Hey';
// すべてのRxプロパティは「呼び出し可能」で、valueを返してくれます。
// ただし `null` は受付不可。nullの場合はUIが更新されない。
name('Hello');
// これはgetterみたいなものです。'Hello' を返します。
name() ;
/// num型の場合
final count = 0.obs;
// num型の非破壊的な演算子はすべて使えます。
count + 1;
// 注意! この場合は`count`がfinalなら有効ではないです。varなら有効。
count += 1;
// 比較演算子も使用可能
count > 2;
/// bool型の場合
final flag = false.obs;
// true/false を入れ替えることができます。
flag.toggle();
/// すべての型
// `value` を null にセット。
flag.nil();
// toString(), toJson() などの操作はすべて `value` が対象になります。
print( count ); // RxIntの `toString()` が呼び出されて数字がprintされる。
final abc = [0,1,2].obs;
// json配列に変換した値と、'RxList' がprintされます。
// JsonはすべてのRx型でサポートされています!
print('json: ${jsonEncode(abc)}, type: ${abc.runtimeType}');
// RxMap、RxList、RxSetはそれぞれの元の型を拡張した特別なRx型です。
// たとえばRxListは通常のListとして扱うことができる上にリアクティブです。
abc.add(12); // 12をListにプッシュし、Streamを更新してくれます。
abc[3]; // Listと同様にインデックス番号3の値を取得してくれます。
// 等価演算子はRx型と元の型でも動作しますが、.hashCode は常にvalueから取得します。
final number = 12.obs;
print( number == 12 ); // true
/// カスタムのRxモデル
// toJson()やtoString()をモデルクラスに設定すれば、.obsからでもprintされるように実装可能。
class User {
String name, last;
int age;
User({this.name, this.last, this.age});
@override
String toString() => '$name $last, $age years old';
}
final user = User(name: 'John', last: 'Doe', age: 33).obs;
// `user` 自体はリアクティブですが、その中のプロパティはリアクティブではありません。
// そのため、このようにプロパティの値を変更してもWidgetは更新されません。
user.value.name = 'Roi';
// `Rx` には自ら変更を検知する手段がないからです。
// そのため、カスタムクラスの場合はこのようにWidgetに変更を知らせる必要があります。
user.refresh();
// もしくは `update()` メソッドを使用してください。
user.update((value){
value.name='Roi';
});
print( user );
```
#### StateMixin
`UI`の状態を管理するもう一つの手法として、`StateMixin<T>`を利用する方法があります。
controllerクラスに`with`を使って`StateMixin<T>`を追加することで実装可能です。
``` dart
class Controller extends GetController with StateMixin<User>{}
```
`change()`メソッドにより好きなタイミングで状態を変更することができます。
このようにデータと状態を渡すだけです。
```dart
change(data, status: RxStatus.success());
```
RxStatus には以下のステータスが存在します。
``` dart
RxStatus.loading();
RxStatus.success();
RxStatus.empty();
RxStatus.error('message');
```
ステータスごとにUIを設定するにはこのようにします。
```dart
class OtherClass extends GetView<Controller> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: controller.obx(
(state)=>Text(state.name),
// ローディング中はカスタムのインジケーターの設定も可能ですが、
// デフォルトで Center(child:CircularProgressIndicator()) となります。
onLoading: CustomLoadingIndicator(),
onEmpty: Text('No data found'),
// 同様にエラーWidgetはカスタム可能ですが、
// デフォルトは Center(child:Text(error)) です。
onError: (error)=>Text(error),
),
);
}
```
#### GetView
このWidgetは私のお気に入りです。とてもシンプルで扱いやすいですよ!
このWidgetを一言で表現すると、「controllerをgetterに持つ `const` な StatelessWidget」です。
```dart
class AwesomeController extends GetController {
final String title = 'My Awesome View';
}
// controllerの `型` を渡すのを忘れずに!
class AwesomeView extends GetView<AwesomeController> {
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(20),
child: Text(controller.title), // `controller.なんとか` でアクセス
);
}
}
```
#### GetResponsiveView
GetViewをレスポンシブデザインに対応させたい場合はこのWidgetを継承してください。
画面サイズやデバイスタイプなどの情報を持つ `screen` プロパティを保持しています。
##### 使い方
Widgetをビルドする方法は2つあります。
- `builder` メソッドを使う。
- `desktop`, `tablet`, `phone`, `watch` メソッドを使う。
画面サイズ、デバイスタイプに応じたWidgetがビルドされます。
たとえば画面が [ScreenType.Tablet] なら `tablet` メソッドが実行されます。
**注:** `alwaysUseBuilder` プロパティをfalseにする必要があります。
`settings` プロパティでブレイクポイントを設定することもできます。
![](https://github.com/SchabanBo/get_page_example/blob/master/docs/Example.gif?raw=true)
この画面のコード
[コード](https://github.com/SchabanBo/get_page_example/blob/master/lib/pages/responsive_example/responsive_view.dart)
#### GetWidget
このWidgetはあまり知られておらず、使用するケースは稀です。
GetViewとの違いは、Controllerを`キャッシュ`してくれる点です。
このキャッシュがあるため `const` にはできません。
> それでは一体いつControllerをキャッシュする必要があるのかって?
それは **GetX** のこれまた使う機会の少ない `Get.create()` を使うときです。
`Get.create(()=>Controller())` は `Get.find<Controller>()` を実行するたびに
新しいControllerインスタンスを生成します。
そこで `GetWidget` の出番です。たとえば、Todoアイテムのリスト内容を保持したいとき。
Widgetが更新されてもアイテムはControllerのキャッシュを参照してくれます。
#### GetxService
このクラスは `GetxController` に似ており、同様のライフサイクル(`onInit()`, `onReady()`, `onClose()`)を共有しますが、そこに「ロジック」はありません。**GetX**の依存オブジェクト注入システムに、このサブクラスがメモリから **削除できない** ということを知らせるだけです。
そのため `Get.find()` で `ApiService`, `StorageService`, `CacheService` のようなサービス系クラスにいつでもアクセスできるようにしておくと非常に便利です。
```dart
Future<void> main() async {
await initServices(); /// サービスクラスの初期化をawait
runApp(SomeApp());
}
/// Flutterアプリ実行前にサービスクラスを初期化してフローをコントロールするのは賢いやり方です。
/// たとえば GetMaterialAppを更新する必要がないようにUser別の
/// Theme、apiKey、言語設定などをApiサービス実行前にロードしたり。
void initServices() async {
print('starting services ...');
/// get_storage, hive, shared_pref の初期化はここで行います。
/// あるいは moor の connection など非同期のメソッドならなんでも。
await Get.putAsync(() => DbService().init());
await Get.putAsync(SettingsService()).init();
print('All services started...');
}
class DbService extends GetxService {
Future<DbService> init() async {
print('$runtimeType delays 2 sec');
await 2.delay();
print('$runtimeType ready!');
return this;
}
}
class SettingsService extends GetxService {
void init() async {
print('$runtimeType delays 1 sec');
await 1.delay();
print('$runtimeType ready!');
}
}
```
`GetxService` を破棄する唯一の方法は `Get.reset()` メソッドを使うことです。
これはアプリにおける「ホットリブート」のようなものです。あるクラスのインスタンスを
ライフサイクルの間ずっと残しておきたい場合は `GetxService` を使うというのを覚えておいてください。
## テストの実行
Controllerのライフサイクル含め、他のクラスと同様にテストを実行することができます。
```dart
class Controller extends GetxController {
@override
void onInit() {
super.onInit();
// 値を name2 に変更
name.value = 'name2';
}
@override
void onClose() {
name.value = '';
super.onClose();
}
final name = 'name1'.obs;
void changeName() => name.value = 'name3';
}
void main() {
test('''
Test the state of the reactive variable "name" across all of its lifecycles''',
() {
/// ライフサイクルごとのテストは必ずしも行う必要はありませんが、
/// GetXの依存オブジェクト注入機能を使用しているのであれば実行をおすすめします。
final controller = Controller();
expect(controller.name.value, 'name1');
/// このようにライフサイクル経過ごとの状態をテスト可能です。
Get.put(controller); // onInit が実行される
expect(controller.name.value, 'name2');
/// 関数もテストしましょう
controller.changeName();
expect(controller.name.value, 'name3');
/// onClose が実行される
Get.delete<Controller>();
expect(controller.name.value, '');
});
}
```
#### mockitoやmocktailを使う場合
GetxController/GetxService をモックする場合 Mock をミックスインしてください。
```dart
class NotificationServiceMock extends GetxService with Mock implements NotificationService {}
```
#### Get.reset()
WidgetやGroupのテスト時に、テストの最後かtearDownの中で Get.reset() を実行することで設定をリセットすることができます。
#### Get.testMode
Controllerの中でナビゲーションを使用している場合は、`Get.testMode = true`をmainの開始で実行してください。
# バージョン2.0からの破壊的変更
1- Rx型の名称
| 変更前 | 変更後 |
| ------- | ---------- |
| StringX | `RxString` |
| IntX | `RxInt` |
| MapX | `RxMap` |
| ListX | `RxList` |
| NumX | `RxNum` |
| DoubleX | `RxDouble` |
RxControllerとGetBuilderが統合され、Controllerにどれを使うか覚えておく必要がなくなりました。GetxControllerを使うだけで、リアクティブと非リアクティブな状態管理の両方に対応できるようになりました。
2- 名前付きRoute
変更前:
```dart
GetMaterialApp(
namedRoutes: {
'/': GetRoute(page: Home()),
}
)
```
変更後:
```dart
GetMaterialApp(
getPages: [
GetPage(name: '/', page: () => Home()),
]
)
```
変更の効果:
ページ表示にはパラメータやログイントークンを起点にする方法もありますが、以前のアプローチではこれができず、柔軟性に欠けていました。
ページを関数から取得するよう変更したことで、このようなアプローチを可能にし、アプリ起動直後にRouteがメモリに割り当てられることもないため、RAMの消費量を大幅に削減することもできました。
```dart
GetStorage box = GetStorage();
GetMaterialApp(
getPages: [
GetPage(name: '/', page:(){
return box.hasData('token') ? Home() : Login();
})
]
)
```
# なぜGetXなのか
1- Flutterのアップデートが重なると、依存パッケージがうまく動かなくなることがあります。コンパイルエラーを起こしたり、その時点で解決方法がないようなエラーが発生したり。開発者はそのエラーを追跡し、該当リポジトリにissueを提起し、問題が解決されるのを待つ必要があります。Getは開発に必要な主要リソース(状態管理、依存オブジェクト管理、Route管理)を一元化し、Pubspecにパッケージを1つ追加するだけでコーディングを開始することができます。Flutterがアップデートしたときに必要なことは、Getも併せてアップデートすることだけです。それですぐに作業を再開できます。またGetはパッケージ間の互換性の問題も解消します。互いに依存するパッケージAの最新バージョンとBの最新バージョンの間に互換性がない、ということが何度あったでしょうか。Getを使えばすべてが同じパッケージ内にあるため、互換性の心配はありません。
2- Flutterは手軽で素晴らしいフレームワークですが、`Navigator.of(context).push (context, builder [...]`のように、ほとんどの開発者にとって不要な定型文が一部にあります。Getを使えばそのような定型文を簡素化できます。Routeを呼ぶためだけに8行のコードを書く代わりに、`Get.to(Home())`を実行すれば、次の画面に遷移することができるのです。またウェブURLを動的なものにすることは現在Flutterでは本当に骨の折れる作業ですが、GetXを使えば非常に簡単です。そしてFlutterにおける状態管理と依存オブジェクト管理については、たくさんのパターンやパッケージがあるので多くの議論を生んでいます。しかしGetXのアプローチは大変シンプルです。これは一例ですが、変数の最後に「.obs」を追加して「Obx()」の中にWidgetを配置するだけで、その変数の状態変化が自動でWidgetに反映されます。
3- GetXではパフォーマンスのことをあまり気にせず開発ができます。Flutterのパフォーマンスはそれだけで素晴らしいものですが、状態管理と併せて BLoC / データストア / Controller などを届けるためのサービスロケーターを使用することを想像してみてください。そのインスタンスが必要ないときはリソースを解放するイベントを明示的に呼び出さなければなりません。そんなとき、使用されなくなったら自動でメモリから削除してくれればいいのに、と考えたことはありませんか?それを実現してくれるのがGetXです。SmartManagement機能により未使用のリソースはすべて自動でメモリから破棄されるので、本来の開発作業に集中することができます。メモリ管理のためのロジックを作らなくても、常に必要最小限のリソースを使っていることが保証されるのです。
4- コードのデカップリング(分離)がしやすい。「ビューをビジネスロジックから分離する」というコンセプトを聞いたことがあるかもしれません。これはなにもBLoC、MVC、MVVMに限ったことではなく、どのアーキテクチャパターンもこのコンセプトが考え方の基本にあると言っていいでしょう。しかし、Flutterではcontextの使用によりこのコンセプトが薄まってしまいがちです。
InheritedWidgetを参照するためにcontextが必要なとき、ビューの中でそれを使用するか、引数としてcontextを渡しますよね?私はこの方法は美しくないと感じます。常にビュー内のビジネスロジックに依存しなければならないのは、特にチームで仕事をする場面においては不便だと感じます。GetXによるアプローチでは、StatefulWidgetやinitStateなどの使用を禁止しているわけではありませんが、それらよりもずっとスッキリ書けるようになっています。Controller自体にライフサイクルがあるため、たとえばREST APIのリクエストを行うときも、ビューの中の何かに依存するということがありません。Controllerのライフサイクルの一つである onInit を使用してhttpを呼び出し、データが到着すると変数にセットされます。GetXはリアクティブな変数を扱うことができるので、インスタンス変数が変わりし次第、その変数に依存するWidgetがすべて自動更新されます。これによりUIの担当者はWidgetの見た目に注力することができ、ボタンクリックなどのユーザーイベント以外のものをビジネスロジックに渡す必要がなくなります。その一方でビジネスロジックの担当者はビジネスロジックだけに集中し、個別のテストを簡単に行うことができます。
GetXライブラリは今後も更新され続け、新しい機能を実装していきます。気軽にプルリクエストを出していただき、ライブラリの成長に貢献していただけるとうれしいです。
# コミュニティ
## コミュニティチャンネル
GetXコミュニティは非常に活発で有益な情報であふれています。ご質問がある場合や、このフレームワークの使用に関して支援が必要な場合は、ぜひコミュニティチャンネルにご参加ください。このリポジトリは、issueの提起およびリクエスト専用ですが、気軽にコミュニティにご参加いただければ幸いです。
| **Slack** | **Discord** | **Telegram** |
| :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------- |
| [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://communityinviter.com/apps/getxworkspace/getx) | [![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N) | [![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g) |
## コントリビュート方法
_GetXプロジェクトに貢献してみませんか?あなたをコントリビューターの一人としてご紹介できるのを楽しみにしています。GetおよびFlutterをより良いものにするためのコントリビュート例をご紹介します。_
- Readmeの多言語対応。
- Readmeの追加ドキュメント執筆 (ドキュメントで触れられていない機能がまだまだたくさんあります)。
- Getの使い方を紹介する記事やビデオの作成(Readmeに掲載させていただきます。将来的にWikiができればそこにも掲載予定)。
- コードやテストのプルリクエスト。
- 新機能の提案。
どのような形の貢献であれ歓迎しますので、ぜひコミュニティにご参加ください!
## GetXに関する記事と動画
- [Flutter Getx EcoSystem package for arabic people](https://www.youtube.com/playlist?list=PLV1fXIAyjeuZ6M8m56zajMUwu4uE3-SL0) - Tutorial by [Pesa Coder](https://github.com/UsamaElgendy).
- [Dynamic Themes in 3 lines using GetX™](https://medium.com/swlh/flutter-dynamic-themes-in-3-lines-c3b375f292e3) - Tutorial by [Rod Brown](https://github.com/RodBr).
- [Complete GetX™ Navigation](https://www.youtube.com/watch?v=RaqPIoJSTtI) - Route management video by Amateur Coder.
- [Complete GetX State Management](https://www.youtube.com/watch?v=CNpXbeI_slw) - State management video by Amateur Coder.
- [GetX™ Other Features](https://youtu.be/ttQtlX_Q0eU) - Utils, storage, bindings and other features video by Amateur Coder.
- [Firestore User with GetX | Todo App](https://www.youtube.com/watch?v=BiV0DcXgk58) - Video by Amateur Coder.
- [Firebase Auth with GetX | Todo App](https://www.youtube.com/watch?v=-H-T_BSgfOE) - Video by Amateur Coder.
- [The Flutter GetX™ Ecosystem ~ State Management](https://medium.com/flutter-community/the-flutter-getx-ecosystem-state-management-881c7235511d) - State management by [Aachman Garg](https://github.com/imaachman).
- [The Flutter GetX™ Ecosystem ~ Dependency Injection](https://medium.com/flutter-community/the-flutter-getx-ecosystem-dependency-injection-8e763d0ec6b9) - Dependency Injection by [Aachman Garg](https://github.com/imaachman).
- [GetX, the all-in-one Flutter package](https://www.youtube.com/watch?v=IYQgtu9TM74) - A brief tutorial covering State Management and Navigation by Thad Carnevalli.
- [Build a To-do List App from scratch using Flutter and GetX](https://www.youtube.com/watch?v=EcnqFasHf18) - UI + State Management + Storage video by Thad Carnevalli.
- [GetX Flutter Firebase Auth Example](https://medium.com/@jeffmcmorris/getx-flutter-firebase-auth-example-b383c1dd1de2) - Article by Jeff McMorris.
- [Flutter State Management with GetX – Complete App](https://www.appwithflutter.com/flutter-state-management-with-getx/) - by App With Flutter.
- [Flutter Routing with Animation using Get Package](https://www.appwithflutter.com/flutter-routing-using-get-package/) - by App With Flutter.
- [A minimal example on dartpad](https://dartpad.dev/2b3d0d6f9d4e312c5fdbefc414c1727e?) - by [Roi Peker](https://github.com/roipeker)
... ...
# 依存オブジェクト管理
- [依存オブジェクト管理](#依存オブジェクト管理)
- [インスタンス化に使用するメソッド](#インスタンス化に使用するメソッド)
- [Get.put()](#getput)
- [Get.lazyPut()](#getlazyput)
- [Get.putAsync()](#getputasync)
- [Get.create()](#getcreate)
- [インスタンス化したクラスを使う](#インスタンス化したクラスを使う)
- [依存オブジェクトの置換](#依存オブジェクトの置換)
- [各メソッドの違い](#各メソッドの違い)
- [Bindings(Routeと依存オブジェクトの結束)](#Bindings(Routeと依存オブジェクトの結束))
- [Bindingsクラス](#bindingsクラス)
- [BindingsBuilder](#bindingsbuilder)
- [SmartManagement](#smartmanagement)
- [設定の変更方法](#設定の変更方法)
- [SmartManagement.full](#smartmanagementfull)
- [SmartManagement.onlyBuilder](#smartmanagementonlybuilder)
- [SmartManagement.keepFactory](#smartmanagementkeepfactory)
- [Bindingsの仕組み](#Bindingsの仕組み)
- [補足](#補足)
Getにはシンプルで強力な依存オブジェクト管理機能があります。たった1行のコードで、Provider contextやinheritedWidgetを使うことなく、BlocもしくはControllerのインスタンスを取得することができます。
```dart
Controller controller = Get.put(Controller()); // Controller controller = Controller() の代わりに
```
UIクラスの中でControllerクラスをインスタンス化する代わりに、Getインスタンスの中でインスタンス化することで、アプリ全体でControllerを利用できるようになります。
- 注: Getの状態管理機能を使用する場合は、[Bindings](#bindings)の使用も検討してください。Bindingsを使うことでビューにControllerを結合させることができます。
- 注²: Getの依存オブジェクト管理機能は、パッケージの他の部分から独立しています。そのため、たとえばあなたのアプリが既に他の状態管理ライブラリを使用している場合(どんなものでも)、何の問題もなく2つを組み合わせることができます。
## インスタンス化に使用するメソッド
Controllerを初期化するのに使用するメソッドとその設定パラメーターについてご説明します。
### Get.put()
依存オブジェクトを注入するための最も基本のメソッド。たとえば、ビューで使用するControllerに使います。
```dart
Get.put<SomeClass>(SomeClass());
Get.put<LoginController>(LoginController(), permanent: true);
Get.put<ListItemController>(ListItemController, tag: "some unique string");
```
以下が put() を使用する際に設定できるパラメーターです。
```dart
Get.put<S>(
// 必須。インスタンスを保存しておきたいControllerなどを設定
// 注: "S" 型はジェネリクスで、どんな型でもOK
S dependency
// オプション。これは同じ型のControllerインスタンスが複数存在する場合に、
// タグIDにより識別したい場合に使用します。
// Get.find<Controller>(tag: ) でこのputで設定したインスタンスを探します。
// tag はユニークなStringである必要があります。
String tag,
// オプション。デフォルトでは使用されなくなったインスタンスは破棄されますが、
// (たとえばビューが閉じられた場合など) SharedPreferencesのインスタンスなど、
// アプリ全体を通して生かしておきたい場合があるかと思います。
// その場合はこれをtrueにしてください。デフォルトはfalseです。
bool permanent = false,
// オプション。テストで抽象クラスを使用した後、別クラスに置換してテストを追うことができます。
// デフォルトはfalseです。
bool overrideAbstract = false,
// オプション: 依存オブジェクトを関数を使って作ることができます。
// 使用頻度は低いと思います。
InstanceBuilderCallback<S> builder,
)
```
### Get.lazyPut()
依存オブジェクトをすぐにロードする代わりに、lazy(遅延、消極的)ロードすることができます。実際に使用されるときに初めてインスタンス化されます。計算量の多いクラスや、Bindingsを使って複数のControllerをまとめてインスタンス化したいが、その時点ではすぐにそれらを使用しないことがわかっている場合などに非常に便利です。
```dart
/// この場合のApiMockは Get.find<ApiMock> を使用した時点でインスタンス化されます。
Get.lazyPut<ApiMock>(() => ApiMock());
Get.lazyPut<FirebaseAuth>(
() {
// 必要ならここに何かのロジック
return FirebaseAuth();
},
tag: Math.random().toString(),
fenix: true
)
Get.lazyPut<Controller>( () => Controller() )
```
これが .lazyPut で設定できるパラメーターです。
```dart
Get.lazyPut<S>(
// 必須。クラスSが初めてfindの対象になったときに実行されるメソッド
InstanceBuilderCallback builder,
// オプション。Get.put()のtagと同様に同じクラスの異なるインスタンスが必要なときに使用
// ユニークな値を指定
String tag,
// オプション。"permanent" に似ていますが、使用されていないときはインスタンスが
// 破棄され、再び使用するときにGetがインスタンスを再び作成する点が異なります。
// Bindings APIの "SmartManagement.keepFactory " と同じです。
// デフォルトはfalse
bool fenix = false
)
```
### Get.putAsync()
SharedPreferencesなど、非同期のインスタンスを登録したいときに使います。
```dart
Get.putAsync<SharedPreferences>(() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setInt('counter', 12345);
return prefs;
});
Get.putAsync<YourAsyncClass>( () async => await YourAsyncClass() )
```
これが .putAsync で設定できるパラメーターです。
```dart
Get.putAsync<S>(
// 必須。クラスをインスタンス化するための非同期メソッドを指定
AsyncInstanceBuilderCallback<S> builder,
// オプション。Get.put() と同じです。同じクラスの異なるインスタンスを作りたいときに使用
// ユニークな値を指定
String tag,
// オプション。Get.put() と同じです。アプリ全体を通して生かしておきたい場合に使用
// デフォルトはfalse
bool permanent = false
)
```
### Get.create()
これは使いどころに迷うかもしれません。他のものとの違いなど詳細な説明は「[各メソッドの違い](#各メソッドの違い)」のセクションをご一読ください。
```dart
Get.Create<SomeClass>(() => SomeClass());
Get.Create<LoginController>(() => LoginController());
```
これが .create で設定できるパラメーターです。
```dart
Get.create<S>(
// 必須。Get.find() が呼ばれるたびにインスタンスがこの関数で新たに組み立てられる。
// 例: Get.create<YourClass>(() => YourClass())
FcBuilderFunc<S> builder,
// オプション。Get.put() のtagと同様で、同じクラスによる
// 複数インスタンスを扱うときに使用します。
// リストのアイテムにそれぞれコントローラが必要な場合に便利です。
// 文字列はユニークである必要があります。
String name,
// オプション。Get.put() と同様アプリ全体でインスタンスを維持するときに使用。
// 唯一の違いは Get.create() のpermanentはデフォルトでtrueということだけです。
bool permanent = true
```
## インスタンス化したクラスを使う
いくつかのRouteを渡り歩いた後、以前のControllerに残してきたデータが必要になったとしたら、Provider や Get_it と組み合わせる必要がありますよね?Getの場合は違います。GetにControllerの「検索」を依頼するだけで追加の依存オブジェクトの注入は必要ありません。
```dart
final controller = Get.find<Controller>();
// もしくは
Controller controller = Get.find();
// マジックみたいですよね。でも実際にGetはControllerのインスタンスを探して届けてくれます。
// 100万ものControllerをインスタンス化していても、Getは常に正しいControllerを探してくれます。
```
そしてそのControllerが以前取得したデータをあなたは復元することができます。
```dart
Text(controller.textFromApi);
```
戻り値は通常のクラスなので、そのクラスで可能なことは何でもできます。
```dart
int count = Get.find<SharedPreferences>().getInt('counter');
print(count); // 出力: 12345
```
インスタンスを明示的に削除したい場合はこのようにしてください。
```dart
Get.delete<Controller>(); // 通常であれば、GetXは未使用Controllerを自動削除するので、この作業は必要ありません。
```
## 依存オブジェクトの置換
注入されている依存オブジェクトは `replace` または `lazyReplace` メソッドを使って、子クラスなど関連クラスのインスタンスに置き換えることができます。これは元の抽象クラスを型指定することで取得することができます。
```dart
abstract class BaseClass {}
class ParentClass extends BaseClass {}
class ChildClass extends ParentClass {
bool isChild = true;
}
Get.put<BaseClass>(ParentClass());
Get.replace<BaseClass>(ChildClass());
final instance = Get.find<BaseClass>();
print(instance is ChildClass); //true
class OtherClass extends BaseClass {}
Get.lazyReplace<BaseClass>(() => OtherClass());
final instance = Get.find<BaseClass>();
print(instance is ChildClass); // false
print(instance is OtherClass); //true
```
## Differences between methods
まずは Get.lazyPut の `fenix` プロパティと、他メソッドの `permanent` プロパティの違いについてご説明します。
`permanent` と `fenix` の根本的な違いは、インスタンスをどのように保持したいかという点に尽きます。
しつこいようですが、GetXでは使われていないインスタンスは削除されるのがデフォルトの動作です。
これはもし画面AがController A、画面BがController Bを持っている場合において、画面Bに遷移するときに画面Aをスタックから削除した場合(`Get.off()` や `Get.offNamed()` を使うなどして)、Controller Aは消えてなくなるということです。
しかし Get.put() する際に `permanent:true` としていれば、Controller Aはこの画面削除により失われることはありません。これはアプリケーション全体を通してControllerを残しておきたい場合に大変便利です。
一方の `fenix` は、画面削除に伴っていったんはControllerが消去されますが、再び使いたいと思ったときに復活させることができます。つまり基本的には未使用の Controller / サービス / その他クラス は消去されますが、必要なときは新しいインスタンスを「燃えカス」から作り直すことができるのです。
各メソッドを使用する際のプロセスの違いをご説明します。
- Get.put と Get.putAsync はインスタンスを作成して初期化するプロセスは同じですが、後者は非同期メソッドを使用するという違いがあります。この2つのメソッドは内部に保有するメソッド `insert` に `permanent: false` と `isSingleton: true` という引数を渡して、メモリに直接インスタンスを挿入します (この isSingleton が行っていることは、依存オブジェクトを `dependency` と `builder` プロパティのどちらから拝借するかを判断することだけです)。その後に `Get.find()` が呼ばれると、メモリ上にあるインスタンスを即座に初期化するというプロセスをたどります。
- Get.create はその名の通り、依存オブジェクトを「クリエイト」します。Get.put() と同様に内部メソッドである `insert` を呼び出してインスタンス化します。違いは `permanent: true` で `isSingleton: false` である点です (依存オブジェクトを「クリエイト」しているため、シングルトンにはなりません。それが false になっている理由です)。また `permanent: true` となっているので、デフォルトでは画面の破棄などでインスタンスを失わないというメリットがあります。また `Get.find()` はすぐに呼ばれず、画面内で実際に使用されてから呼ばれます。これは `permanent` の特性を活かすための設計ですが、それゆえ `Get.create()` は共有しないけど破棄もされないインスタンスを作成する目的で作られたと言えます。たとえば、ListViewの中のボタンアイテムに使うControllerインスタンスのように、そのリスト内でしか使わないけどリストアイテムごとに固有のインスタンスが必要なケースなどが考えられます。そのため、Get.create は GetWidget との併用がマストです。
- Get.lazyPut は初期化をlazy(遅延、消極的)に行います。実行されるとインスタンスは作成されますが、すぐに使用できるように初期化はされず、待機状態になります。また他のメソッドと異なり `insert` メソッドは呼び出されません。その代わり、インスタンスはメモリの別の部分に挿入されます。この部分を「ファクトリー」と呼ぶことにしましょう。「ファクトリー」は、そのインスタンスが再生成できるかどうかを決める役割を持っています。これは後で使う予定のものを、現在進行形で使われているものと混ざらないようにするための工夫です。ここで `fenix` によるマジックが登場します。デフォルトの `fenix: false` のままにしており、かつ `SmartManagement` が `keepFactory` ではない場合において `Get.find` を使用すると、インスタンスは「ファクトリー」から共有メモリ領域に移動します。その直後にインスタンスは「ファクトリー」から削除されます。しかし `fenix: true` としていた場合、インスタンスは「ファクトリー」に残るため、共有メモリ領域から削除されても再び呼び出すことができるのです。
## Bindings(Routeと依存オブジェクトの結束)
このパッケージの一番の差別化要素は、Route管理 / 状態管理 / 依存オブジェクト管理 を統合したことにあると思っています。
スタックからRouteが削除されれば、関係するController、変数、オブジェクトのインスタンスがすべてメモリから削除されます。たとえばStreamやTimerを使用している場合も同様ですので、開発者は何も心配する必要はありません。
Getはバージョン2.10からBindings APIをフル実装しました。
Bindingsを使用すれば init でControllerを起動する必要はありません。またControllerの型を指定する必要もありません。Controllerやサービスは各々適切な場所で起動することができるようになりました。
Bindingsは依存オブジェクトの注入をビューから切り離すことができるクラスです。それに加え、状態と依存オブジェクトの管理機能をRouteに「結束(bind)」してくれます。
これによりGetは、あるControllerが使用されたときにどの画面UIが表示されているかを知ることができます。つまり、そのControllerをどのタイミングでどう処分するかを判断することができるということです。
さらにBindingsでは SmartManager の制御により、依存オブジェクトをどのタイミング(スタックからRouteを削除したときか、それに依存するWidgetを表示したときか、いずれでもないか)で整理するかを設定することができます。インテリジェントな依存オブジェクトの自動管理機能を持ちつつ、自分の好きなように設定できるのです。
### Bindingsクラス
- Bindings機能を実装したクラスを作成することができます。
```dart
class HomeBinding implements Bindings {}
```
"dependencies" メソッドをオーバーライドするようIDEに自動で指摘されます。表示をクリックしてメソッドを override し、そのRoute内で使用するすべてのクラスを挿入してください。
```dart
class HomeBinding implements Bindings {
@override
void dependencies() {
Get.lazyPut<HomeController>(() => HomeController());
Get.put<Service>(()=> Api());
}
}
class DetailsBinding implements Bindings {
@override
void dependencies() {
Get.lazyPut<DetailsController>(() => DetailsController());
}
}
```
Bindingsを設定したら、このクラスが Route管理 / 依存オブジェクト管理 / 状態管理 を互いに接続する目的で使用されるものだということをRouteに知らせてあげます。
- 名前付きRouteを使う場合
```dart
getPages: [
GetPage(
name: '/',
page: () => HomeView(),
binding: HomeBinding(),
),
GetPage(
name: '/details',
page: () => DetailsView(),
binding: DetailsBinding(),
),
];
```
- 通常のRouteを使う場合
```dart
Get.to(Home(), binding: HomeBinding());
Get.to(DetailsView(), binding: DetailsBinding())
```
これでアプリケーションのメモリ管理を気にする必要がなくなります。Getがすべてやってくれます。
BindingsクラスはRouteの呼び出しと同時に呼び出されます。また、すべてに共通の依存オブジェクトを挿入するためには GetMaterialApp の initialBinding プロパティを使用してください。
```dart
GetMaterialApp(
initialBinding: SampleBind(),
home: Home(),
);
```
### BindingsBuilder
Bindingsを作成する一般的な方法は Bindings を実装したクラスを作成することですが、`BindingsBuilder` コールバックを使う方法もあります。
Example:
```dart
getPages: [
GetPage(
name: '/',
page: () => HomeView(),
binding: BindingsBuilder(() {
Get.lazyPut<ControllerX>(() => ControllerX());
Get.put<Service>(()=> Api());
}),
),
GetPage(
name: '/details',
page: () => DetailsView(),
binding: BindingsBuilder(() {
Get.lazyPut<DetailsController>(() => DetailsController());
}),
),
];
```
この方法ならRouteごとにBindingsクラスを作る必要はありません。
どちらの方法でも効果は変わりませんのでお好みの方法を使ってください。
### SmartManagement
エラーが発生してControllerを使用するWidgetが正しく破棄されなかった場合でも、Controllerが未使用になればGetXはデフォルトの動作通りそれをメモリから削除します。
これがいわゆる依存オブジェクト管理機能の `full` モードと呼ばれるものです。
しかしもしGetXによるオブジェクト破棄の方法をコントロールしたい場合は、`SmartManagement`クラスを使って設定してください。
#### 設定の変更方法
この設定は通常変更する必要はありませんが、変更されたい場合はこのようにしてください。
```dart
void main () {
runApp(
GetMaterialApp(
smartManagement: SmartManagement.onlyBuilder // ここで設定
home: Home(),
)
)
}
```
#### SmartManagement.full
これがデフォルトのモードです。使用されていない、かつ `permanent: true` が設定されていないオブジェクトを自動で破棄してくれます。特殊な事情がない限り、この設定は触らない方がいいでしょう。GetXを使って間がない場合は特に。
#### SmartManagement.onlyBuilder
`init:` もしくはBindings内で `Get.lazyPut()` により設定したビルダー製のオブジェクトだけを破棄するモードです。
もしそれが `Get.put()` や `Get.putAsync()` などのアプローチで生成したオブジェクトだとしたら、SmartManagement は勝手にメモリから除外することはできません。
それに対してデフォルトのモードでは `Get.put()` で生成したオブジェクトも破棄します。
#### SmartManagement.keepFactory
SmartManagement.full と同じように、オブジェクトが使用されていない状態になれば破棄します。ただし、前述の「ファクトリー」に存在するものだけは残します。つまりそのインスタンスが再び必要になった際は依存オブジェクトを再度生成するということです。
### Bindingsの仕組み
Bindingsは「一過性のファクトリー」のようなものを作成します。これはそのRouteに画面遷移した瞬間に作成され、そこから画面移動するアニメーションが発生した瞬間に破棄されます。
この動作は非常に高速で行われるので、アナライザーでは捕捉できないほどです。
再び元の画面に戻ると新しい「一過性のファクトリー」が呼び出されます。そのためこれは SmartManagement.keepFactory を使用するよりも多くの場合好ましいですが、Bindingsを作成したくない場合やすべての依存オブジェクトを同じBindingsに持っておきたい場合は SmartManagement.keepFactory を使うといいでしょう。
ファクトリーのメモリ使用量は少なく、インスタンスを保持することはありません。その代わりにそのクラスのインスタンスを形作る関数を保持します。
メモリコストは非常に低いのですが、最小リソースで最大パフォーマンスを得ることが目的のGetではデフォルトでファクトリーを削除します。
どちらか都合に合う方ををお使いいただければと思います。
## 補足
- 複数のBindingsを使う場合は SmartManagement.keepFactory は**使わない**でください。これは Bindings を使わないケースや、GetMaterialAppのinitialBindingに設定された単独のBindingと一緒に使うケースを想定されて作られました。
- Bindingsを使うことは必須ではありません。`Get.put()` と `Get.find()` だけでも全く問題ありません。
ただし、サービスやその他抽象度の高いクラスをアプリに取り入れる場合はコード整理のために使うことをおすすめします。
... ...
- [Route管理](#Route管理)
- [使い方](#使い方)
- [通常Routeによるナビゲーション](#通常Routeによるナビゲーション)
- [名前付きRouteによるナビゲーション](#名前付きRouteによるナビゲーション)
- [名前付きRouteにデータを送る](#名前付きRouteにデータを送る)
- [動的URLの生成](#動的URLの生成)
- [ミドルウェアの使用](#ミドルウェアの使用)
- [contextを使わないナビゲーション](#contextを使わないナビゲーション)
- [SnackBar](#snackbar)
- [Dialog](#dialog)
- [BottomSheet](#bottomsheet)
- [ネスト構造のナビゲーション](#ネスト構造のナビゲーション)
# Route管理
このドキュメントではGetXにおけるRoute管理のすべてをご説明します。
## How to use
次の3文字を pubspec.yaml ファイルに追加してください。
```yaml
dependencies:
get:
```
Route / SnackBar / Dialog / BottomSheet をcontextなしで、あるいは高レベルのGet APIを使用するには MaterialApp の前に「Get」を追加してください。それだけで GetMaterialApp の機能が使用できます。
```dart
GetMaterialApp( // 変更前: MaterialApp(
home: MyHome(),
)
```
## 名前付きRouteによる画面遷移
次の画面に遷移するには Get.to を使ってください。
```dart
Get.to(NextScreen());
```
SnackBar / Dialog / BottomSheet など Navigator.pop(context) で閉じるものと同じものを閉じるには Get.back を使います。
```dart
Get.back();
```
次の画面に遷移しつつ、前の画面に戻れないようにするには Get.off を使います(スプラッシュスクリーンやログイン画面などで使用)。
```dart
Get.off(NextScreen());
```
次の画面に遷移して、それ以前のRouteはすべて破棄するには Get.offAll を使います(ショッピングカート、投票、テストなどで使用)
```dart
Get.offAll(NextScreen());
```
次の画面に遷移して、戻ったらデータを受け取る方法はこちら。
```dart
var data = await Get.to(Payment());
```
次の画面では、このようにデータを前の画面に送る必要があります。
```dart
Get.back(result: 'success');
```
そして使いましょう。
ex:
```dart
if(data == 'success') madeAnything();
```
どのようなシンタックスがあるかもっと知りたいですか?
いつもの Navigator ではなく navigator と入れてみてください。通常のNavigatorで使えるプロパティがcontextなしで使えるようになっているかと思います。
```dart
// 通常のFlutterによるNavigator
Navigator.of(context).push(
context,
MaterialPageRoute(
builder: (BuildContext context) {
return HomePage();
},
),
);
// GetではFlutterのシンタックスをcontextなしで使えます
navigator.push(
MaterialPageRoute(
builder: (_) {
return HomePage();
},
),
);
// Getのシンタックス(上記よりかなり短いですね)
Get.to(HomePage());
```
## 名前付きRouteによる画面遷移
- Getは名前付きRouteによる遷移もサポートしています。
次の画面への遷移はこう。
```dart
Get.toNamed("/NextScreen");
```
Get.off の名前付きRoute版。
```dart
Get.offNamed("/NextScreen");
```
Get.offAll の名前付きRoute版。
```dart
Get.offAllNamed("/NextScreen");
```
Routeを定義するにはGetMaterialAppを使ってください。
```dart
void main() {
runApp(
GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => MyHomePage()),
GetPage(name: '/second', page: () => Second()),
GetPage(
name: '/third',
page: () => Third(),
transition: Transition.zoom
),
],
)
);
}
```
未定義Route(404エラー)に遷移させるには、GetMaterialAppで unknownRoute を設定してください。
```dart
void main() {
runApp(
GetMaterialApp(
unknownRoute: GetPage(name: '/notfound', page: () => UnknownRoutePage()),
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => MyHomePage()),
GetPage(name: '/second', page: () => Second()),
],
)
);
}
```
### 名前付きRouteにデータを送る
次の画面に渡すデータは arguments で引数を設定します。Getでは引数にどんなものでも指定できます。StringでもMapでもListでも、クラスのインスタンスでも大丈夫です。
```dart
Get.toNamed("/NextScreen", arguments: 'Get is the best');
```
ビュー側のクラスやControllerで値を使うにはこうしてください。
```dart
print(Get.arguments);
// Get is the best が表示される
```
### 動的URLの生成
Getはウェブのような高度な動的URLを提供します。ウェブ開発者はFlutterにこの機能が提供されることを待ち望んでいたことでしょう。この機能の提供を謳うパッケージは存在しますが、ウェブ上のURLとは全く異なるシンタックスが表示されているのを見たことがあるかもしれません。Getはこの点も解決します。
```dart
Get.offAllNamed("/NextScreen?device=phone&id=354&name=Enzo");
```
ビュー側のクラスやControllerで値を使う方法。
```dart
print(Get.parameters['id']);
// 出力: 354
print(Get.parameters['name']);
// 出力: Enzo
```
この名前付きパラメーターはこのように簡単に受け取ることもできます。
```dart
void main() {
runApp(
GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(
name: '/',
page: () => MyHomePage(),
),
GetPage(
name: '/profile/',
page: () => MyProfile(),
),
// 引数userを使う場合と使わない場合で別ページを定義することが可能です。
// ただ、そのためにはスラッシュ '/' をベースのRoute名の後に入れる必要があります。
GetPage(
name: '/profile/:user',
page: () => UserProfile(),
),
GetPage(
name: '/third',
page: () => Third(),
transition: Transition.cupertino
),
],
)
);
}
```
Route名を使ってデータを送る方法。
```dart
Get.toNamed("/profile/34954");
```
次の画面でデータを受け取る方法。
```dart
print(Get.parameters['user']);
// out: 34954
```
複数のパラメーターを送信するにはこちら。
```dart
Get.toNamed("/profile/34954?flag=true&country=italy");
```
もしくは
```dart
var parameters = <String, String>{"flag": "true","country": "italy",};
Get.toNamed("/profile/34954", parameters: parameters);
```
次の画面でデータを受け取る方法。
```dart
print(Get.parameters['user']);
print(Get.parameters['flag']);
print(Get.parameters['country']);
// 出力: 34954 true italy
```
あとは Get.toNamed() を使い、名前付きRouteを指定するだけです(contextを使わないので BLoC や Controller から直接Routeを呼び出すことができます)。ウェブアプリとしてコンパイルされると、Routeが正しくURLに表示されます。
### ミドルウェアの使用
何かのアクションのトリガーとなるイベントを取得したい場合は、routingCallbackを使用してください。
```dart
GetMaterialApp(
routingCallback: (routing) {
if(routing.current == '/second'){
openAds();
}
}
)
```
GetMaterialAppを使用しない場合は、手動のAPIを使ってミドルウェアオブザーバーを設定してください。
```dart
void main() {
runApp(
MaterialApp(
onGenerateRoute: Router.generateRoute,
initialRoute: "/",
navigatorKey: Get.key,
navigatorObservers: [
GetObserver(MiddleWare.observer), // ここ
],
),
);
}
```
ミドルウェアクラスを作成する
```dart
class MiddleWare {
static observer(Routing routing) {
/// Routeの他に SnackBar / Dialog / BottomSheet のイベントも監視することができます。
/// また、ここで直接この3つのいずれかを表示したい場合は、
/// イベント自身が「それではない」ことを事前にチェックする必要があります。
if (routing.current == '/second' && !routing.isSnackbar) {
Get.snackbar("Hi", "You are on second route");
} else if (routing.current =='/third'){
print('last route called');
}
}
}
```
それではGetをコードで使ってみましょう。
```dart
class First extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.add),
onPressed: () {
Get.snackbar("hi", "i am a modern snackbar");
},
),
title: Text('First Route'),
),
body: Center(
child: ElevatedButton(
child: Text('Open route'),
onPressed: () {
Get.toNamed("/second");
},
),
),
);
}
}
class Second extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.add),
onPressed: () {
Get.snackbar("hi", "i am a modern snackbar");
},
),
title: Text('second Route'),
),
body: Center(
child: ElevatedButton(
child: Text('Open route'),
onPressed: () {
Get.toNamed("/third");
},
),
),
);
}
}
class Third extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Third Route"),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Get.back();
},
child: Text('Go back!'),
),
),
);
}
}
```
## contextを使わないナビゲーション
### SnackBar
FlutterでシンプルなSnackBarを表示したいとき、Scaffoldのcontextか、GlobalKeyを取得する必要があります。
```dart
final snackBar = SnackBar(
content: Text('Hi!'),
action: SnackBarAction(
label: 'I am a old and ugly snackbar :(',
onPressed: (){}
),
);
// WidgetツリーでScaffoldを探し、それをSnackBar表示に使用します。
Scaffold.of(context).showSnackBar(snackBar);
```
Getならこうなります。
```dart
Get.snackbar('Hi', 'i am a modern snackbar');
```
コードのどこにいようと、Get.snackbar を呼ぶだけでいいのです。カスタマイズも自由自在です。
```dart
Get.snackbar(
"Hey i'm a Get SnackBar!", // タイトル
"It's unbelievable! I'm using SnackBar without context, without boilerplate, without Scaffold, it is something truly amazing!", // 本文
icon: Icon(Icons.alarm),
shouldIconPulse: true,
onTap:(){},
barBlur: 20,
isDismissible: true,
duration: Duration(seconds: 3),
);
////////// すべてのプロパティ //////////
// Color colorText,
// Duration duration,
// SnackPosition snackPosition,
// Widget titleText,
// Widget messageText,
// bool instantInit,
// Widget icon,
// bool shouldIconPulse,
// double maxWidth,
// EdgeInsets margin,
// EdgeInsets padding,
// double borderRadius,
// Color borderColor,
// double borderWidth,
// Color backgroundColor,
// Color leftBarIndicatorColor,
// List<BoxShadow> boxShadows,
// Gradient backgroundGradient,
// TextButton mainButton,
// OnTap onTap,
// bool isDismissible,
// bool showProgressIndicator,
// AnimationController progressIndicatorController,
// Color progressIndicatorBackgroundColor,
// Animation<Color> progressIndicatorValueColor,
// SnackStyle snackStyle,
// Curve forwardAnimationCurve,
// Curve reverseAnimationCurve,
// Duration animationDuration,
// double barBlur,
// double overlayBlur,
// Color overlayColor,
// Form userInputForm
///////////////////////////////////
```
従来の SnackBar がお好みの場合や、ゼロからカスタマイズしたい場合 (たとえば Get.snackbar ではタイトルと本文が必須項目となっています)は `Get.rawSnackbar();` を使ってください。SnackBarの元々のAPIを取得できます。
### Dialog
ダイアログを表示する方法。
```dart
Get.dialog(YourDialogWidget());
```
デフォルトのダイアログを表示する方法。
```dart
Get.defaultDialog(
onConfirm: () => print("Ok"),
middleText: "Dialog made in 3 lines of code"
);
```
また showGeneralDialog の代わりに Get.generalDialog が使えます。
Overlayを使用するCupertino含むその他のFlutterのダイアログについては、contextの代わりに Get.overlayContext を使うことでコードのどこでもダイアログを表示することができます。
Overlayを使わないWidgetについては、Get.context が使えます。
これら2つのcontextはほとんどのケースでUIのcontextを代替することができるでしょう。ただし、ナビゲーションのcontextを使用せずInheritedWidgetが使われているケースは例外です。
### BottomSheet
Get.bottomSheet は showModalBottomSheet に似ていますが、contextが不要です。
```dart
Get.bottomSheet(
Container(
child: Wrap(
children: <Widget>[
ListTile(
leading: Icon(Icons.music_note),
title: Text('Music'),
onTap: () {}
),
ListTile(
leading: Icon(Icons.videocam),
title: Text('Video'),
onTap: () {},
),
],
),
)
);
```
## ネスト構造のナビゲーション
GetはFlutterのネスト構造のナビゲーションの扱いも簡単にしてくれます。
contextを必要とせず、IDによりナビゲーションのスタックを見つけることができます。
- 注: 並列のナビゲーションスタックを作成することは危険です。ネスト構造のNavigatorを使用しないか、使用を控えめにするのが理想です。必要なら使っていただいても問題ありませんが、複数のナビゲーションのスタックを保持することはRAM消費の面で好ましくないということは覚えておいてください。
こんなに簡単にできます。
```dart
Navigator(
key: Get.nestedKey(1), // インデックス指定でkey作成
initialRoute: '/',
onGenerateRoute: (settings) {
if (settings.name == '/') {
return GetPageRoute(
page: () => Scaffold(
appBar: AppBar(
title: Text("Main"),
),
body: Center(
child: TextButton(
color: Colors.blue,
onPressed: () {
Get.toNamed('/second', id:1); // インデックス指定でネスト型Routeに遷移
},
child: Text("Go to second"),
),
),
),
);
} else if (settings.name == '/second') {
return GetPageRoute(
page: () => Center(
child: Scaffold(
appBar: AppBar(
title: Text("Main"),
),
body: Center(
child: Text("second")
),
),
),
);
}
}
),
```
... ...
* [状態管理](#状態管理)
+ [リアクティブな状態管理](#リアクティブな状態管理)
- [利点](#利点)
- [パフォーマンスの最大化](#パフォーマンスの最大化)
- [リアクティブな変数の宣言](#リアクティブな変数の宣言)
- [初期値の設定](#初期値の設定)
- [Observableの値をビュー内で使う](#Observableの値をビュー内で使う)
- [更新条件を設定](#更新条件を設定)
- [.obsの使いどころ](#.obsの使いどころ)
- [List(Rx)に関する補足](#List(Rx)に関する補足)
- [なぜ「.value」を使う必要があるのか](#なぜ「.value」を使う必要があるのか)
- [Obx()](#obx)
- [Worker](#worker)
+ [非リアクティブな状態管理](#非リアクティブな状態管理)
- [利点](#利点)
- [使用例](#使用例)
- [Controllerインスタンスの扱い](#Controllerインスタンスの扱い)
- [StatefulWidgetsはもういらない](#StatefulWidgetsはもういらない)
- [Getの目的](#Getの目的)
- [Controllerの様々な使用方法](#Controllerの様々な使用方法)
- [ユニークIDの設定](#ユニークIDの設定)
+ [状態管理ソリューションを混在させる](#状態管理ソリューションを混在させる)
+ [StateMixin](#StateMixin)
+ [GetBuilder VS GetX VS Obx VS MixinBuilder](#GetBuilder-VS-GetX-VS-Obx-VS-MixinBuilder)
# 状態管理
GetXは他の状態管理ライブラリのように Stream や ChangeNotifier を使用する必要がありません。なぜか?私たちは応答時間とRAM消費量を改善するために GetValueとGetStream という低遅延のソリューションを開発しましたが、状態管理機能を含むGetXのリソースはすべてこれをベースに作られているためです。このソリューションはより低い運用コストと高いパフォーマンスを実現します。GetXを使えばAndroid、iOS、Web、Linux、macOS用のアプリケーションを作成するだけでなく、Flutter/GetXと同じシンタックスでサーバーアプリケーションを作ることができます。
* _バカみたいにシンプル_: 他の状態管理アプローチの中には、複雑で多くのボイラープレートコードを書かなければいけないものもあります。この問題により少なくない人たちがFlutterを見限りましたが、今ようやく、バカみたいにシンプルなソリューションを手に入れることができました。GetXを使えば、非常にすっきりとした、記述量の少ないコードでより多くのことができるようになります。イベントごとにクラスを定義する必要もありません。
* _コード生成にサヨナラ_: 開発時間の半分はアプリケーションロジックの作成に費やします。それにも関わらず、状態管理ライブラリの中には、ミニマルなコードを作るためにコード生成ツールに依存しているものがあります。変数を変更して build_runner を実行するのは非生産的ですし、flutter clean 後の待ち時間もコーヒーをたくさん飲まなければならないほど長くなることもあります。
GetXはすべてがリアクティブであり、コード生成ツールに依存しないため、開発のあらゆる面において生産性が向上します。
* _context依存にサヨナラ_: ビューとビジネスロジックを連携させるため、ビューのcontextをControllerに送る必要に迫られた。contextがないところで依存オブジェクトの注入をする必要があり、contextを方々のクラスや関数からなんとか渡した。これらの経験、誰もが通ってきた道かと思います。しかし、GetXではこのような経験をすることはありません。contextなしで、Controllerの中から別のControllerにアクセスすることができます。パラメーターを通じて無駄にcontextを送る必要はもうありません。
* _細かいコントロール_: 多くの状態管理ソリューションは、ChangeNotifierをベースにしています。ChangeNotifierは、notifyListenerが呼ばれたときに、依存するすべてのWidgetに通知します。画面に40個のWidgetがあるとしましょう。それらがすべてChangeNotifierの変数に依存している場合、変数を1つでも更新すれば、すべてのWidgetが更新されます。
GetXを使えばネストされたWidgetさえも的確にビルドを処理することができます。ListViewを担当するObxと、ListViewの中のチェックボックスを担当するObxがあれば、チェックボックスの値を変更した場合はチェックボックスWidgetだけが更新され、Listの値を変更した場合はListViewだけが更新されます。
* _変数が本当に変わったときだけ更新する_: GetXはデータの流れをコントロールします。つまり、Textに紐づいたObservable(監視可能)変数の値 'Paola' を、同じ 'Paola' に変更してもWidgetは更新されません。これは、GetXがTextに'Paola'がすでに表示されていることをわかっているためです。
多くの状態管理ソリューションは、この場合更新を行います。
## リアクティブな状態管理
リアクティブプログラミングは複雑であると言われがちなためか、多くの人に敬遠されています。しかし、GetXはリアクティブプログラミングを非常にシンプルなものにしてくれます。
* StreamControllerを作る必要はありません。
* 変数ごとにStreamBuilderをセットする必要はありません。
* 状態ごとにクラスを作る必要はありません。
* 初期値のためにgetを準備する必要はありません。
Getによるリアクティブプログラミングは、setState並に簡単です。
たとえば name という変数があり、それを変更するたびに変数に依存するすべてのWidgetを自動更新したいとします。
これがその name 変数です。
``` dart
var name = 'Jonatas Borges';
```
これをObservable(監視可能)にするには、値の末尾に ".obs" を付け足すだけです。
``` dart
var name = 'Jonatas Borges'.obs;
```
これで終わりです。*こんなに* 簡単なんですよ。
(以後、このリアクティブな ".obs" 変数、Observable(監視可能)を _Rx_ と呼ぶことがあります。)
内部ではこのような処理を行っています: `String`の`Stream`を作成し、初期値`"Jonatas Borges"`を割り当て、`"Jonatas Borges"`に依存するすべてのWidgetに、あなたは今この変数の影響下にあるから、_Rx_の値が変更されたときには、あなたも同様に変更する必要がある旨を通知。
これがDartの機能のおかげで実現できた **GetX マジック** です。
しかし皆さんご存知の通り、`Widget` は関数の中にいなければ自らを更新できません。静的クラスには「自動更新」の機能がないからです。
それなら、同じスコープ内で複数の変数に依存してWidgetをビルドする場合は、複数の `StreamBuilder` をネストして変数の変化を監視する必要がありますね。
いいえ、**GetX** なら `StreamBuilder` すら不要です。
またWidgetを更新する際のボイラープレートコードについても、**GetX**では忘れてください。
`StreamBuilder( ... )` ? `initialValue: ...` ? `builder: ...` ? これらはすべて不要で、対象のWidgetを `Obx()` の中に入れるだけです。
``` dart
Obx (() => Text (controller.name));
```
_覚えること?_ それは `Obx(() =>` だけです。
そのWidgetをアロー関数を通じて `Obx()`(_Rx_のObserver(監視者))に渡すだけです。
`Obx` は非常に賢く、`controller.name` の値が本当に変わったときにのみ、Widgetの更新をかけます。
`name` が `"John"` だとして、それを `"John"` ( `name.value = "John"` ) に変更しても、以前と同じ `value` のため画面上では何も変化しません。`Obx` はリソースを節約するために値を無視し、Widgetを更新しません。**すごいでしょ?**
> では、もしも `Obx` の中に_Rx_(Observable)変数が5つあったらどうでしょう?
5つの**いずれかに**値の変化があればWidgetは更新されます。
> また、1つのControllerクラスに30もの変数がある場合、1つの変数を更新したら変数に関わるWidgetが**すべて**更新されてしまうのでしょうか?
いいえ、_Rx_ 変数を使う特定のWidgetだけが更新されます。
言い換えるなら、**GetX**は _Rx_ 変数の値が変化したときだけ画面更新をしてくれるということです。
```dart
final isOpen = false.obs;
// 同じ値なので何も起きません。
void onButtonTap() => isOpen.value=false;
```
### 利点
**GetX()**は何を更新して何をしないのか、の**細かい**コントロールが可能です。
すべての更新するのでそのようなコントロールが不要な場合は、`GetBuilder` を検討してください。
これはわずか数行のコードで作られた、状態更新のためのシンプルなビルダーです。(`setState()`のようにブロックで)
CPUへの影響を最小限にするために作られており、単一の目的(_状態_ の再構築)を果たすため、可能な限りリソース消費を抑えました。
**強力な** 状態管理のソリューションを求めているなら、**GetX**で間違いはありません。
変数をそのまま扱うことはできませんが、内部では `Stream` としてデータが扱われています。
すべてが `Stream` なので、_RxDart_ を組み合わせることも可能ですし、
"_Rx_ 変数" のイベントや状態を監視することも可能です。
GetXは _MobX_ より簡単で、コード自動生成や記述量を減らした_BLoC_ 型アプローチと言えるかもしれません。
値の末尾に `.obs` を付けるだけで**なんでも** _"Observable(監視可能)"_ にできるのです。
### パフォーマンスの最大化
ビルドを最小限に抑えるための賢いアルゴリズムに加えて、
**GetX**はコンパレーターを使用して状態が変更されたことを確認します。
アプリでなにかしらのエラーが発生し、状態が変更された情報を
二重に送信してしまったとしても**GetX**はクラッシュを防いでくれます。
**GetX**では値が変化したときにはじめて「状態」が変化するためです。
これが **GetX** と _MobX の `computed`_ を使う際の主な違いです。
2つの __Observable__ を組み合わせて一つが変化したとき、それを監視しているオブジェクトも変化します。
これは `GetX()` (`Observer()`のようなもの) において2つの変数を組み合わせた場合においても、
それが本当に状態の変化を意味するときだけWidgetの更新が行われるということでもあります。
### リアクティブな変数の宣言
変数を "Observable" にする方法は3つあります。
1 - **`Rx{Type}`** を使用する
``` dart
// 初期値を入れることを推奨しますが、必須ではありません
final name = RxString('');
final isLogged = RxBool(false);
final count = RxInt(0);
final balance = RxDouble(0.0);
final items = RxList<String>([]);
final myMap = RxMap<String, int>({});
```
2 - **`Rx`** とジェネリクスによる型指定の組み合わせ
``` dart
final name = Rx<String>('');
final isLogged = Rx<Bool>(false);
final count = Rx<Int>(0);
final balance = Rx<Double>(0.0);
final number = Rx<Num>(0);
final items = Rx<List<String>>([]);
final myMap = Rx<Map<String, int>>({});
// 任意の型を指定可能 - どんなクラスでもOK
final user = Rx<User>();
```
3 - 最も実用的で簡単な方法として、**`.obs`** を値に付ける
``` dart
final name = ''.obs;
final isLogged = false.obs;
final count = 0.obs;
final balance = 0.0.obs;
final number = 0.obs;
final items = <String>[].obs;
final myMap = <String, int>{}.obs;
// カスタムクラスのインスタンスにも付けられます
final user = User().obs;
```
##### 初期値の設定
ご存知の通り、_Dart_ は現在 _null safety_ へ移行しているところです。
それに備えるために今後は _Rx_ 変数は常に**初期値**を設定してください。
> **GetX** で変数を _Observable_ にしつつ _初期値_ を設定するのはとても簡単です。
変数の末尾に `.obs` を付ける。**それだけ。**
めでたく Observable とそのプロパティ `.value` (つまり _初期値_)ができました。
### Observableの値をビュー内で使う
``` dart
// Controllerクラス
final count1 = 0.obs;
final count2 = 0.obs;
int get sum => count1.value + count2.value;
```
``` dart
// ビュークラス
GetX<Controller>(
builder: (controller) {
print("count 1 rebuild");
return Text('${controller.count1.value}');
},
),
GetX<Controller>(
builder: (controller) {
print("count 2 rebuild");
return Text('${controller.count2.value}');
},
),
GetX<Controller>(
builder: (controller) {
print("count 3 rebuild");
return Text('${controller.sum}');
},
),
```
`count1.value++` を実行すると、以下の通りprintされます。
* `count 1 rebuild`
* `count 3 rebuild`
なぜなら `count1` の値が `1` に変わり、それに伴ってgetter `sum` の値にも `1 + 0 = 1` と変化が起こるからです。
今度は `count2.value++` を実行してみましょう。
* `count 2 rebuild`
* `count 3 rebuild`
もうおわかりですね。これは `count2.value` が変わり、その結果 `sum` が `2` になったからです。
* 注: デフォルト仕様では、`value` に変化がなかったとしても、それが最初のイベントであればWidgetを更新します。
この仕様はbool変数の性質から来るものです。
たとえばこの場合を想像してみてください。
``` dart
var isLogged = false.obs;
```
そして、isLogged(ユーザーがログインしたかどうか)の変化をトリガーにever関数内のコールバックfireRouteを呼び出したいとします。
``` dart
@override
onInit() async {
// everは引数1が変化するたびに引数2を実行するリスナー
ever(isLogged, fireRoute);
isLogged.value = await Preferences.hasToken();
}
fireRoute(logged) {
if (logged) {
Get.off(Home());
} else {
Get.off(Login());
}
}
```
もし `hasToken` が `false` なら `isLogged` に変化はありません。すると `ever()` のコールバックは永遠に呼び出されないことになります。
このような挙動を防ぐために _Observable_ への最初の更新は、それがたとえ同じ `.value` だったとしても
常にイベントを引き起こすようにしています。
ご参考までに、この仕様は以下の設定で解除することができます。
`isLogged.firstRebuild = false;`
### 更新条件を設定
Getにはさらに洗練された「状態」のコントロール方法があります。イベント(Listへのオブジェクト追加など)に対して条件を付けることが可能です。
``` dart
// 引数1: Listにオブジェクトを加える条件。trueかfalseを返すこと
// 引数2: 条件がtrueの場合に加える新しいオブジェクト
list.addIf(item < limit, item);
```
最低限のコードで、コード生成ツールも使わず、とても簡単ですね :smile:
カウンターアプリもこのようにシンプルに実現できます。
``` dart
class CountController extends GetxController {
final count = 0.obs;
}
```
Controllerを設定して、下記を実行するだけ。
``` dart
controller.count.value++
```
UIの数字が置き換わりましたね。このようにアプリのどこであっても更新をかけることができます。
### .obsの使いどころ
.obs を使うことでどんなものもObservableにすることができます。方法は2つ。
* クラスのインスタンス変数をobsに変換する
``` dart
class RxUser {
final name = "Camila".obs;
final age = 18.obs;
}
```
* クラスのインスタンスを丸々obsに変換する
``` dart
class User {
User({String name, int age});
var name;
var age;
}
// インスタンス化の際
final user = User(name: "Camila", age: 18).obs;
```
### List(Rx)に関する補足
List(Rx)はその中のオブジェクトと同様、監視可能(Observable)です。そのためオブジェクトを追加すると、List(Rx)に依存するWidgetは自動更新されます。
またList(Rx)をListとするために ".value" を使う必要はありません。DartのAPIがこれを可能にしてくれました。ただ、残念ながら他のStringやintのようなプリミティブ型は拡張ができないため、.value を使う必要があります。getterやsetterを活用するのであればあまり問題になりませんが。
``` dart
// Controllerクラス
final String title = 'User Info:'.obs
final list = List<User>().obs;
// ビュークラス
Text(controller.title.value), // Stringの場合は .value が必要
ListView.builder (
itemCount: controller.list.length // Listの場合は不要
)
```
カスタムのクラスをObservableにした場合は、様々な方法で値を更新することができます。
``` dart
// モデルクラス
// 属性をobsにするやり方ではなく、クラス全体をobsにする方法を採ります
class User() {
User({this.name = '', this.age = 0});
String name;
int age;
}
// Controllerクラス
final user = User().obs;
// user変数を更新するときはこのようなメソッドを作ります
user.update( (user) { // このパラメーターは更新するオブジェクトそのもの
user.name = 'Jonny';
user.age = 18;
});
// あるいは、この方法でも。変数名は呼び出し可能です。
user(User(name: 'João', age: 35));
// ビュークラス
Obx(()=> Text("Name ${user.value.name}: Age: ${user.value.age}"))
// .value を使わずにモデルのプロパティにアクセスすることも可能です
user().name; // userがUserではないことに注目。user()でUserを受け取れます。
```
ListのsetAllやsetRangeメソッドの代わりに、"assign" "assignAll" APIを使っていただくことも可能です。
"assign" APIはListの内容をクリアした後に、指定した単独のオブジェクトを追加してくれます。
"assignAll" APIはそのIterable版です。
### なぜ「.value」を使う必要があるのか
ちょっとしたアノテーションとコード生成ツールを使って`String`や`int`で `.value` を使わなくて済むようにもすることはできますが、このライブラリの目的は「外部依存パッケージを減らす」ことです。私たちは、外部パッケージを必要としない、必須ツール(Route、依存オブジェクト、状態の管理)が揃った開発環境を軽量かつシンプルな方法で提供したいと考えています。
まさに pubspecに3文字(get)とコロンを加えて、プログラミングを始めることができるのです。Route管理から状態管理まで、必要なソリューションが標準装備されています。GetXはシンプルさ、生産性、高いパフォーマンスを目指します。
これほど多機能であるにも関わらず、このライブラリの総容量は他の多くの状態管理ライブラリよりも少ないです。その点をご理解いただけるとうれしいです。
`.value` が嫌でコード生成ツールを使いたいという方には、MobXは素晴らしいライブラリだと思いますし、Getと併用することもできます。逆に多くの外部パッケージに依存したくない方、パッケージ間の互換性を気にしたくない方、状態管理ツールや依存オブジェクトから状態更新エラーが出ているかどうかを気にせずプログラミングをしたい方、依存するControllerクラスのインスタンスがあるかどうかを都度都度心配したくない方にとってはGetはまさに最適です。
MobXのコード生成や、BLoCのボイラープレートコードが気にならないのであれば、Route管理にだけでもGetをお使いいただけるとうれしいです。GetのSEMとRSMは必要に迫られて生まれたものです。私の会社で以前、90以上のControllerを持つプロジェクトがあり、それなりの性能のマシンでflutter cleanを行った後でさえ、コード生成ツールがタスクを完了するのに30分以上かかりました。もしあなたが大きなプロジェクトに関わっており、コード生成ツールが問題になっているのであれば、Getを検討してみてください。
もちろん、コード生成ツールをGetXに導入したい方が実際にツールを作成してプロジェクトに貢献した場合は、このReadMeに代替ソリューションとして掲載させていただきます。私はすべての開発者のニーズをかなえたいわけではありませんが、今はこの質問に対しては、「すでにMobXのように同様のことを実現してくれる良いソリューションがある」とだけ言わせてください。
### Obx()
GetX()の代わりにObx()を使用することもできます。ObxはWidgetを生成する匿名関数をパラメーターに持ちます。複数のControllerに対応することができますが、自身はControllerのインスタンスを持たず、型指定もできません。そのため別途Controllerのインスタンスを作るか、`Get.find<Controller>()` でインスタンスを探しておく必要があります。
### Worker
Worker はイベント発生に伴って指定したコールバックを呼び出すことができます。
``` dart
/// `count1` が更新されるたびに第2引数のコールバックが実行される
ever(count1, (_) => print("$_ has been changed"));
/// `count1` の最初の更新時のみ実行される
once(count1, (_) => print("$_ was changed once"));
/// DDoS攻撃対策に最適。たとえば、ユーザーが打鍵やクリックを止めて1秒後に実行など
debounce(count1, (_) => print("debouce$_"), time: Duration(seconds: 1));
/// 1秒以内の連続更新はすべて無視して実行しない
interval(count1, (_) => print("interval $_"), time: Duration(seconds: 1));
```
すべてのWorker(`debounce` 以外) は `condition` パラメーターを持ちます。`condition` は `bool` でも `bool` を返すコールバックでも構いません。
この `condition` が Worker のコールバックを実行するかどうかを決めています。
また Worker は `Worker` インスタンスを返します。これは `dispose()` などを通じて Worker を破棄するときに使用します。
* **`ever`**
は _Rx_ 変数が新しい値になるたびに呼ばれます。
* **`everAll`**
`ever` とほぼ同じですが、_Rx_ 変数の `List` を受け取ります。いずれかの値が更新されれば、その更新後の値を受け取ってコールバックが実行されます。
* **`once`**
変数が最初に更新されたときのみに呼ばれます。
* **`debounce`**
'debounce' は検索機能などで導入するととても有益です。たとえば、ユーザーがタイピングを止めたときにのみAPIを呼び出したいときに使います。ユーザーが "Jonny" と入れたときに 5回も APIに問い合わせを行うのは避けたいですよね。Getなら "debounce" があるので大丈夫です。
* **`interval`**
`interval` は debounce とは異なります。ユーザーが1秒間に1000回変数に変更を加えた場合、debounceが一定期間経過後(デフォルトは800ミリ秒)に最後の変更イベントだけ送信するのに対して、intervalは代わりに一定期間の間のユーザーによるアクションをすべて無視します。intervalは1秒ごとにイベントを送信しており、3秒に設定した場合は1分間に20個のイベントを送信します。これはユーザーがキーやマウスを連打することで何かしらの報酬を得られる場合に、その悪用を避けるために使用できます(ユーザーが何かをクリックしてコインを獲得できるとします。たとえ何秒かかったとしても300回クリックすれば300枚のコインを得ることができてしまいます。intervalを使用してインターバルを3秒に設定した場合は、何回クリックしようが1分間で得られるコインの上限は20枚になります)。一方のdebounceはアンチDDosや、検索のように変更を加えるたびにonChangeからAPI問い合わせが発生するような機能に適しています。ユーザーが入力し終わるのを待ってリクエストを送信するのです。debounceを前述のコイン獲得のケースで使用した場合、ユーザーはコインを1枚しか獲得できません。これは指定した期間、ユーザーが動作を「一時停止」したときにのみ実行されるからです。
* 注: Workerを使用する場合は、Controllerなどを起動するときに次のいずれかの方法で登録する必要があります。onInit(推奨)内、クラスのコンストラクタ、またはStatefulWidgetのinitState内(この方法は推奨しませんが、副作用はないはずです)。
## 非リアクティブな状態管理
GetはChangeNotifierを使わない軽量かつシンプルな状態管理機能を有しています。特にFlutterに慣れていない方のニーズを満たし、大規模なアプリケーションでも問題を起こすことがないと信じています。
GetBuilderは複数の状態を扱う場面で使われることを想定して作られました。たとえばショッピングカートに30個の商品があるとします。そしてあなたが商品を一つ削除すると同時に、カートのリストが更新され、合計金額が更新され、アイテム数を示すバッジが更新されます。GetBuilderはこのようなユースケースに最適です。というのも、GetBuilderは状態をControllerで束ねてそのControllerに依存するすべてのWidgetを一度に更新させることができるからです。
それらとは独立したControllerが必要な場合は、GetBuilderのidプロパティに専用IDを割り当てるか、GetXを使ってください。ケースバイケースですが、そのような「独立した」Widetが多いほど GetX() のパフォーマンスが際立ち、複数の状態変化がありそれに伴うWidgetの更新が多いほど GetBuilder() のパフォーマンスが勝ることを覚えておいてください。
### 利点
1. 必要なWidgetのみ更新される。
2. ChangeNotifierを使わず、メモリ使用量が少ない。
3. StatefulWidgetのことはもう忘れましょう。Getでは必要ありません。他の状態管理ライブラリではStatefulWidgetを使用することがあるでしょう。しかしAppBarやScaffoldなどクラス内のほとんどのWidgetがStatelessであるにも関わらず、StatefulなWidgetの状態だけを保持する代わりに、クラス全体の状態を保持しているのはなぜでしょうか?Getならクラス全体をStatelessにすることができます。更新が必要なコンポーネントは GetBuilder などで囲むことで「状態」が保持されます。
4. プロジェクトを整理しましょう!ControllerはUIの中にあってはいけません。TextEditControllerなどの類はすべてControllerクラスに配置してしまいましょう。
5. Widgetのレンダリングが開始されると同時にイベントを実行してWidgetを更新させる必要がありますか?GetBuilderにはStatefulWidgetと同様の「initState」プロパティがあり、そこからControllerのイベントを直接呼び出すことができます。initStateを使用する必要はもうありません。
6. StreamやTimerのインスタンスを破棄したい場合はGetBuilderのdisposeプロパティを利用してください。Widgetが破棄されると同時にイベントを呼び出すことができます。
7. GetXとStreamController / StreamBuilderを組み合わせるなどしてStreamを普通に使っていただいても問題ありませんが、必要なときに限って使うことをおすすめします。Streamのメモリ消費は適度であり、リアクティブプログラミングは美しいですが、たとえば30ものStreamを同時に立ち上げることを考えてみてください。これはChangeNotifierを使うよりもよくないことのように思います。
8. 必要以上にRAMを使わずWidgetを更新します。GetはGetBuilderのクリエーターIDのみを保存し、必要に応じてGetBuilderを更新します。何千ものGetBuilderを作成したとしても、ID保存のためのメモリ消費量は非常に少ないです。GetBuilderを新規に作成するということは、実際にはクリエーターIDを持つ GetBuilder の状態を共有しているに過ぎないためです。GetBuilderごとに状態が新たに作成されるわけではないため、特に大規模なアプリケーションでは多くのRAMを節約できます。基本的にGetXで作成するアプリケーションは全体的にStatelessであり、いくつかのStatefulなWidget(GetBuilder内のWidget)は単一の状態を持っているため、一つを更新すればすべてが更新されます。
9. Getはアプリ全体の流れをよく把握しており、Controllerをメモリから破棄するタイミングを正確に知っています。実際の破棄はGetがやってくれるため、開発者が心配する必要はありません。
### 使用例
``` dart
// Controllerクラスを作成してGetxControllerを継承しましょう
class Controller extends GetxController {
int counter = 0;
void increment() {
counter++;
update();
// increment 実行時にcounter変数に依存するUIを更新。
// GetBuilderを使うWidgetの場合はupdate()が必要。
}
}
// ビュー側のクラスでGetBuilderを使ってcounter変数を組み込む
GetBuilder<Controller>(
init: Controller(), // 最初に使用するときのみ初期化
builder: (_) => Text(
'${_.counter}',
),
)
// Controllerの初期化は最初の1回だけ行ってください。同じControllerを再度 GetBuilder / GetX で使用する場合は初期化する必要はありません。コントローラは、それを「init」とマークしたウィジェットがデプロイされると同時に、自動的にメモリから削除されます。Getがすべて自動で行ってくれるので、何も心配することはありません。同じControllerを2つ立ち上げることがないよう、それだけご注意ください。
```
**最後に**
* 以上、Getを使った状態管理の手法をご説明させていただきました。
* 注: もっと柔軟に管理する手法として、initプロパティを使わない方法もあります。Bindingsを継承したクラスを作成し、dependenciesメソッドをoverrideしてその中でGet.put()でControllerを注入してください(複数可)。このクラスとUI側のクラスを紐づけることでControllerをそのRoute内で使用できます。そしてそのControllerを初めて使用するとき、Getはdependencies内を見て初期化を実行してくれます。このlazy(遅延、消極的)ロードを維持しつつ、不要になったControllerは破棄し続けます。具体的な仕組みについてはpub.devの例をご参照ください。
Routeを移動して以前使用したControllerのデータが必要になった場合は、再度GetBuilderを使用してください。initする必要はありません。
``` dart
class OtherClass extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: GetBuilder<Controller>(
builder: (s) => Text('${s.counter}'),
),
),
);
}
```
GetBuilderの外でControllerを使用する場合は、Controller内にgetterを作成しておくと便利です。Controller.to で呼び出しましょう。(もしくは `Get.find<Controller>()` を使うのもありです)
``` dart
class Controller extends GetxController {
/// 記述量を省くためにstaticメソッドにすることをおすすめします。
/// staticメソッド使う場合 → Controller.to.increment();
/// 使わない場合 → Get.find<Controller>().increment();
/// どちらを使ってもパフォーマンスに影響があったり副作用が出たりはしません。前者は型の指定が不要という違いがあるだけです
static Controller get to => Get.find(); // これを追加
int counter = 0;
void increment() {
counter++;
update();
}
}
```
これで以下のようにControllerに直接アクセスできます。
``` dart
FloatingActionButton(
onPressed: () {
Controller.to.increment(),
} // とっても簡単ですね!
child: Text("${Controller.to.counter}"),
),
```
FloatingActionButton を押すと counter変数 に依存するWidgetがすべて自動的に更新されます。
### Controllerインスタンスの扱い
次のような画面の流れがあるとします。
`画面A => 画面B (Controller X を使用) => 画面C (Controller X を使用)`
画面Aの段階ではまだ未使用なので、Controllerはメモリにありません(Getは基本lazyロードなので)。画面Bに遷移すると、Controllerがメモリ内に保存されます。画面Cでは画面Bと同じControllerを使用しているため、GetはBとCでControllerの状態を共有し、同じControllerがメモリ内に引き続きいることになります。画面Cを閉じてさらに画面Bを閉じ、画面Aに戻ったとしましょう。そこではControllerが使われていないため、GetはControllerをメモリから出してリソースを解放します。そこで再度画面Bに遷移すると、Controllerは再度メモリに保存されます。そして今度は画面Cに行かずに画面Aに戻ります。Getは同様にControllerをメモリから破棄してくれます。また、仮に画面CがControllerを使っておらず画面Cにいたとして、画面BをRouteスタックから削除したとしましょう。するとControllerを使用している画面(クラス)はなくなりますので、同様にメモリから破棄されます。Getが正常動作しないと考えられる唯一の例外は、画面Cにいるときに画面Bを誤ってRouteスタックから削除してしまい、Controllerの使用を試みたときです。この場合は、画面Bで作成されたControllerのクリエーターIDが削除されてしまったことが原因です(GetはクリエーターIDのないControllerはメモリから破棄するようプログラムされています)。もし意図があってこの事例に対応したい場合は、画面BのGetBuilderに "autoRemove: false" フラグを追加した上で、CクラスのGetBuilderに "adoptID: true" を追加してください。
### StatefulWidgetsはもういらない
StatefulWidgetsを使用すると、画面全体の状態を不必要に保存することになります。ウィジェットを最小限に再構築する必要がある場合は、Consumer/Observer/BlocProvider/GetBuilder/GetX/Obxの中に埋め込むことになりますが、それは別のStatefulWidgetになります。
StatefulWidgetはStatelessWidgetよりも多くのRAMが割り当てられます。これは1つや2つのStatefulWidgetでは大きな違いは産まないかもしれませんが、それが100もあった場合は確実に違いが出ます。
TickerProviderStateMixinのようなMixinを使用する必要がない限り、GetでStatefulWidgetを使用する必要はありません。
たとえばinitState()やdispose()メソッドなど、StatefulWidgetのメソッドをGetBuilderから直接呼び出すことも可能です。
``` dart
GetBuilder<Controller>(
initState: (_) => Controller.to.fetchApi(),
dispose: (_) => Controller.to.closeStreams(),
builder: (s) => Text('${s.username}'),
),
```
しかし、これよりもベターなアプローチはControllerの中で直接 onInit() や onClose() メソッドを呼び出すことです。
``` dart
@override
void onInit() {
fetchApi();
super.onInit();
}
```
* 注: コンストラクタを通じてControllerを立ち上げる必要はありません。このようなプラクティスは、パフォーマンス重視であるGetのControllerの作成や割り当ての原理、考え方から外れてしまいます(コンストラクタ経由でインスタンスを作成すれば、実際に使用される前の段階でControllerを生成し、メモリを割り当てることになります)。onInit() と onClose() メソッドはこのために作られたもので、Controllerのインスタンスが作成されたとき、または初めて使用されたときに呼び出されます(Get.lazyPutを使用しているか否か次第)。たとえば、データを取得するためにAPIを呼び出したい場合は initState/dispose の代わりに onInit() を使用し、Streamを閉じるなどのコマンドを実行する必要がある場合は onClose() を使用してください。
### Getの目的
このパッケージの目的は、Routeのナビゲーション、依存オブジェクトと状態の管理のための完全なソリューションを、開発者が外部パッケージに極力依存せずに済むような形で提供し、高度なコード分離性(デカップリング)を実現することです。それを確実なものとするため、Getはあらゆる高レベルおよび低レベルのFlutter APIを取り込んでいます。これによりビューとロジックを切り分けることが容易になり、UIチームにはWidgetの構築に集中してもらい、ビジネスロジック担当チームにはロジックに集中してもらうことができます。Getを使うことでよりクリーンな作業環境を構築することができるのです。
要するに、initState内でメソッドを呼び出してパラメーターを通じてControllerにデータを送信する必要も、そのためにControllerのコンストラクタを使用する必要もありません。Getには必要なタイミングでサービスを呼び出してくれう onInit() メソッドがあります。
Controllerが不要になれば、onClose() メソッドがジャストなタイミングでメモリから破棄してくれます。これにより、ビューとビジネスロジックを分離することができるのです。
GetxController 内に dispose() メソッドがあっても何も起こらないので記述しないでください。ControllerはWidgetではないので「dispose」できません。たとえばController内でStreamを使用していてそれを閉じたい場合は、以下のように onClose() メソッドにコードを記述してください。
``` dart
class Controller extends GetxController {
StreamController<User> user = StreamController<User>();
StreamController<String> name = StreamController<String>();
/// Streamを閉じる場合は dispose() ではなく onClose()
@override
void onClose() {
user.close();
name.close();
super.onClose();
}
}
```
Controllerのライフサイクルについて。
* onInit() はControllerが作成されたタイミングで実行されます。
* onClose() は onDelete() メソッドが実行される直前のタイミングで実行されます。
* Controllerが削除されるとそのAPIにアクセスすることはできません。文字通りメモリからの削除だからです。削除のトレースログも残りません。
### Controllerの様々な使用方法
ControllerインスタンスはGetBuilderのvalueを通じて使用することができます。
``` dart
GetBuilder<Controller>(
init: Controller(),
builder: (value) => Text(
'${value.counter}', // ここ
),
),
```
GetBuilderの外でControllerインスタンスを使う場合は、このアプローチをおすすめします。
``` dart
class Controller extends GetxController {
static Controller get to => Get.find();
[...]
}
// ビュー側で
GetBuilder<Controller>(
init: Controller(), // 最初に使うときだけ必要
builder: (_) => Text(
'${Controller.to.counter}', // ここ
)
),
```
もしくは
``` dart
class Controller extends GetxController {
// static get を省き、
[...]
}
// ビュー側で
GetBuilder<Controller>(
init: Controller(), // 最初に使うときだけ必要
builder: (_) => Text(
'${Get.find<Controller>().counter}', // ここ
),
),
```
* get_it や modular など他の依存オブジェクト管理ライブラリを使用しているため、単にControllerのインスタンスを渡したいだけの場合は、このような「非正規」な方法もあります。
``` dart
Controller controller = Controller();
[...]
GetBuilder<Controller>(
init: controller, // ここ
builder: (_) => Text(
'${controller.counter}', // ここ
),
),
```
### ユニークIDの設定
GetBuilderを使ってWidgetの更新をコントロールしたい場合は、このようにユニークIDを振ってください。
``` dart
GetBuilder<Controller>(
id: 'text'
init: Controller(), // 最初に使うときだけ必要
builder: (_) => Text(
'${Get.find<Controller>().counter}', // ここ
),
),
```
そして以下のようにWidgetを更新します。
``` dart
update(['text']);
```
さらに更新に条件を設けることができます。
``` dart
update(['text'], counter < 10);
```
GetXはこの更新を自動で行ってくれます。指定したIDを持ち、その変数に依存するWidgetのみを更新します。また変数を前の値と同じ値に変更しても、それが状態の変化を意味しない場合はメモリとCPUサイクルを節約するためにWidgetを更新しません (画面に 3 が表示されているときに、変数を再び 3 に変更したとします。このような場合にWidgetを更新する状態管理ソリューションも存在しますが、GetXでは実際に状態が変更された場合にのみ更新されます)。
## 状態管理ソリューションを混在させる
MixinBuilderはObxとGetBuilderを併用したいというリクエストから発想して作られました。これは ".obs" 変数の変更によるリアクティブな更新と、update() メソッドによるメカニカルな更新の両方を混在可能にします。ただし、GetBuiler / GetX / Obx / MixinBuilder の4つの中で最もリソースを消費するWidgetです。というのも、Widgetからのイベントを検知するためのSubscriptionに加えて、Controller自身のupdateメソッドも購読する必要があるからです。
MixinBuilderに使用するControllerクラスには、変数を `.obs`(Observable)とするかどうかに関わらず、GetxControllerを継承したものを使用してください。GetxControllerにはライフサイクルがあり、onInit() および onClose() メソッドでイベントを「開始」したり「終了」したりすることができます。
## StateMixin
`StateMixin<T>` を使うことでさらに `UI` の「状態」を便利に扱うことができます。
`with` を使って `StateMixin<T>` をControllerにミックスインしてください。Tにはモデルのクラス名が入ります。
``` dart
class Controller extends GetController with StateMixin<User>{}
```
状態を変更するには `change()` メソッドを使ってください。
パラメーターにはビューに渡すデータと「状態」をセットします。
```dart
change(data, status: RxStatus.success());
```
RxStatus の「状態」は以下の4つです。
``` dart
RxStatus.loading(); // ロード中
RxStatus.success(); // ロード成功
RxStatus.empty(); // データが空
RxStatus.error('message'); // エラー
```
それぞれの「状態」をUIで表すには以下のようにします。
```dart
class OtherClass extends GetView<Controller> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: controller.obx(
(state)=>Text(state.name),
// ここはカスタムのロードインジケーターでも可能ですが、
// デフォルトは Center(child:CircularProgressIndicator())
onLoading: CustomLoadingIndicator(),
onEmpty: Text('No data found'),
// ここもカスタムのエラーWidgetでも構いませんが、
// デフォルトは Center(child:Text(error))
onError: (error)=>Text(error),
),
);
}
```
## GetBuilder VS GetX VS Obx VS MixinBuilder
私は10年間プログラミングに携わってきて、いくつか貴重な教訓を得ることができました。
リアクティブプログラミングに初めて触れたとき、「おお、これはすごい」と感嘆せずにはいられませんでしたし、実際にすごいものでした。
しかし、リアクティブプログラミングはすべての状況に適しているわけではありません。多くの場合、必要なのは2,3のWidgetの状態を同時に更新すること、またはローカルでの一時的な状態の変更であり、これらの場合はリアクティブである必要はありません。
リアクティブプログラミングのRAM消費量の多さは、必要なときだけ、かつ1つのWidgetだけ同時に更新するようすることである程度補うことができます。ただ、たとえば80ものオブジェクトを持つリストがあったとして、それぞれが複数のStreamを持つのは得策ではありません。Dartのインスペクターを開いて、StreamBuilderがどれだけRAMを消費しているか見てみてください。私が伝えたいことを理解してもらえると思います。
そのことを念頭に私はシンプルな状態管理ソリューションを作りました。このシンプルさに期待することは、リソース面で経済的である点、Widget単位ではなくブロック単位で状態を更新できる点であるべきです。
GetBuilderはRAM消費の面で最も経済的なソリューションだと信じています(もしあれば、ぜひ教えてください)。
しかし、GetBuilderは依然として update() により更新がかかるスタイルのメカニカルな状態管理ソリューションであり、notifyListeners() と呼び出し回数は変わりありません。
一方で、ここでリアクティブプログラミングを使わないのは車輪の再発明なんじゃないかと思えるような状況もあります。この点を考慮して、GetX() は先進的な状態管理の手法を提供するために作られました。必要なものを必要なときだけ更新し、エラーが発生してユーザーが300ものイベントを同時に送信したとしても、GetX() は状態の変化をフィルタリングして画面を更新してくれます。
GetX() は他のリアクティブな状態管理ソリューションに比べて経済的であることに変わりはありませんが、GetBuilder() よりは少しだけ多くのRAMを消費します。その点を考慮し、リソース消費を最大限活かすことを目指して Obx() は開発されました。GetX() や GetBuilder() と異なり、Obx() の中でControllerを初期化することはできません。Obx() は、子Widgetからの更新イベントを受け取る Stream購読Widgetでしかありません。GetX() よりは経済的ですが、GetBuilder() には負けます。GetBuilder() はWidgetのハッシュ値と状態のsetterを保持しているだけなので、これはある意味当然です。Obx() はControllerの型を指定する必要がなく、複数の異なるコントローラからの変更を聞くことができます。ただし、Obx() の外かBindingsで事前にControllerを初期化しておく必要があります。
... ...