first commit

This commit is contained in:
Hamza-Ayed
2026-06-09 08:40:31 +03:00
commit d8901e1a87
3161 changed files with 536187 additions and 0 deletions

View File

@@ -0,0 +1,405 @@
# 依存オブジェクト管理
- [依存オブジェクト管理](#依存オブジェクト管理)
- [インスタンス化に使用するメソッド](#インスタンス化に使用するメソッド)
- [Get.put()](#getput)
- [Get.lazyPut()](#getlazyput)
- [Get.putAsync()](#getputasync)
- [Get.create()](#getcreate)
- [インスタンス化したクラスを使う](#インスタンス化したクラスを使う)
- [依存オブジェクトの置換](#依存オブジェクトの置換)
- [各メソッドの違い](#各メソッドの違い)
- [BindingsRouteと依存オブジェクトの結束](#BindingsRouteと依存オブジェクトの結束)
- [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` としていた場合、インスタンスは「ファクトリー」に残るため、共有メモリ領域から削除されても再び呼び出すことができるのです。
## BindingsRouteと依存オブジェクトの結束
このパッケージの一番の差別化要素は、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()` だけでも全く問題ありません。
ただし、サービスやその他抽象度の高いクラスをアプリに取り入れる場合はコード整理のために使うことをおすすめします。

View File

@@ -0,0 +1,567 @@
- [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
),
],
)
);
}
```
未定義Route404エラーに遷移させるには、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")
),
),
),
);
}
}
),
```

View File

@@ -0,0 +1,787 @@
* [状態管理](#状態管理)
+ [リアクティブな状態管理](#リアクティブな状態管理)
- [利点](#利点)
- [パフォーマンスの最大化](#パフォーマンスの最大化)
- [リアクティブな変数の宣言](#リアクティブな変数の宣言)
- [初期値の設定](#初期値の設定)
- [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を初期化しておく必要があります。