getx用法詳細解析以及注意事項

源碼地址
在 Flutter 中,Get 是來自 get 包的一個輕量級、功能強大的狀態管理與路由框架,常用于:

  • 狀態管理
  • 路由管理
  • 依賴注入(DI)
  • Snackbar / Dialog / BottomSheet 管理
  • 本地化(多語言)

下面是 get 的常見用法介紹:


? 1. 安裝

pubspec.yaml 中添加依賴:

dependencies:get: ^4.6.6

? 2. 基礎設置

main.dart 使用 GetMaterialApp 替代 MaterialApp

import 'package:flutter/material.dart';
import 'package:get/get.dart';void main() {runApp(GetMaterialApp(home: HomePage(),));
}

? 3. 狀態管理用法

3.1 使用 GetxController 管理狀態:

class CounterController extends GetxController {var count = 0.obs;void increment() {count++;}
}

3.2 頁面中使用:

class HomePage extends StatelessWidget {final CounterController c = Get.put(CounterController());Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("GetX Example")),body: Center(child: Obx(() => Text("Count: ${c.count}")),),floatingActionButton: FloatingActionButton(onPressed: c.increment,child: Icon(Icons.add),),);}
}

明白了!你問的是 Flutter 的 GetX 框架中狀態管理的幾種方式,包括如何定義狀態、如何更新狀態、以及它們的區別與用法示例。


? GetX 狀態管理的三種主要方式

類型特點使用方式關鍵字
響應式(Reactive)基于 .obs 變量 + Obx 小部件Rx / .obs + Obx
簡單狀態(Simple)基于 GetBuilder 刷新 widgetGetBuilder
混合狀態(Worker)使用 ever/once 監聽變化Workers

🧪 方式一:響應式(Reactive State)Rx + Obx

? 使用場景:最常用,響應式自動刷新,無需手動更新。

🧵 示例:

class CounterController extends GetxController {var count = 0.obs;void increment() {count++;}
}class CounterPage extends StatelessWidget {final CounterController c = Get.put(CounterController());Widget build(BuildContext context) {return Scaffold(body: Center(child: Obx(() => Text("Count: ${c.count}"))),floatingActionButton: FloatingActionButton(onPressed: c.increment,child: Icon(Icons.add),),);}
}

? 特點:

  • 自動刷新 UI(通過 Obx
  • 狀態類型必須是 .obs 形式或 Rx<Type> 類型
  • 適合實時反應數據變化

🧪 方式二:簡單狀態(Simple State)使用 GetBuilder<T>

? 使用場景:性能高、不需要響應式,只在需要時手動刷新。

🧵 示例:

class CounterController extends GetxController {int count = 0;void increment() {count++;update(); // 手動通知刷新}
}class CounterPage extends StatelessWidget {Widget build(BuildContext context) {return GetBuilder<CounterController>(init: CounterController(),builder: (c) => Scaffold(body: Center(child: Text("Count: ${c!.count}")),floatingActionButton: FloatingActionButton(onPressed: c.increment,child: Icon(Icons.add),),),);}
}

? 特點:

  • update()GetBuilder 中的 widget 刷新
  • 性能更高,無響應式開銷
  • 不需要使用 .obs

🧪 方式三:混合監聽 Worker(更高級)

? 使用場景:在狀態變化時執行副作用邏輯(如網絡請求、打印等)

🧵 示例:

class CounterController extends GetxController {var count = 0.obs;void onInit() {ever(count, (value) => print("count changed: $value"));super.onInit();}void increment() => count++;
}

? 常用 Worker 方法:

方法說明
ever()每次變化都會觸發
once()第一次變化觸發一次
debounce()用戶停止輸入一段時間后才觸發(適合搜索)
interval()一定時間內只觸發一次(節流)

🔄 狀態更新對比

方式自動刷新手動調用響應式變量性能
Obx???中等
GetBuilder??(update()?
Worker???用于副作用

🧠 總結建議:

使用目的推薦方式
快速開發、響應式 UIRx + Obx
高性能、低耦合局部刷新GetBuilder
響應狀態變化做副作用Workers 系列

Obx 會監聽 所有.obs 的變化嗎,還是可以指定,他是與某個Controller綁定嗎


🎯 Obx 的監聽機制:只監聽它內部使用到的 .obs 變量

? 它不會監聽所有 .obs,只監聽你在 Obx 函數體中實際使用到的 .obs


🔍 示例說明:

class MyController extends GetxController {var a = 0.obs;var b = 0.obs;
}
final c = Get.put(MyController());Obx(() => Text("${c.a}")); // ? 只會監聽 c.a 的變化,不管 c.b

如果 c.b 改變了,Obx 不會重建,因為你沒有在 Obx 里使用它。


🧠 你問的三個點詳解:

1. ? Obx 會監聽所有 .obs 嗎?

?不會。
只監聽 Obx 函數內部使用到的 .obs,不會管你定義了多少個 .obs


2. ? 可以指定監聽變量嗎?

?是的,通過寫在 Obx 內部來“指定”。

你只要在 Obx 里訪問了某個 .obs 變量,它就自動監聽它。

例如:

Obx(() => Text("${controller.username}")); // 只監聽 username

3. ? Obx 是與某個 Controller 綁定的嗎?

?不是綁定關系,它只是“用了”某個 Controller 里的 .obs

你甚至可以在 Obx 中混用多個 controller 的 .obs

Obx(() => Text("${c1.a} - ${c2.b}")); // 同時監聽 c1 和 c2 的狀態

? 總結一句話:

Obx 監聽誰,取決于你在它里面訪問了哪些 .obs 變量,與是否來自某個 Controller 無關。


如果你想實現多個 Obx 分別監聽不同狀態避免無關刷新,這就是 GetX 的優勢之一,寫法也很清晰。

這里是一個完整示例,展示如何用多個 Obx 精確監聽不同的 .obs 狀態變量,從而避免不必要的重建 —— 每個 Obx 只響應自己的數據變化。


? 示例場景:兩個計數器,兩個 Obx,互不干擾

import 'package:flutter/material.dart';
import 'package:get/get.dart';void main() {runApp(GetMaterialApp(home: CounterPage()));
}/// 控制器,包含兩個獨立的狀態變量
class CounterController extends GetxController {var countA = 0.obs;var countB = 0.obs;void incrementA() => countA++;void incrementB() => countB++;
}

? 頁面布局(兩個 Obx 精準監聽)

class CounterPage extends StatelessWidget {final controller = Get.put(CounterController());Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("多個 Obx 獨立監聽示例")),body: Column(mainAxisAlignment: MainAxisAlignment.center,children: [// 只監聽 countAObx(() => Text("計數器 A:${controller.countA}",style: TextStyle(fontSize: 20))),ElevatedButton(onPressed: controller.incrementA,child: Text("A +1"),),SizedBox(height: 40),// 只監聽 countBObx(() => Text("計數器 B:${controller.countB}",style: TextStyle(fontSize: 20))),ElevatedButton(onPressed: controller.incrementB,child: Text("B +1"),),],),);}
}

? 運行效果

  • 點擊「A +1」只會刷新第一個 Text,第二個不動。
  • 點擊「B +1」只會刷新第二個 Text,第一個不動。

🧠 總結要點

做法原因/作用
多個 Obx精準監聽、獨立更新
.obs 拆成多個變量分離狀態,避免串聯刷新
不要在一個 Obx 里訪問多個狀態否則它會“監聽多個”,導致全部重建

如果你要優化一個頁面性能,建議就是:

  • 把狀態拆得更小(多個 .obs);
  • 把 UI 拆得更細(多個 Obx);
  • 這樣能避免不必要的 widget rebuild。

GetBuilder 會監聽 所有.obs 的變化嗎,還是可以指定,他是與某個Controller綁定嗎

我們來精準拆解 GetBuilder<T> 的監聽機制


? 結論先行

問題答案
GetBuilder<T> 會監聽所有 .obs 的變化嗎?? 不會。它不監聽 .obs,和 .obs 完全無關。
GetBuilder 可以指定監聽變量嗎?? 可以手動控制刷新范圍,但不是通過變量監聽,而是 id 區分。
它是與某個 Controller 綁定的嗎?? 是的T 就是綁定的 Controller 類型。

🧠 本質區別:GetBuilder.obs 沒有關系

GetBuilder 的刷新是由 controller.update() 主動觸發的,和你有沒有使用 .obs 完全無關。

? 示例說明:

class MyController extends GetxController {int countA = 0;int countB = 0;void incrementA() {countA++;update(['a']); // 指定 id = 'a' 的 GetBuilder 會刷新}void incrementB() {countB++;update(['b']); // 指定 id = 'b' 的 GetBuilder 會刷新}
}
class MyPage extends StatelessWidget {final controller = Get.put(MyController());Widget build(BuildContext context) {return Scaffold(body: Column(children: [GetBuilder<MyController>(id: 'a',builder: (_) => Text("A: ${_.countA}"),),GetBuilder<MyController>(id: 'b',builder: (_) => Text("B: ${_.countB}"),),ElevatedButton(onPressed: controller.incrementA, child: Text("A +1")),ElevatedButton(onPressed: controller.incrementB, child: Text("B +1")),]),);}
}

? update() 概要:

調用方式刷新范圍
update()所有使用該 Controller 的 GetBuilder
update(['a'])只刷新 id 為 'a'GetBuilder
update(['b', 'c'])同時刷新 'b''c'

? 總結對比表:Obx vs GetBuilder

特性Obx(響應式)GetBuilder(手動)
是否與 .obs 有關? 是? 無關
是否自動刷新? 會自動監聽 .obs 的變化? 不會,需手動調用 update()
是否能精細控制刷新區域? 拆多個 Obx? 支持 id 精細控制
性能表現中等(需響應式依賴追蹤)高(只有調用 update() 才重建)
與 Controller 綁定? 沒有綁定(只用了變量)? 強綁定,必須指定類型 T

如果你是性能優先、UI固定、只需要響應某些點擊/事件,推薦 GetBuilder
如果你需要動態響應式 UI(比如登錄狀態、購物車數量),用 Obx 更適合。


🔍 Worker 監聽機制簡述

Worker 是 GetX 中用來監聽特定 .obs 狀態變化并執行副作用操作的一組工具函數,例如 everoncedebounceinterval 等。


? 問題逐條回答

問題回答
Worker 會監聽所有 .obs 的變化嗎?? 不會,只監聽你顯式傳入的某個 .obs 變量。不會自動監聽全部。
可以指定監聽哪個 .obs 嗎?? 必須指定監聽哪個 .obs,Worker 的第一個參數就是監聽目標。
Worker 是否與某個 Controller 綁定?? 通常放在某個 Controller 中使用,但綁定的是 .obs不是 Controller 本身

🧪 示例:監聽某個 .obs 變量的變化

class MyController extends GetxController {var count = 0.obs;var username = ''.obs;void onInit() {super.onInit();// 每次 count 改變時打印ever(count, (value) => print("count changed: $value"));// 只監聽 username 第一次改變once(username, (value) => print("username changed once: $value"));// 用戶停止輸入 800ms 后才觸發(如搜索)debounce(username, (value) => print("debounced: $value"), time: Duration(milliseconds: 800));// 每 2 秒最多觸發一次interval(count, (value) => print("interval: $value"), time: Duration(seconds: 2));}void increment() => count++;void setUsername(String name) => username.value = name;
}

🧠 總結:Worker 用法與綁定機制

項目說明
監聽對象必須手動傳入某個 .obs(如 count, username
可監聽多個變量? 可以在一個 controller 里設置多個 ever() 等,監聽多個 .obs
生命周期綁定通常在 onInit() 中設置,自動隨 controller 生命周期注銷
與 Controller 關系? 通常放在 controller 中,但不是監聽整個 controller,僅監聽你指定的 .obs

? Worker 適用場景

場景使用方法
搜索輸入防抖(停止輸入才查)debounce(textObs, callback)
防止按鈕頻繁點擊interval(buttonTapObs, callback)
登錄狀態變化提示ever(isLoggedInObs, callback)
頁面加載后只響應一次(如埋點)once(pageReadyObs, callback)

好的,這里是一個完整示例:
使用 GetX 的 Worker 機制 來監聽兩個 .obs

  1. 用戶名輸入框 → 使用 debounce 實現 防抖搜索
  2. 登錄狀態 → 使用 ever 實現登錄提示(彈 Snackbar)

? 項目結構預覽

lib/
├── main.dart
└── controller.dart  ← 狀態 & Worker 邏輯

📄 controller.dart

import 'package:get/get.dart';
import 'package:flutter/material.dart';class AuthController extends GetxController {// 狀態變量var username = ''.obs;var isLoggedIn = false.obs;void onInit() {super.onInit();// 防抖搜索:用戶停止輸入 800ms 后觸發搜索邏輯debounce(username, (value) {print("🔍 執行搜索:$value");// 模擬調用搜索 API}, time: Duration(milliseconds: 800));// 登錄狀態變化提示ever(isLoggedIn, (status) {if (status == true) {Get.snackbar("登錄成功", "歡迎你,${username.value} 🎉");} else {Get.snackbar("退出登錄", "已成功退出 👋");}});}void login() {if (username.value.isNotEmpty) {isLoggedIn.value = true;}}void logout() {isLoggedIn.value = false;}
}

📄 main.dart

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'controller.dart';void main() {runApp(GetMaterialApp(home: LoginPage()));
}class LoginPage extends StatelessWidget {final auth = Get.put(AuthController());Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("GetX Worker 示例")),body: Padding(padding: const EdgeInsets.all(24),child: Column(children: [TextField(decoration: InputDecoration(labelText: "用戶名(用于模擬搜索)"),onChanged: (value) => auth.username.value = value,),SizedBox(height: 20),Obx(() => auth.isLoggedIn.value? Column(children: [Text("已登錄為:${auth.username.value}",style: TextStyle(fontSize: 18)),SizedBox(height: 10),ElevatedButton(onPressed: auth.logout,child: Text("退出登錄"),),],): ElevatedButton(onPressed: auth.login,child: Text("登錄"),)),],),),);}
}

? 運行效果

  • 輸入用戶名時,不會立刻打印“搜索”,而是停止輸入 800ms 后觸發一次模擬搜索(防抖)。
  • 點擊“登錄”后,isLoggedIntrue,會自動觸發 ever,顯示 Snackbar 提示“登錄成功”。

🧠 技術要點

功能技術手段
搜索輸入防抖debounce(username, ...)
登錄狀態變化提示ever(isLoggedIn, ...)
Snackbar 彈窗Get.snackbar(...)
狀態綁定 UIObx(() => ...)
控制器全局管理Get.put(AuthController())

擴展上一個 Worker 示例,加入以下兩個功能:


? 目標功能擴展

1. ? 模擬異步搜索請求(帶加載動畫)

  • 當用戶輸入停止后,模擬調接口(2秒)
  • 顯示搜索中動畫
  • 請求完成后顯示「已搜索:XXX」

2. ? 登錄成功自動跳轉到歡迎頁面

  • 用戶輸入用戶名,點擊登錄
  • 彈出 Snackbar 提示
  • 自動跳轉到歡迎頁面(WelcomePage

📦 新狀態變量(在 Controller 中添加)

var isSearching = false.obs;
var searchResult = ''.obs;

📄 controller.dart(更新后的)

import 'package:get/get.dart';
import 'package:flutter/material.dart';class AuthController extends GetxController {var username = ''.obs;var isLoggedIn = false.obs;var isSearching = false.obs;var searchResult = ''.obs;void onInit() {super.onInit();// 防抖搜索debounce(username, (val) async {if (val.toString().isEmpty) return;isSearching.value = true;searchResult.value = '';print("開始搜索:$val");// 模擬異步接口調用await Future.delayed(Duration(seconds: 2));searchResult.value = "搜索完成:$val";isSearching.value = false;}, time: Duration(milliseconds: 800));// 登錄提示 & 自動跳轉ever(isLoggedIn, (status) {if (status == true) {Get.snackbar("登錄成功", "歡迎你,${username.value} 🎉");Future.delayed(Duration(milliseconds: 800), () {Get.off(WelcomePage()); // 跳轉歡迎頁});} else {Get.snackbar("退出登錄", "已成功退出 👋");}});}void login() {if (username.value.isNotEmpty) {isLoggedIn.value = true;}}void logout() {isLoggedIn.value = false;}
}

📄 main.dart

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'controller.dart';void main() {runApp(GetMaterialApp(home: LoginPage()));
}class LoginPage extends StatelessWidget {final auth = Get.put(AuthController());Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("GetX Worker 擴展示例")),body: Padding(padding: const EdgeInsets.all(24),child: Column(crossAxisAlignment: CrossAxisAlignment.start,children: [TextField(decoration: InputDecoration(labelText: "用戶名"),onChanged: (value) => auth.username.value = value,),SizedBox(height: 16),// 搜索中或結果Obx(() {if (auth.isSearching.value) {return Row(children: [CircularProgressIndicator(strokeWidth: 2),SizedBox(width: 8),Text("搜索中..."),],);} else if (auth.searchResult.value.isNotEmpty) {return Text(auth.searchResult.value,style: TextStyle(color: Colors.green));} else {return Container();}}),SizedBox(height: 32),Obx(() => auth.isLoggedIn.value? Column(crossAxisAlignment: CrossAxisAlignment.start,children: [Text("已登錄為:${auth.username.value}",style: TextStyle(fontSize: 18)),SizedBox(height: 10),ElevatedButton(onPressed: auth.logout,child: Text("退出登錄"),),],): ElevatedButton(onPressed: auth.login,child: Text("登錄"),)),],),),);}
}class WelcomePage extends StatelessWidget {Widget build(BuildContext context) {final username = Get.find<AuthController>().username.value;return Scaffold(appBar: AppBar(title: Text("歡迎頁面")),body: Center(child: Text("歡迎回來,$username!", style: TextStyle(fontSize: 24)),),);}
}

? 運行效果

操作效果
輸入用戶名停止輸入 800ms 后開始「搜索」,2秒后顯示搜索結果
點擊登錄按鈕彈出「登錄成功」提示,800ms 后自動跳轉歡迎頁
歡迎頁展示當前用戶名

🧠 技術亮點

功能技術手段
防抖搜索debounce(obs, callback)
登錄提示ever(isLoggedIn, callback)
頁面跳轉Get.off()
狀態綁定顯示Obx(() => ...)
異步處理加載動畫.obs + Future + CircularProgressIndicator

是否還需要加上:

  • ? 登錄失敗提示(用戶名為空時)
  • ? 登錄后的 token 保存 / 本地持久化
  • ? 從登錄頁自動恢復登錄狀態

? 4. 路由管理(導航)

GetX 的路由(導航)管理功能非常強大、簡潔,無需 context,還支持命名路由、無命名路由、參數傳遞、動畫控制等。


📦 一、基礎配置:使用 GetMaterialApp

main.dart 中使用:

void main() {runApp(GetMaterialApp(initialRoute: '/',getPages: AppRoutes.routes,));
}

📁 二、定義路由表(推薦結構)

創建 routes.dart 文件:

import 'package:get/get.dart';
import 'home_page.dart';
import 'detail_page.dart';class AppRoutes {static final routes = [GetPage(name: '/', page: () => HomePage()),GetPage(name: '/detail', page: () => DetailPage()),];
}

🚀 三、導航跳轉方式

? 1. 普通跳轉(非命名)

Get.to(DetailPage());

? 2. 命名路由跳轉

Get.toNamed('/detail');

? 3. 返回上一頁

Get.back();

? 4. 替換當前頁(不能返回)

Get.off(DetailPage());
Get.offNamed('/detail');

? 5. 清空歷史并跳轉(常用于登錄成功)

Get.offAllNamed('/home');

🎯 四、參數傳遞方式

? 方式一:通過 arguments 傳遞參數(推薦)

🔁 傳參:
Get.toNamed('/detail', arguments: {'id': 123, 'title': '測試'});
🧾 接收:
final args = Get.arguments as Map;
print(args['id']); // 123

? 方式二:通過 URL 參數(路徑參數)

📥 定義路由時設置參數:
GetPage(name: '/detail/:id',page: () => DetailPage(),
)
🔁 傳參:
Get.toNamed('/detail/888');
🧾 接收:
final id = Get.parameters['id']; // "888"
? 也支持 query:
Get.toNamed('/detail/888?title=測試');
final title = Get.parameters['title']; // "測試"

? 五、完整例子

📄 main.dart

void main() {runApp(GetMaterialApp(initialRoute: '/',getPages: [GetPage(name: '/', page: () => HomePage()),GetPage(name: '/detail/:id', page: () => DetailPage()),],));
}

📄 home_page.dart

class HomePage extends StatelessWidget {Widget build(BuildContext context) {return Scaffold(body: Center(child: ElevatedButton(onPressed: () => Get.toNamed('/detail/123?title=你好'),child: Text("去詳情頁"),),),);}
}

📄 detail_page.dart

class DetailPage extends StatelessWidget {Widget build(BuildContext context) {final id = Get.parameters['id'];final title = Get.parameters['title'];return Scaffold(appBar: AppBar(title: Text("詳情頁")),body: Center(child: Text("ID: $id, 標題: $title"),),);}
}

🧠 常用跳轉方法對比總結

方法功能
Get.to(Widget())跳轉新頁面
Get.toNamed('/path')命名路由跳轉
Get.off(...)替換當前頁面
Get.offAll(...)清空棧跳轉,常用于登錄成功
Get.back()返回上一頁
Get.arguments獲取參數(Map)
Get.parameters['id']獲取 URL 參數

在 GetX 中,每個路由都可以獨立配置動畫,通過 GetPage 的以下屬性實現:


? 一、使用內建動畫配置

你可以通過 transitiontransitionDuration 快速配置內建動畫:

GetPage(name: '/detail',page: () => DetailPage(),transition: Transition.rightToLeftWithFade, // 動畫類型transitionDuration: Duration(milliseconds: 400), // 動畫時間
)

🎬 內建動畫類型一覽(Transition 枚舉):

動畫類型效果描述
Transition.fade淡入淡出
Transition.rightToLeft從右向左滑動進入
Transition.leftToRight從左向右滑動進入
Transition.upToDown從上往下
Transition.downToUp從下往上
Transition.rightToLeftWithFade滑動 + 淡入淡出
Transition.zoom縮放
Transition.topLevel立體層疊感(較強)

? 二、自定義動畫 customTransition

如果內建動畫不滿足需求,可以自定義動畫:

🔧 1. 創建自定義動畫類:

class MyCustomTransition extends CustomTransition {Widget buildTransition(BuildContext context,Curve curve,Alignment alignment,Animation<double> animation,Animation<double> secondaryAnimation,Widget child,) {return SlideTransition(position: Tween<Offset>(begin: Offset(0, 1),  // 從底部進來end: Offset.zero,).animate(animation),child: child,);}
}

📌 2. 應用在 GetPage 中:

GetPage(name: '/detail',page: () => DetailPage(),customTransition: MyCustomTransition(),transitionDuration: Duration(milliseconds: 500),
)

? 三、全局默認過渡動畫(不推薦)

如果你希望所有頁面默認使用統一動畫,可以在 GetMaterialApp 中配置:

GetMaterialApp(defaultTransition: Transition.fade,transitionDuration: Duration(milliseconds: 300),
)

?? 缺點:所有頁面一刀切,不靈活。


? 四、頁面跳轉時單獨設置動畫(臨時跳轉)

Get.to(DetailPage(),transition: Transition.zoom,duration: Duration(milliseconds: 400),curve: Curves.easeInOut,
);

🎯 總結:路由動畫配置選型

方式優點使用場景
transition + duration簡潔、常規頁面跳轉動畫大多數頁面跳轉
customTransition靈活自定義復雜動畫自定義 slide、fade、組合動畫
defaultTransition快速統一默認動畫小項目統一風格
Get.to() 傳入動畫參數單次臨時跳轉自定義特定跳轉需要額外動畫時

? 5. 依賴注入

GetX 的依賴注入(Dependency Injection, 簡稱 DI)非常強大且簡單,常用于控制器、服務類的自動注冊與全局獲取,避免你手動管理生命周期和傳遞 context


🧠 為什么使用 Get 的依賴注入?

  • 不用手動傳對象
  • 生命周期自動管理
  • 支持懶加載、永久實例、局部注入
  • Provider 簡單很多

? 1. 基本用法:Get.put()(立即注入)

📌 注冊依賴

final controller = Get.put(MyController());
  • 立即創建并注冊
  • 可在任何地方調用 Get.find<MyController>() 來獲取

? 2. 懶加載:Get.lazyPut()

Get.lazyPut<MyController>(() => MyController());
  • 在第一次使用時才創建
  • 更節省內存,適合大項目中很多控制器

? 3. 永久依賴:Get.put(..., permanent: true)

Get.put(MyService(), permanent: true);
  • 永久存在,Get.reset() 也不會清除
  • 適合如網絡層、用戶配置等全局服務

? 4. 獲取依賴:Get.find<T>()

final c = Get.find<MyController>();
  • 在任意位置獲取,不需要 context
  • 如果未注冊,會拋錯

? 5. 刪除依賴:Get.delete<T>()

Get.delete<MyController>();
  • 銷毀注冊的依賴,釋放內存

? 6. 結合頁面使用示例

👇 創建一個 Controller

class CounterController extends GetxController {var count = 0.obs;void increment() => count++;
}

👇 頁面注冊 & 使用(推薦在頁面內 Get.put()

class CounterPage extends StatelessWidget {final controller = Get.put(CounterController());Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("計數器")),body: Center(child: Obx(() => Text("Count: ${controller.count}")),),floatingActionButton: FloatingActionButton(onPressed: controller.increment,child: Icon(Icons.add),),);}
}

? 7. 自動依賴綁定:使用 Bindings

適用于路由時自動注入依賴。

創建 Bindings 類:

class CounterBinding extends Bindings {void dependencies() {Get.lazyPut<CounterController>(() => CounterController());}
}

配置到路由:

GetPage(name: '/counter',page: () => CounterPage(),binding: CounterBinding(),
)

使用時跳轉:

Get.toNamed('/counter'); // 會自動執行綁定

🧠 總結依賴注入方法

方法用途
Get.put()立即注入
Get.lazyPut()懶加載注入,首次使用才創建
Get.putAsync()異步創建實例
Get.find<T>()獲取已注入實例
Get.delete<T>()刪除實例
permanent: true保留永久實例(不被清除)
Bindings自動依賴注入,推薦配合路由使用

以下是適合中大型項目的 GetX 推薦目錄結構 + 依賴注入(DI)配置方案,可直接用于你實際項目中。


🗂 推薦項目結構

lib/
├── main.dart
├── app/
│   ├── routes/
│   │   ├── app_pages.dart        ← 所有頁面路由配置(含綁定)
│   │   └── app_routes.dart       ← 所有路由名定義
│   ├── bindings/
│   │   ├── auth_binding.dart     ← 登錄相關依賴
│   │   └── global_binding.dart   ← 全局一次性綁定(如網絡服務)
│   ├── controllers/
│   │   ├── auth_controller.dart
│   │   └── home_controller.dart
│   ├── views/
│   │   ├── login_page.dart
│   │   └── home_page.dart
│   └── services/
│       ├── api_service.dart
│       └── storage_service.dart

🧠 1. 控制器定義(如 auth_controller.dart

class AuthController extends GetxController {var isLoggedIn = false.obs;void login(String username) {isLoggedIn.value = true;}void logout() {isLoggedIn.value = false;}
}

?? 2. 單個 Binding 示例(如 auth_binding.dart

class AuthBinding extends Bindings {void dependencies() {Get.lazyPut<AuthController>(() => AuthController());}
}

?? 3. 全局 Binding(如 global_binding.dart

class GlobalBinding extends Bindings {void dependencies() {// 注冊全局服務Get.put<ApiService>(ApiService(), permanent: true);Get.put<StorageService>(StorageService(), permanent: true);}
}

🔁 4. 路由配置(app_pages.dart)

import 'package:get/get.dart';
import '../views/login_page.dart';
import '../views/home_page.dart';
import '../bindings/auth_binding.dart';class AppPages {static final routes = [GetPage(name: '/login',page: () => LoginPage(),binding: AuthBinding(),),GetPage(name: '/home',page: () => HomePage(),),];
}

🧭 5. 啟動入口(main.dart)

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'app/routes/app_pages.dart';
import 'app/bindings/global_binding.dart';void main() {runApp(GetMaterialApp(initialRoute: '/login',getPages: AppPages.routes,initialBinding: GlobalBinding(), // 注冊全局依賴debugShowCheckedModeBanner: false,));
}

? 跳轉方式示例

Get.toNamed('/home');

控制器在跳轉頁面時會自動注入(如果該頁面設置了 binding:


🚀 小貼士:常見依賴注入場景與方式

場景推薦寫法
登錄頁面需要用到 AuthControllerbinding: AuthBinding()
首頁需要多個 controllerbinding: BindingsBuilder(() => {...})
全局單例服務(如網絡、緩存)Get.put(Service(), permanent: true)
獲取控制器實例final auth = Get.find<AuthController>();

🔚 總結

GetX 的依賴注入建議這樣使用:

類型使用方式說明
頁面級 ControllerlazyPut + Binding路由進入時自動注入
全局服務put(..., permanent: true)啟動時注入,全程共享
自定義 Service單獨建 services/ 目錄網絡、數據庫、存儲類都放這里
中央注冊點GlobalBinding 做一次性全局注入避免在 main.dart 重復注入依賴

? 6. Snackbar / Dialog / BottomSheet

GetX 提供了非常方便的 Snackbar、Dialog 和 BottomSheet 管理方法,支持無 Context 調用,語法簡潔,且自帶動畫和豐富參數。


1. Snackbar

基礎用法

Get.snackbar('標題','內容信息',snackPosition: SnackPosition.BOTTOM, // 頂部還是底部duration: Duration(seconds: 3),      // 顯示時間backgroundColor: Colors.blueGrey,colorText: Colors.white,
);

常用參數

參數說明
title標題文本
message內容文本
snackPosition顯示位置(TOP 或 BOTTOM)
duration顯示時間
backgroundColor背景顏色
colorText文字顏色
icon左側圖標
mainButton右側按鈕(Widget)

2. Dialog(彈窗)

簡單對話框

Get.defaultDialog(title: '提示',middleText: '你確定要刪除嗎?',textConfirm: '確認',textCancel: '取消',onConfirm: () {print('確認刪除');Get.back();},onCancel: () {print('取消刪除');},
);

自定義內容對話框

Get.dialog(AlertDialog(title: Text('自定義標題'),content: Text('這是自定義內容'),actions: [TextButton(onPressed: () => Get.back(),child: Text('關閉'),)],),
);

3. BottomSheet(底部彈窗)

簡單底部彈窗

Get.bottomSheet(Container(color: Colors.white,padding: EdgeInsets.all(16),child: Wrap(children: [ListTile(leading: Icon(Icons.photo),title: Text('照片'),onTap: () => Get.back(),),ListTile(leading: Icon(Icons.music_note),title: Text('音樂'),onTap: () => Get.back(),),],),),backgroundColor: Colors.white,shape: RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(20)),),
);

4. 關閉彈窗或 Snackbar

Get.back(); // 關閉當前彈窗、Snackbar、BottomSheet 等

5. 結合示例

ElevatedButton(onPressed: () {Get.snackbar('Hi', '這是一個消息提示');},child: Text('顯示Snackbar'),
);ElevatedButton(onPressed: () {Get.defaultDialog(title: '確認',middleText: '是否刪除該條數據?',textConfirm: '是',textCancel: '否',onConfirm: () {print('刪除');Get.back();},);},child: Text('顯示Dialog'),
);ElevatedButton(onPressed: () {Get.bottomSheet(Container(height: 200,color: Colors.white,child: Center(child: Text('這是一個底部彈窗')),),);},child: Text('顯示BottomSheet'),
);

下面是一個完整 Flutter 頁面示例,集成 GetX 的 Snackbar、Dialog、BottomSheet,帶按鈕交互,包含關閉邏輯和動畫配置,方便直接拿去用。


import 'package:flutter/material.dart';
import 'package:get/get.dart';class GetUIExamplePage extends StatelessWidget {Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('GetX Snackbar/Dialog/BottomSheet 示例'),),body: Padding(padding: const EdgeInsets.all(20),child: Column(children: [ElevatedButton(onPressed: () {Get.snackbar('提示','這是一條消息提示',snackPosition: SnackPosition.BOTTOM,duration: Duration(seconds: 4),backgroundColor: Colors.blueGrey.shade700,colorText: Colors.white,icon: Icon(Icons.info, color: Colors.white),mainButton: TextButton(onPressed: () => Get.back(),child: Text('關閉', style: TextStyle(color: Colors.white)),),);},child: Text('顯示 Snackbar'),),SizedBox(height: 20),ElevatedButton(onPressed: () {Get.defaultDialog(title: '確認操作',middleText: '確定要刪除這條記錄嗎?',textConfirm: '確認',textCancel: '取消',barrierDismissible: false,onConfirm: () {Get.back();Get.snackbar('刪除', '記錄已刪除', snackPosition: SnackPosition.BOTTOM);},onCancel: () => Get.back(),);},child: Text('顯示 Dialog'),),SizedBox(height: 20),ElevatedButton(onPressed: () {Get.bottomSheet(Container(decoration: BoxDecoration(color: Colors.white,borderRadius: BorderRadius.vertical(top: Radius.circular(20)),),padding: EdgeInsets.all(16),child: Wrap(children: [ListTile(leading: Icon(Icons.photo),title: Text('相冊'),onTap: () {Get.back();Get.snackbar('選擇', '點擊了相冊', snackPosition: SnackPosition.BOTTOM);},),ListTile(leading: Icon(Icons.camera_alt),title: Text('拍照'),onTap: () {Get.back();Get.snackbar('選擇', '點擊了拍照', snackPosition: SnackPosition.BOTTOM);},),ListTile(leading: Icon(Icons.cancel),title: Text('取消'),onTap: () => Get.back(),),],),),isDismissible: true,enableDrag: true,shape: RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(20)),),);},child: Text('顯示 BottomSheet'),),],),),);}
}

說明

  • Snackbar 帶關閉按鈕和圖標,位置在底部,自動消失
  • Dialog 有確認和取消按鈕,且點擊遮罩不可關閉(barrierDismissible: false
  • BottomSheet 帶圓角,上滑可拖拽關閉,包含幾個選項點擊時關閉并提示

你只需

Get.to(() => GetUIExamplePage());

? 7. 國際化(多語言)

GetX 內置了強大的本地化(國際化)支持,讓你快速實現多語言切換,且使用簡單靈活。


📦 基礎使用步驟

1. 創建語言翻譯類,繼承 Translations

import 'package:get/get.dart';class Messages extends Translations {Map<String, Map<String, String>> get keys => {'en_US': {'hello': 'Hello World','login': 'Login',},'zh_CN': {'hello': '你好,世界','login': '登錄',},};
}

2. 在 GetMaterialApp 中配置

GetMaterialApp(translations: Messages(),          // 綁定翻譯類locale: Locale('en', 'US'),        // 默認語言fallbackLocale: Locale('en', 'US'), // 找不到翻譯時的兜底語言home: MyHomePage(),
);

3. 頁面中使用 .tr 獲取對應語言文本

Text('hello'.tr),    // 自動根據當前語言顯示文本
ElevatedButton(onPressed: () => print('login'.tr),child: Text('login'.tr),
),

4. 動態切換語言

// 切換為中文
Get.updateLocale(Locale('zh', 'CN'));// 切換為英語
Get.updateLocale(Locale('en', 'US'));

? 完整示例

import 'package:flutter/material.dart';
import 'package:get/get.dart';void main() {runApp(GetMaterialApp(translations: Messages(),locale: Locale('en', 'US'),fallbackLocale: Locale('en', 'US'),home: HomePage(),));
}class Messages extends Translations {Map<String, Map<String, String>> get keys => {'en_US': {'hello': 'Hello World', 'login': 'Login'},'zh_CN': {'hello': '你好,世界', 'login': '登錄'},};
}class HomePage extends StatelessWidget {Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('hello'.tr)),body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: [Text('login'.tr),SizedBox(height: 20),ElevatedButton(onPressed: () => Get.updateLocale(Locale('zh', 'CN')),child: Text('切換到中文'),),ElevatedButton(onPressed: () => Get.updateLocale(Locale('en', 'US')),child: Text('Switch to English'),),],),),);}
}

🧠 注意事項

  • keys 里每個 key 是 語言_國家 格式,必須嚴格寫對
  • .trString 的擴展方法,直接調用即可
  • 語言切換后,Get 會自動通知所有使用 .tr 的 Widget 更新
  • 可以結合持久化保存用戶選擇的語言,下次啟動時自動加載

完整的 GetX 本地化示例

  • 多語言 JSON 文件分離管理
  • 從 JSON 讀取語言包
  • 語言切換后自動刷新 UI
  • 語言偏好保存到本地,下次啟動自動加載

1. 準備工作:在 assets/lang/ 目錄放語言 JSON 文件

assets/lang/en_US.json
assets/lang/zh_CN.json

示例 en_US.json

{"hello": "Hello World","login": "Login","logout": "Logout"
}

示例 zh_CN.json

{"hello": "你好,世界","login": "登錄","logout": "退出登錄"
}

2. 修改 pubspec.yaml,聲明資源文件

flutter:assets:- assets/lang/en_US.json- assets/lang/zh_CN.json

3. 創建 translation_service.dart,實現從 JSON 加載翻譯

import 'dart:convert';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:flutter/material.dart';class TranslationService extends Translations {static Locale? locale;static Locale fallbackLocale = Locale('en', 'US');// 用于緩存翻譯Mapfinal Map<String, Map<String, String>> _translations = {};TranslationService() {_loadTranslations();}// 從 assets 加載所有語言 JSON 文件Future<void> _loadTranslations() async {final locales = ['en_US', 'zh_CN'];for (var loc in locales) {final jsonString =await rootBundle.loadString('assets/lang/$loc.json');final Map<String, dynamic> jsonMap = json.decode(jsonString);_translations[loc] = jsonMap.map((key, value) => MapEntry(key, value.toString()));}}Map<String, Map<String, String>> get keys => _translations;// 切換語言static void changeLocale(Locale newLocale) {locale = newLocale;Get.updateLocale(newLocale);}
}

注意:這里用 async 讀取,建議在 main() 里先初始化好翻譯資源。


4. 修改 main.dart,初始化語言資源并讀取用戶偏好

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'translation_service.dart';
import 'package:shared_preferences/shared_preferences.dart';void main() async {WidgetsFlutterBinding.ensureInitialized();final prefs = await SharedPreferences.getInstance();String? languageCode = prefs.getString('language_code') ?? 'en';String? countryCode = prefs.getString('country_code') ?? 'US';TranslationService.locale = Locale(languageCode, countryCode);runApp(MyApp());
}class MyApp extends StatelessWidget {final TranslationService _translationService = TranslationService();Widget build(BuildContext context) {return GetMaterialApp(translations: _translationService,locale: TranslationService.locale,fallbackLocale: TranslationService.fallbackLocale,home: HomePage(),);}
}

5. 創建一個示例 HomePage,支持切換語言并保存偏好

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'translation_service.dart';class HomePage extends StatelessWidget {Future<void> _changeLanguage(Locale locale) async {TranslationService.changeLocale(locale);final prefs = await SharedPreferences.getInstance();await prefs.setString('language_code', locale.languageCode);await prefs.setString('country_code', locale.countryCode ?? '');}Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('hello'.tr),),body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: [Text('login'.tr, style: TextStyle(fontSize: 24)),SizedBox(height: 20),ElevatedButton(onPressed: () => _changeLanguage(Locale('zh', 'CN')),child: Text('切換到中文'),),ElevatedButton(onPressed: () => _changeLanguage(Locale('en', 'US')),child: Text('Switch to English'),),],),),);}
}

6. 依賴添加

pubspec.yaml 加入:

dependencies:get: ^4.6.5shared_preferences: ^2.0.15

小結

  • 語言文本放 JSON 文件,方便維護
  • TranslationService 負責加載 JSON 并提供翻譯
  • SharedPreferences 保存用戶語言偏好,應用啟動時讀取并設置
  • 頁面使用 .tr 獲取文本,自動根據當前語言刷新
  • 語言切換時調用 TranslationService.changeLocale() 并保存

GetX 中 Controller 回收機制

在使用 GetX 的 Controller(例如 GetxControllerBindings 綁定的 Controller) 時,生命周期管理(即“回收”)非常關鍵,不當使用可能會造成內存泄漏或狀態異常。

下面是關于 Controller 回收機制的說明,以及你在項目中應注意的關鍵問題。


? GetX 中 Controller 回收機制

1. 默認行為:Get.put() 注冊的是單例

Get.put(MyController());
  • 控制器將常駐內存,不會自動回收
  • 調用 Get.delete<MyController>()Get.reset() 才會釋放
  • permanent: true 時,即使手動調用 Get.reset(),也不會刪除

2. 按需釋放:Get.lazyPut()

Get.lazyPut(() => MyController());
  • 懶加載,首次使用時才創建實例
  • 默認會綁定頁面,當頁面被銷毀時自動回收

3. 自動回收:使用 GetBuilderGet.put 并指定 tag/fenix: false

Get.lazyPut(() => MyController(), fenix: false); // 頁面銷毀即銷毀 controller

🔁 Controller 生命周期方法

你可以在 Controller 中覆蓋這些方法:

class MyController extends GetxController {void onInit() {super.onInit();print('Controller 初始化');}void onReady() {super.onReady();print('頁面渲染完成');}void onClose() {print('Controller 被銷毀');super.onClose();}
}

🧠 實戰注意事項

場景推薦寫法注意事項
頁面間跳轉帶狀態Get.lazyPut(() => Controller())會隨頁面銷毀
多頁面共享狀態Get.put(Controller(), permanent: true)手動釋放 Get.delete()
臨時彈窗/小組件使用 Get.create(() => Controller())每次調用都創建新實例
獲取已存在實例Get.find<Controller>()沒有注冊會報錯

🧹 手動釋放 Controller

Get.delete<MyController>();
  • 如果你用的是 Get.put(),Controller 不會自動銷毀,必須手動釋放
  • 可加在 onClose()dispose()WillPopScope

🐞 常見坑 & 解決

? 控制器被重復創建

// 錯誤示例:每次都創建新 controller
Get.put(MyController()); // 多次執行

? 解決:

// 用 Get.putIfAbsent,避免重復創建
Get.put<MyController>(MyController(), permanent: false);

? Controller 沒有銷毀,狀態不一致

? 檢查是否用了 permanent: true,如果不再需要,應手動:

Get.delete<MyController>();

? 建議的寫法總結

目的推薦寫法
頁面內獨立 controllerGet.lazyPut(() => Controller())
共享全局狀態Get.put(() => Controller(), permanent: true)
確保生命周期自動管理使用 Bindings 注冊 controller
頁面關閉主動釋放Get.delete<Controller>()

如果你在使用 GetX 做狀態管理/DI/本地化,但不使用 Get.to() / Get.off() 等 GetX 路由 API,而是繼續用 Flutter 原生的 Navigator.push(),也是完全可以的——但你需要注意以下幾點,否則會失去 GetX 的一些功能或引起生命周期問題。


? 總體策略

如果你使用 Navigator.push(),但控制器是通過 Get.put() / Get.lazyPut() 提供的,你必須 手動管理 Controller 生命周期手動注入綁定邏輯,否則可能出現重復注入或無法回收。


? 正確使用方式說明

? 場景 1:頁面不需要獨立 Controller(或使用全局 Controller)

你可以正常使用原生跳轉:

Navigator.push(context,MaterialPageRoute(builder: (_) => HomePage()),
);

這時候只要你頁面中用的是:

final authController = Get.find<AuthController>();

就可以繼續享受 GetX 的狀態管理、.obs 等。


? 場景 2:頁面需要自己的 Controller + 生命周期管理

?? 由于你沒有用 GetPage() 路由定義,GetX 無法幫你自動綁定/釋放 Controller,你就需要手動注冊 + 銷毀。

? 推薦寫法:
class DetailPage extends StatelessWidget {final controller = Get.put(DetailController()); // 手動注冊Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('詳情')),body: Center(child: Obx(() => Text('value: ${controller.count}')),),);}
}

🔁 頁面關閉后,在合適的地方釋放:


void dispose() {Get.delete<DetailController>();super.dispose();
}
  • 如果你用的是 StatelessWidget,可以用 WidgetsBindingObserver + Get.delete() 或者改為 StatefulWidget

? 場景 3:自己實現一個 NavigatorWrapper 做中間處理

你也可以封裝一層 Navigator,用于兼容 Flutter 原生跳轉 + GetX 注入生命周期

void navigateWithInjection<T>(BuildContext context,Widget page, {required Bindings binding,
}) {binding.dependencies(); // 手動注入綁定Navigator.push(context,MaterialPageRoute(builder: (_) => page,),).then((_) {// 頁面返回后手動釋放Get.delete<T>();});
}

使用:

navigateWithInjection<DetailController>(context,DetailPage(),binding: BindingsBuilder(() {Get.put(DetailController());}),
);

🚫 不推薦的混用寫法(示例)

// 頁面 A 使用 Flutter Navigator.push
Navigator.push(context, MaterialPageRoute(builder: (_) => PageB()));// 頁面 B 用了 GetBuilder 綁定了 Controller,但沒有 GetPage 注入,也沒手動注入
GetBuilder<MyController>(builder: (_) => ...)

?? 這會導致:

  • MyController 根本沒被注冊(或重復注冊)
  • 頁面退出后不會釋放
  • 控制器是全局狀態?還是每次新建?邏輯混亂

? 最推薦方案總結

目標推薦方案
使用 Get 的所有功能(路由、DI、生命周期)使用 Get.to() + GetPage + binding
繼續使用原生路由 + 使用 Controller手動 Get.put() + Get.delete()
頁面中不涉及獨立 Controller可直接用 Navigator.push() + Get.find() 獲取全局依賴
自定義兼容方案封裝 push + binding/deletion 工具方法

👉 總結一句話:

如果你選擇使用 Flutter 原生導航,你必須自己負責控制器的注冊與釋放,GetX 不會自動幫你做這些了。


在使用 GetX 的 GetxController 時,理解并正確使用其生命周期方法對資源管理、網絡請求、控制器重用等場景非常關鍵。


🧭 GetxController 生命周期方法一覽(按執行順序)

方法名觸發時機作用
onInit()Controller 被創建后第一次調用初始化數據、訂閱、加載緩存等
onReady()Widget 渲染完畢(可獲取 context)適合啟動動畫、發送請求、打開 dialog 等
onClose()Controller 被銷毀前調用清理資源(如取消訂閱、定時器、監聽器等)
dispose()僅用于 GetxService,一般不用與 Flutter 原生 Widget 的 dispose 相同

? 推薦用法說明

onInit()

  • 用于初始化變量、Rx監聽器、計時器、數據拉取等
  • 不要訪問 context,它還沒準備好

void onInit() {super.onInit();debounce(searchTerm, (_) => fetchResults(), time: Duration(milliseconds: 500));
}

onReady()

  • 適合訪問 UI 或調用依賴 context 的邏輯(比如打開 dialog、動畫、focus)
  • 會在 widget tree build 完成后執行一次

void onReady() {super.onReady();Future.delayed(Duration(milliseconds: 300), () {Get.snackbar('提示', '頁面加載完成');});
}

onClose()

  • 回收資源、取消監聽、關閉 stream、關閉計時器、斷開 WebSocket 等都放這里
  • 頁面關閉、Controller 被銷毀時自動調用
late Timer timer;
void onInit() {timer = Timer.periodic(Duration(seconds: 1), (_) => print('tick'));super.onInit();
}
void onClose() {timer.cancel();super.onClose();
}

?? 常見誤區

錯誤情況原因解決方案
onClose() 不調用Controller 沒有被釋放(如 permanent: true改用非 permanent,或手動 Get.delete<>()
onInit() 里訪問 contextcontext 尚未可用使用 onReady()
訂閱 .obs 沒有清理內存泄漏ever()debounce() 時在 onClose() 里調用 dispose()
Controller 只用了一次卻沒銷毀忘記用 lazyPut()/綁定頁面,或 Get.put() 沒 delete用綁定系統或在頁面返回時手動 Get.delete()

🌟 典型 Controller 生命周期例子

class LoginController extends GetxController {final RxString username = ''.obs;late Worker _worker;void onInit() {super.onInit();// 輸入防抖_worker = debounce(username, (_) {print("用戶名變化: $username");}, time: Duration(milliseconds: 500));}void onReady() {super.onReady();print('登錄頁就緒,可以顯示動畫或提示');}void onClose() {_worker.dispose();print('LoginController 已銷毀');super.onClose();}
}

? 最佳實踐總結

你要做什么放在哪個生命周期中
初始化變量、綁定 Rx 監聽onInit()
動畫/彈窗/訪問 contextonReady()
清理 Rx Worker、Timer、StreamonClose()
控制器不釋放,生命周期不觸發檢查是否用了 permanent 或未 delete

我們來繼續擴展你的文檔內容,添加一節關于 “多個頁面共用同一個 Controller” 的處理方式與注意事項,將如下內容追加到你現有文檔末尾:


🔄 多個頁面共用同一個 Controller:處理方式與注意事項

在大型應用中,為了保持狀態一致、避免重復邏輯,我們常常希望多個頁面共用一個 Controller,例如登錄狀態、購物車、用戶信息等。

? 實現方式

最常用的方式是使用 Get.put()Get.lazyPut() 注冊為全局 Controller:

// 在 GlobalBinding 或 main 中注入
Get.put<UserController>(UserController(), permanent: true);

在多個頁面中使用:

final userController = Get.find<UserController>();

? 可選方式:使用 Tag 區分多個實例(不推薦用于共享場景)

除非你需要多個實例互不干擾,否則不要用 tag:

Get.put(UserController(), tag: 'A');
Get.put(UserController(), tag: 'B');

🧠 注意事項

?? 問題說明與建議
Controller 被重復創建避免在每個頁面中都寫 Get.put(),應通過 Get.find() 獲取已存在實例
生命周期混亂,onClose() 不調用若用了 permanent: true 或未被銷毀,onClose() 不會觸發
控制器數據被提前銷毀避免將共享 Controller 用 lazyPut 且未設置 fenix: true
同步多個頁面 UI 更新使用 .obsObx()GetBuilder() 保證響應式更新
修改數據后部分頁面未更新檢查是否遺漏 .obs 或未正確包裹 UI

? 推薦 Controller 注冊方式

場景推薦注入方式
多頁面共享狀態(如用戶信息)Get.put(UserController(), permanent: true)
頁面獨立狀態管理Get.lazyPut(() => PageController())
動態創建多個實例Get.put(..., tag: 'xxx')

💡 示例:用戶控制器在多個頁面共享

// controller
class UserController extends GetxController {var username = ''.obs;
}// 頁面 A
Obx(() => Text('用戶名:${Get.find<UserController>().username.value}'))// 頁面 B
Get.find<UserController>().username.value = '新名字';

此時 A、B 頁面都會自動同步更新。


好的,我來為你的文檔添加一節完整示例,展示:

? 多個頁面共享一個 Controller
? 頁面間通過 GetX 路由跳轉
? 動畫過渡
? 控制器數據聯動更新


🧪 示例:多個頁面共享同一個 Controller + 路由動畫 + 數據聯動

🎯 場景說明

  • 有兩個頁面:ProfilePageEditNamePage
  • 它們共用 UserController,編輯名字后自動同步到另一個頁面
  • 路由跳轉帶有動畫

🧬 Step 1:UserController

class UserController extends GetxController {var username = '張三'.obs;
}

🖼? Step 2:ProfilePage(顯示用戶名)

class ProfilePage extends StatelessWidget {final userController = Get.find<UserController>();Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('用戶信息')),body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: [Obx(() => Text('用戶名:${userController.username.value}',style: TextStyle(fontSize: 24))),SizedBox(height: 20),ElevatedButton(onPressed: () {Get.toNamed('/edit');},child: Text('編輯用戶名'),),],),),);}
}

?? Step 3:EditNamePage(修改用戶名)

class EditNamePage extends StatelessWidget {final userController = Get.find<UserController>();final TextEditingController textController = TextEditingController();Widget build(BuildContext context) {textController.text = userController.username.value;return Scaffold(appBar: AppBar(title: Text('編輯用戶名')),body: Padding(padding: const EdgeInsets.all(16.0),child: Column(children: [TextField(controller: textController),SizedBox(height: 20),ElevatedButton(onPressed: () {userController.username.value = textController.text;Get.back(); // 返回上一頁},child: Text('保存'),)],),),);}
}

🧭 Step 4:路由配置(含動畫)

GetMaterialApp(initialRoute: '/profile',getPages: [GetPage(name: '/profile',page: () => ProfilePage(),),GetPage(name: '/edit',page: () => EditNamePage(),transition: Transition.downToUp,transitionDuration: Duration(milliseconds: 400),),],initialBinding: BindingsBuilder(() {Get.put(UserController(), permanent: true); // 全局共享}),
);

? 效果預期

  1. 打開 ProfilePage 顯示用戶名
  2. 點擊“編輯用戶名”進入 EditNamePage(帶動畫)
  3. 修改并保存后返回 ProfilePage,頁面立即更新

如何使用多個 Controller 實例,并包括創建、管理、銷毀、避免沖突等實踐。


🧬 多個 Controller 實例的管理方式

在某些場景中(如多個 tab、多個子組件、多個動態生成的 item),你可能需要為同一個 Controller 創建多個獨立實例。GetX 提供兩種方式來實現:


? 方式一:使用 tag 區分多個實例

GetX 的 tag 就像“命名空間”,可以讓你為同一個 Controller 類型創建多個獨立的實例。

🔧 注冊多個實例
Get.put(OrderController(), tag: 'order1');
Get.put(OrderController(), tag: 'order2');
📦 獲取實例
final order1 = Get.find<OrderController>(tag: 'order1');
final order2 = Get.find<OrderController>(tag: 'order2');

? 方式二:使用 Get.create()(每次都新建)

Get.create() 不會緩存 Controller,每次調用都創建一個新實例,適合臨時組件或彈窗用。

Get.create(() => TempController()); // 每次調用都是新的

使用時:

final tempController = Get.put(TempController());

?? 注意事項與最佳實踐

問題說明與建議
? 用 Get.put() 多次注冊無 tag會拋異常或覆蓋原實例,建議加 tag 區分
? 控制器按需使用后釋放用完后手動 Get.delete<OrderController>(tag: 'xxx')
? tag 命名唯一且可追蹤建議統一格式,如 "chat_user_$id""product_$index"
? 使用 BindingsBuilder 創建支持多 tag 注入,保持結構清晰

🌟 示例:多個訂單頁面使用不同的控制器實例

📦 Controller:

class OrderController extends GetxController {final String orderId;OrderController(this.orderId);var status = ''.obs;void onInit() {super.onInit();status.value = '正在加載訂單 $orderId';}
}

🖼? 頁面中使用:

class OrderPage extends StatelessWidget {final String orderId;OrderPage(this.orderId);Widget build(BuildContext context) {final controller = Get.put(OrderController(orderId), tag: orderId);return Scaffold(appBar: AppBar(title: Text('訂單 $orderId')),body: Obx(() => Text(controller.status.value)),);}
}

🧭 路由跳轉:

Get.to(() => OrderPage('order_001'));
Get.to(() => OrderPage('order_002'));

每個頁面擁有各自的 Controller 實例,互不干擾。


? 總結:使用多個 Controller 實例的建議

場景推薦方法
同一類型 Controller 多實例使用 tag
臨時或短生命周期 Controller使用 Get.create()
頁面綁定多個 Controller 實例BindingsBuilder() + tag
實例用完釋放手動 Get.delete<T>(tag: ...)

下面是「帶多個 Tab,每個 Tab 使用獨立 Controller 實例」的完整示例。


🧪 示例:Tab 頁面中每個 Tab 使用獨立的 Controller 實例

🎯 場景說明

  • 頁面有多個 Tab,每個 Tab 顯示獨立數據(如:推薦、熱門、最新)
  • 每個 Tab 用一個獨立的 TabController
  • 所有 Tab 使用相同類型的 NewsController,通過 tag 區分
  • 數據互不干擾,生命周期由頁面控制

🧬 Step 1:創建 Controller

class NewsController extends GetxController {final String category;NewsController(this.category);var articles = <String>[].obs;void onInit() {super.onInit();fetchArticles();}void fetchArticles() {// 模擬網絡加載articles.value = List.generate(5, (index) => '$category 新聞 $index');}
}

🖼? Step 2:主 Tab 頁面

class NewsTabPage extends StatelessWidget {final tabs = ['推薦', '熱門', '最新'];Widget build(BuildContext context) {return DefaultTabController(length: tabs.length,child: Scaffold(appBar: AppBar(title: Text('新聞中心'),bottom: TabBar(tabs: tabs.map((t) => Tab(text: t)).toList(),),),body: TabBarView(children: tabs.map((category) {final tag = 'news_$category';// 每個 tab 注入自己的 controller(只注入一次)if (!Get.isRegistered<NewsController>(tag: tag)) {Get.put(NewsController(category), tag: tag);}return NewsTabContent(tag: tag);}).toList(),),),);}
}

🧩 Step 3:Tab 內容組件

class NewsTabContent extends StatelessWidget {final String tag;const NewsTabContent({required this.tag});Widget build(BuildContext context) {final controller = Get.find<NewsController>(tag: tag);return Obx(() => ListView.builder(itemCount: controller.articles.length,itemBuilder: (_, i) => ListTile(title: Text(controller.articles[i]),),));}
}

🧹 Step 4:頁面銷毀時釋放 Controller(可選)

你可以在頁面 pop 時手動釋放所有 tab 的 controller:


void dispose() {for (final tab in ['推薦', '熱門', '最新']) {Get.delete<NewsController>(tag: 'news_$tab');}super.dispose();
}

或者設置 fenix: false 時自動釋放(只要不用 permanent)。


? 小結

優勢實現方式
每個 Tab 獨立邏輯與狀態Get.put(..., tag: category)
相同 Controller 類型復用結構通過 tag 實現實例區分
生命周期清晰,資源可回收頁面退出手動 Get.delete(tag: ...)

源碼地址

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/85545.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/85545.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/85545.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

深度學習:人工神經網絡基礎概念

本文目錄&#xff1a; 一、什么是神經網絡二、如何構建神經網絡三、神經網絡內部狀態值和激活值 一、什么是神經網絡 人工神經網絡&#xff08;Artificial Neural Network&#xff0c; 簡寫為ANN&#xff09;也簡稱為神經網絡&#xff08;NN&#xff09;&#xff0c;是一種模仿…

Unity2D 街機風太空射擊游戲 學習記錄 #12環射道具的引入

概述 這是一款基于Unity引擎開發的2D街機風太空射擊游戲&#xff0c;筆者并不是游戲開發人&#xff0c;作者是siki學院的涼鞋老師。 筆者只是學習項目&#xff0c;記錄學習&#xff0c;同時也想幫助他人更好的學習這個項目 作者會記錄學習這一期用到的知識&#xff0c;和一些…

網站如何啟用HTTPS訪問?本地內網部署的https網站怎么在外網打開?

在互聯網的世界里&#xff0c;數據安全已經成為了每個網站和用戶都不得不面對的問題。近期&#xff0c;網絡信息泄露事件頻發&#xff0c;讓越來越多的網站開始重視起用戶數據的安全性&#xff0c;因此啟用HTTPS訪問成為了一個熱門話題。作為一名網絡安全專家&#xff0c;我希望…

計算機網絡-----詳解網絡原理TCP/IP(上)

文章目錄 &#x1f4d5;1. UDP協議??1.1 UDP的特點??1.2 基于UDP的應用層協議 &#x1f4d5;2. TCP協議??2.1 TCP協議段格式??2.2 TCP協議特點之確認應答??2.3 TCP協議特點之超時重傳??2.4 TCP協議特點之連接管理??2.5 TCP協議特點之滑動窗口??2.6 TCP協議特點…

Lora訓練

一種大模型高效訓練方式&#xff08;PEFT&#xff09; 目標&#xff1a; 訓練有限的ΔW&#xff08;權重更新矩陣&#xff09; ΔW為低秩矩陣→ΔWAB&#xff08;其中A的大小為dr, B的大小為rk&#xff0c;且r<<min(d,k)&#xff09;→ 原本要更新的dk參數量大幅度縮減…

藍牙 5.0 新特性全解析:傳輸距離與速度提升的底層邏輯(面試寶典版)

藍牙技術自 1994 年誕生以來,已經經歷了多次重大升級。作為當前主流的無線通信標準之一,藍牙 5.0 在 2016 年發布后,憑借其顯著的性能提升成為了物聯網(IoT)、智能家居、可穿戴設備等領域的核心技術。本文將深入解析藍牙 5.0 在傳輸距離和速度上的底層技術邏輯,并結合面試…

Minio使用https自簽證書

自簽證書參考&#xff1a;window和ubuntu自簽證書_windows 自簽證書-CSDN博客 // certFilePath: 直接放在 resources 目錄下 或者可以自定實現讀取邏輯 // 讀取的是 .crt 證書文件public static OkHttpClient createTrustingOkHttpClient(String certFilePath) throws Excep…

汽車前縱梁焊接總成與沖壓件的高效自動化三維檢測方案

汽車主體結構件上存在很多安裝位&#xff0c;為保證汽車裝配時的準確性&#xff0c;主體結構件需要進行全方位的尺寸和孔位置精度檢測&#xff0c;以確保裝配線的主體結構件質量合格。 前縱梁焊接總成是車身框架的核心承載部件&#xff0c;焊接總成由多片鈑金沖壓件焊接組成&a…

F接口基礎.go

前言&#xff1a;接口是一組方法的集合&#xff0c;它定義了一個類型應該具備哪些行為&#xff0c;但不關心具體怎么實現這些行為。一個類型只要實現了接口中定義的所有方法&#xff0c;那么它就實現了這個接口。這種實現是隱式的&#xff0c;不需要顯式聲明。 目錄 接口的定…

cartographer官方指導文件說明---第3章 cartographer前端算法流程介紹

cartographer官方指導文件說明 第3章 cartographer前端算法流程介紹 3.1 Scan Match掃描匹配 掃描匹配&#xff08;Scan Matching&#xff09;是 Cartographer 中實現局部SLAM的核心技術&#xff0c;它通過優化算法將當前激光掃描數據對齊到子圖地圖中。下面從計算過程、數學…

汽車整車廠如何用數字孿生系統打造“透明車間”

隨著工業4.0時代的發展&#xff0c;數字孿生技術已成為現代制造業的重要利器。特別是在汽車整車廠&#xff0c;通過數字孿生系統的應用&#xff0c;能夠有效打造一個“透明車間”&#xff0c;實現生產過程的全面可視化與實時監控&#xff0c;提高生產效率&#xff0c;降低成本&…

openKylin適配RISC-V高性能服務器芯片,攜手睿思芯科共拓智算新藍海

3月31日&#xff0c;睿思芯科&#xff08;深圳&#xff09;技術有限公司&#xff08;簡稱“睿思芯科”&#xff09;2025春季新品發布會在深圳前海國際會議中心盛大舉行&#xff0c;作為RISC-V領域的年度盛事&#xff0c;此次發布會吸引了眾多業內目光。此次發布會上&#xff0c…

【已解決】lxml.etree.ParserError: Document is empty

本專欄解決日常生活工作中非快速找到解決方案的問題。 問題背景 在爬取某網站時&#xff0c;使用開源框架報錯&#xff1a;lxml.etree.ParserError: Document is empty 解決方案 1、多個搜索引擎中查找&#xff0c;建議都是對lxml的python源碼進行修改&#xff0c;不好用。…

mac電腦調試iphone真機safari網頁

mac電腦調試iphone真機safari網頁 start 本文主要是記錄一下如何調試蘋果手機上的safari的網頁 方法 1.蘋果手機打開 web檢查器 操作步驟&#xff1a; 打開設置搜索safari最底部“高級”開啟“網頁檢查器” 2.mac電腦打開safari 操作步驟&#xff1a; 先用數據線連接手機和…

opencv依據圖像類型讀取圖像像素點

Mat數據類型和通道對應的type()&#xff1a; 庫類型C1C2C3C4CV_8U081624CV_8S191725CV_16U2101826CV_16S3111927CV_32S4122028CV_32F5132129CV_64F6142230 通過c程序查看類型并讀取圖像像素點&#xff1a; switch (im->type()){case 0:std::cout << "at (&quo…

軟件架構的發展歷程——從早期的單體架構到如今的云原生與智能架構

軟件架構的發展歷程是技術演進與業務需求相互驅動的結果&#xff0c;從早期的單體架構到如今的云原生與智能架構&#xff0c;每一步都在突破系統的可擴展性、靈活性和效率邊界。以下是其核心發展脈絡及未來趨勢的全景解析&#xff1a; 一、發展歷程&#xff1a;從單體到智能的…

Oracle 基礎語句大全:從數據定義到復雜查詢

一、DDL&#xff08;數據定義語言&#xff09;&#xff1a;定義數據庫結構 1. 創建表&#xff08;CREATE TABLE&#xff09; -- 語法格式 CREATE TABLE [schema.]table_name (column1 datatype [CONSTRAINT constraint1],column2 datatype [DEFAULT default_value],-- 表級約…

【學習筆記】鎖+死鎖+gdb調試死鎖

【學習筆記】鎖死鎖gdb調試死鎖 一、互斥鎖&#xff08;std::mutex&#xff09; 最基本的鎖類型&#xff0c;提供排他性訪問&#xff0c;同一時間僅允許一個線程持有鎖。 #include <iostream> #include <mutex> #include <thread>std::mutex mtx; // 全局…

Flutter中將bytes轉換成XFile對象上傳

在Flutter中將字節數據(bytes)轉換為XFile對象并上傳可以通過以下步驟實現&#xff1a; 1.字節數據轉臨時文件 首先需要將字節數據寫入臨時文件&#xff0c;可以使用dart的File類實現&#xff1a; final tempDir await getTemporaryDirectory(); final file File(${tempDi…

餅圖:數據可視化的“切蛋糕”藝術

餅圖&#xff0c;作為數據可視化家族中最經典、最易識別的成員之一&#xff0c;其核心功能如同其名——像切分蛋糕一樣&#xff0c;直觀展示一個整體&#xff08;100%&#xff09;被劃分為若干組成部分的比例關系。 往期文章推薦: 20.用Mermaid代碼畫ER圖&#xff1a;AI時代的…