本文每个章节用一个小场景串起 GetX 的关键能力:状态管理、导航与中间件、依赖注入与生命周期、响应式 Workers、网络与存储、主题与国际化、工程化目录与测试。代码可直接复制使用。
目标读者与成果#
- 初、中级工程师,追求“能落地、好维护”
- 看完能独立搭建一个基于 GetX 的中小型应用骨架,并掌握常见坑的规避方式
0. 快速开始:安装与最小可运行示例#
- 安装依赖
flutter pub add get
- 最小示例(状态 + 路由)
import 'package:flutter/material.dart';import 'package:get/get.dart';
void main() => runApp(const MyApp());
class CounterController extends GetxController { final count = 0.obs; void inc() => count.value++;}
class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return GetMaterialApp( title: 'GetX Demo', home: const HomePage(), getPages: [ GetPage(name: '/', page: () => const HomePage()), GetPage(name: '/detail', page: () => const DetailPage()), ], ); }}
class HomePage extends StatelessWidget { const HomePage({super.key}); @override Widget build(BuildContext context) { final c = Get.put(CounterController()); return Scaffold( appBar: AppBar(title: const Text('Home')), body: Center( child: Obx(() => Text('count: ${c.count.value}')), ), floatingActionButton: FloatingActionButton( onPressed: () => c.inc(), child: const Icon(Icons.add), ), bottomNavigationBar: ElevatedButton( onPressed: () => Get.toNamed('/detail'), child: const Text('Go Detail'), ), ); }}
class DetailPage extends StatelessWidget { const DetailPage({super.key}); @override Widget build(BuildContext context) { final c = Get.find<CounterController>(); return Scaffold( appBar: AppBar(title: const Text('Detail')), body: Center(child: Obx(() => Text('count: ${c.count}'))), ); }}
[!tip] 首次体验
GetMaterialApp
接管导航与上下文能力obs
+Obx
实现细粒度重建Get.put
注入控制器,Get.find
在任意处获取
1. 用例:三种状态管理方式怎么选(入门场景)#
需求:计数器 + 用户名输入,感知不同重建粒度与写法。
- Rx + Obx(“自动”响应式,推荐日常使用)
class AController extends GetxController { final count = 0.obs; final name = ''.obs;}...Obx(() => Text('${c.count}'));
- GetX 小部件(带控制器、局部细化)
GetX<AController>( init: AController(), builder: (c) => Text('${c.count}'),)
- GetBuilder(手动
update()
,最省性能但需要自控)
class BController extends GetxController { int count = 0; void inc() { count++; update(); }}...GetBuilder<BController>( init: BController(), builder: (c) => Text('${c.count}'),)
选择建议
- 读多写少、需要自动响应:Obx/Rx(最省心)
- 写多读多、追求极致可控:GetBuilder(手动更新)
- 复杂组件内既要响应又要依赖控制器:GetX/Obx 混用
2. 用例:带登录拦截的路由导航(中级场景)#
目标:未登录禁止访问“订单页”,登录后自动跳转回来。
- 路由与中间件
class AuthService extends GetxService { final loggedIn = false.obs; Future<AuthService> init() async => this;}class AuthMiddleware extends GetMiddleware { @override RouteSettings? redirect(String? route) { final auth = Get.find<AuthService>(); if (!auth.loggedIn.value && route != '/login') { return const RouteSettings(name: '/login', arguments: {'redirect': '/orders'}); } return null; }}
GetMaterialApp( initialRoute: '/', getPages: [ GetPage(name: '/', page: () => const HomePage()), GetPage(name: '/login', page: () => const LoginPage()), GetPage(name: '/orders', page: () => const OrdersPage(), middlewares: [AuthMiddleware()]), ],);
- 登录页逻辑
class LoginController extends GetxController { Future<void> login() async { final auth = Get.find<AuthService>(); await Future.delayed(const Duration(milliseconds: 400)); auth.loggedIn.value = true; final redirect = (Get.arguments as Map?)?['redirect'] ?? '/'; Get.offAllNamed(redirect); }}
[!tip] 导航速查
Get.to/Get.off/Get.offAll
替代Navigator
- 传参:
Get.toNamed('/p', arguments: {...})
;取参:Get.arguments
或Get.parameters
(URL Query)
3. 用例:依赖注入与生命周期(中级场景)#
- 注入方式速览
Get.put(Controller()); // 立即注入Get.lazyPut(() => Controller()); // 懒注入(首次 find 时创建)Get.putAsync(() async => Controller()); // 异步创建Get.create(() => Controller()); // 每次 find 创建新实例Get.put<Service>(Service(), permanent: true); // 常驻全局
- 生命周期回调(在
GetxController
)
class DemoController extends GetxController { @override void onInit() { super.onInit(); /* 配置监听器 */ } @override void onReady() { /* 首次渲染完成 */ } @override void onClose() { /* 释放资源 */ }}
- 绑定到路由的 Bindings(推荐做法)
class OrdersBinding extends Bindings { @override void dependencies() { Get.lazyPut(() => OrdersController()); Get.put(Repository()); }}
GetPage( name: '/orders', page: () => const OrdersPage(), binding: OrdersBinding(),);
[!warning] 避免内存泄漏
- 不要在 Widget 层多次
Get.put
同一控制器 - 订阅/Timer/Controller 在
onClose
成对释放
4. 用例:搜索框的防抖与节流(Workers,实战高频)#
需求:用户输入时 300ms 防抖请求;“热门词点击”使用 1s 节流。
class SearchController extends GetxController { final keyword = ''.obs; Worker? _debounce, _throttle;
@override void onInit() { super.onInit(); _debounce = debounce<String>(keyword, (v) => _fetch(v), time: 300.milliseconds); _throttle = interval<String>(keyword, (v) => _report(v), time: 1.seconds); }
void onTapHot(String k) => keyword.value = k;
Future<void> _fetch(String kw) async { /* 请求接口 */ } void _report(String kw) { /* 上报埋点 */ }
@override void onClose() { _debounce?.dispose(); _throttle?.dispose(); super.onClose(); }}
其他 Workers:ever
(每次变化)、once
(仅一次)
5. 用例:GetConnect 封装 REST 客户端(中高级)#
- 基础封装
class ApiClient extends GetConnect { @override void onInit() { httpClient.baseUrl = 'https://api.example.com'; httpClient.addRequestModifier<dynamic>((request) { request.headers['Authorization'] = 'Bearer ${Get.find<AuthService>().token}'; return request; }); httpClient.timeout = const Duration(seconds: 10); super.onInit(); }
Future<Response<List<Post>>> listPosts() async { final res = await get('/posts', decoder: (obj) => (obj as List).map((e) => Post.fromJson(e)).toList() ); return res; }}
- 控制器使用
class PostController extends GetxController { final posts = <Post>[].obs; final loading = false.obs; final error = RxnString();
Future<void> refreshList() async { try { loading.value = true; final res = await Get.find<ApiClient>().listPosts(); if (res.isOk && res.body != null) posts.assignAll(res.body!); else error.value = res.statusText ?? 'Unknown error'; } catch (e) { error.value = e.toString(); } finally { loading.value = false; } }}
[!tip] 进阶
- 可注入拦截器(重试、统一错误码处理)、缓存层(本地合并)
6. 用例:GetStorage 做轻量持久化(中级)#
- 初始化和使用
import 'package:get_storage/get_storage.dart';
Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); await GetStorage.init(); // 可传 box 名称 runApp(const MyApp());}
class Prefs { static final box = GetStorage(); static const kOnboardingDone = 'onboarding_done';
static bool get done => box.read(kOnboardingDone) ?? false; static Future<void> setDone() => box.write(kOnboardingDone, true);}
- 用例:仅首次展示引导页
GetMaterialApp( initialRoute: Prefs.done ? '/home' : '/onboarding', getPages: [ GetPage(name: '/onboarding', page: () => const OnboardingPage()), GetPage(name: '/home', page: () => const HomePage()), ],);
7. 用例:主题切换与国际化(中级)#
- 主题切换
Get.changeTheme(Get.isDarkMode ? ThemeData.light() : ThemeData.dark());
- 国际化(Translations)
class MyTranslations extends Translations { @override Map<String, Map<String, String>> get keys => { 'en_US': {'hello': 'Hello'}, 'zh_CN': {'hello': '你好'}, };}
GetMaterialApp( translations: MyTranslations(), locale: const Locale('zh', 'CN'), fallbackLocale: const Locale('en', 'US'),);...Text('hello'.tr);Get.updateLocale(const Locale('en', 'US'));
8. 用例:错误重试 + 分页加载(中高级综合)#
class PagingController extends GetxController { final items = <Item>[].obs; var page = 1; final loading = false.obs; final hasMore = true.obs; final error = RxnString();
Future<void> loadMore() async { if (!hasMore.value || loading.value) return; loading.value = true; try { final res = await api.fetch(page); items.addAll(res.data); hasMore.value = res.hasMore; page++; } catch (e) { error.value = e.toString(); } finally { loading.value = false; } }
Future<void> retry() => loadMore();}
UI 片段
Obx(() { if (c.error.value != null) { return Column( children: [ Text('加载失败:${c.error.value}'), ElevatedButton(onPressed: c.retry, child: const Text('重试')), ], ); } return ListView.builder( itemCount: c.items.length + 1, itemBuilder: (_, i) { if (i == c.items.length) { c.loadMore(); return Center( child: c.hasMore.isTrue ? const CircularProgressIndicator() : const Text('没有更多了'), ); } return ListTile(title: Text(c.items[i].title)); }, );})
9. 工程化目录与模块化(中高级)#
建议目录