# GetX 导航与状态管理详解

18 阅读7分钟

GetX 导航与状态管理详解

1. GetX 简介

GetX 是一个轻量级且强大的 Flutter 插件,集成了状态管理、依赖注入、路由管理等功能。它具有高性能、低耦合、易用性强的特点,是 Flutter 开发中的重要工具。

2. 状态管理

2.1 响应式状态管理

GetX 提供了多种响应式变量类型:

  • RxString: 字符串响应式变量
  • RxInt: 整数响应式变量
  • RxDouble: 双精度浮点数响应式变量
  • RxBool: 布尔响应式变量
  • RxList<T>: 响应式列表
  • RxMap<K, V>: 响应式映射
  • RxSet<T>: 响应式集合
  • Rx<T>: 通用响应式变量
final RxString name = "John".obs;
final RxInt count = 0.obs;
final RxList<String> items = <String>[].obs;
final Rx<Book?> selectedBook = Rx<Book?>(null);

2.2 响应式变量的使用

响应式变量的值通过 .value 获取和设置:

// 获取值
String currentName = name.value;

// 设置值
name.value = "Jane";

// 监听变化
name.listen((value) => print("New value: $value"));

// 条件更新
count.value++; // 自动通知监听者

2.3 Obx 组件

Obx 组件用于监听响应式变量的变化并自动重建 UI:

Obx(() => Text(controller.name.value))

// 或者使用 GetX
GetX<MyController>(
  builder: (controller) => Text(controller.name.value)
)

// 或者使用 GetBuilder
GetBuilder<MyController>(
  builder: (controller) => Text(controller.name.value)
)

2.4 监听器

  • ever: 每次值变化时执行
  • once: 值第一次变化时执行
  • debounce: 防抖监听
  • delay: 延迟监听
// 每次变化都执行
ever(count, (value) => print("Count changed to $value"));

// 只执行一次
once(name, (value) => print("Name initially: $value"));

// 防抖
debounce(count, (value) => print("Debounced: $value"), time: Duration(milliseconds: 300));

3. 依赖注入

3.1 Get.put()

将控制器或服务注册到 GetX 容器中:

Get.put(MyController());
Get.put(MyService(), permanent: true); // 永久保存
Get.putAsync(() => MyController().init()); // 异步初始化

3.2 Get.lazyPut()

延迟注入,只有在需要时才创建实例:

Get.lazyPut<MyController>(() => MyController());
Get.lazyPut<MyService>(() => MyService(), fenix: true); // 永远存活

3.3 Get.find()

从 GetX 容器中获取已注册的实例:

final controller = Get.find<MyController>();
final service = Get.find<MyService>();

3.4 GetxService

用于创建单例服务:

class MyService extends GetxService {
  static MyService get to => Get.find();
  
  Future<MyService> init() async {
    // 初始化代码
    return this;
  }
}

3.5 Bindings

用于在路由级别管理依赖关系:

class HomeBinding implements Bindings {
  @override
  void dependencies() {
    Get.lazyPut(() => HomeController());
    Get.lazyPut(() => UserService());
  }
}

4. 路由管理

4.1 GetMaterialApp

替代 MaterialApp,启用 GetX 路由功能:

return GetMaterialApp(
  title: 'My App',
  initialRoute: '/',
  getPages: [
    GetPage(name: '/', page: () => HomeScreen()),
    GetPage(name: '/details', page: () => DetailsScreen()),
  ],
);

4.2 基本路由操作

  • Get.to(): 跳转到新页面(压入路由栈)
  • Get.off(): 替换当前页面(移除当前页面并跳转)
  • Get.offAll(): 清空路由栈并跳转到新页面
  • Get.back(): 返回上一页(弹出路由栈)
// 跳转到新页面
Get.to(SecondScreen());

// 带参数跳转
Get.to(SecondScreen(), arguments: {'id': 123});

// 替换当前页面
Get.off(ThirdScreen());

// 清空路由栈并跳转
Get.offAll(HomeScreen());

// 返回上一页
Get.back();

// 返回并带回值
Get.back(result: 'Success');

// 返回并替换所有路由
Get.until((route) => route.isFirst);

4.3 命名路由

// 定义命名路由
static final pages = [
  GetPage(
    name: '/home',
    page: () => HomeScreen(),
    binding: HomeBinding(), // 依赖注入
    transition: Transition.fade, // 转场动画
    middlewares: [AuthMiddleware()], // 中间件
  ),
  GetPage(
    name: '/product/:id', // 路由参数
    page: () => ProductScreen(),
    binding: ProductBinding(),
  ),
];

// 跳转到命名路由
Get.toNamed('/product/123');
Get.toNamed('/product', arguments: {'id': 123});

4.4 参数传递

  • 通过 arguments 传递复杂对象
  • 通过 parameters 传递简单参数
  • 通过路由参数获取 Get.parameters
// 传递参数
Get.toNamed('/product', arguments: {
  'productId': 123,
  'productName': 'iPhone',
});

// 接收参数
class ProductController extends GetxController {
  @override
  void onInit() {
    super.onInit();
    var args = Get.arguments; // {'productId': 123, 'productName': 'iPhone'}
    var params = Get.parameters; // {'id': '123'} for '/product/:id'
  }
}

4.5 页面转换动画

GetX 提供了多种内置转场动画:

  • Transition.fade: 淡入淡出
  • Transition.rightToLeft: 从右到左滑动
  • Transition.leftToRight: 从左到右滑动
  • Transition.upToDown: 从上到下滑动
  • Transition.downToUp: 从下到上滑动
  • Transition.scale: 缩放效果
  • Transition.circularReveal: 圆形展开效果
  • Transition.complex: 复杂动画组合
GetPage(
  name: '/home',
  page: () => HomeScreen(),
  transition: Transition.fadeIn,
  transitionDuration: Duration(milliseconds: 500),
)

4.6 路由中间件

class AuthMiddleware extends GetMiddleware {
  @override
  int? get priority => 1;

  @override
  RouteSettings? redirect(String? route) {
    if (!AuthService.to.isAuthenticated) {
      return const RouteSettings(name: '/login');
    }
    return null;
  }
}

4.7 路由守卫

GetPage(
  name: '/profile',
  page: () => ProfileScreen(),
  middlewares: [AuthMiddleware()],
)

5. 控制器生命周期

5.1 生命周期方法

  • onInit(): 控制器初始化时调用
  • onReady(): 控制器准备好时调用(Widget 构建后)
  • onClose(): 控制器关闭时调用
class MyController extends GetxController {
  @override
  void onInit() {
    super.onInit();
    // 初始化响应式变量
    // 设置监听器
    // 订阅服务
  }

  @override
  void onReady() {
    super.onReady();
    // Widget 构建完成后执行
    // 执行需要等待 Widget 构建的操作
  }

  @override
  void onClose() {
    // 清理资源
    // 取消订阅
    // 关闭 Stream
    super.onClose();
  }
}

5.2 控制器管理

class MyController extends GetxController {
  final count = 0.obs;
  
  void increment() => count.value++;
  
  @override
  void onClose() {
    // 重要:清理资源
    count.close(); // 关闭响应式变量
    super.onClose();
  }
}

6. UI 组件

6.1 GetView

简化控制器访问的 StatefulWidget:

class MyWidget extends GetView<MyController> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Text(controller.count.value.toString()),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: controller.increment,
        child: Icon(Icons.add),
      ),
    );
  }
}

6.2 GetWidget

类似 GetView,但适用于 StatelessWidget:

class MyWidget extends GetWidget<MyController> {
  @override
  Widget build(BuildContext context) {
    return Text(Get.find<MyController>().myProperty);
  }
}

6.3 GetBuilder

手动控制重建的组件:

GetBuilder<MyController>(
  builder: (controller) {
    return Text(controller.count.toString());
  },
)

6.4 GetX

专门用于响应式变量的组件:

GetX<MyController>(
  builder: (controller) {
    return Text(controller.count.value.toString());
  },
)

7. 对话框和提示

7.1 Snackbar

Get.snackbar(
  "Title",           // 标题
  "Message",         // 消息
  snackPosition: SnackPosition.BOTTOM, // 位置
  duration: Duration(seconds: 3),      // 持续时间
  backgroundColor: Colors.blue,        // 背景色
  colorText: Colors.white,             // 文字颜色
  icon: Icon(Icons.check),             // 图标
  mainButton: TextButton(              // 主按钮
    onPressed: () => Get.back(),
    child: Text("Undo"),
  ),
);

7.2 对话框

Get.defaultDialog(
  title: "Confirm",
  middleText: "Are you sure?",
  textConfirm: "Yes",
  textCancel: "No",
  onConfirm: () {
    Get.back(result: true);
  },
  onCancel: () {
    Get.back(result: false);
  },
);

// 自定义对话框
Get.dialog(
  AlertDialog(
    title: Text("Custom Dialog"),
    content: Text("This is a custom dialog"),
    actions: [
      TextButton(
        onPressed: () => Get.back(),
        child: Text("OK"),
      ),
    ],
  ),
);

7.3 底部表单

Get.bottomSheet(
  Container(
    padding: EdgeInsets.all(16),
    decoration: BoxDecoration(
      color: Colors.white,
      borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
    ),
    child: Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        Text("Bottom Sheet Content"),
        ElevatedButton(
          onPressed: () => Get.back(),
          child: Text("Close"),
        ),
      ],
    ),
  ),
);

8. 主题管理

8.1 动态主题切换

// 切换主题
Get.changeTheme(ThemeData.dark());
Get.changeThemeMode(ThemeMode.dark);

// 在控制器中切换主题
class ThemeController extends GetxController {
  final RxBool isDarkMode = false.obs;
  
  void toggleTheme() {
    isDarkMode.value = !isDarkMode.value;
    Get.changeThemeMode(isDarkMode.value ? ThemeMode.dark : ThemeMode.light);
  }
}

8.2 主题模式

  • ThemeMode.light: 浅色模式
  • ThemeMode.dark: 深色模式
  • ThemeMode.system: 系统模式
GetMaterialApp(
  theme: ThemeData.light(),
  darkTheme: ThemeData.dark(),
  themeMode: ThemeMode.system,
)

9. 平台检测

if (GetPlatform.isWeb) {
  // Web 特定代码
}

if (GetPlatform.isMobile) {
  // 移动端特定代码
}

if (GetPlatform.isAndroid) {
  // Android 特定代码
}

if (GetPlatform.isIOS) {
  // iOS 特定代码
}

if (GetPlatform.isDesktop) {
  // 桌面端特定代码
}

if (GetPlatform.isWindows) {
  // Windows 特定代码
}

if (GetPlatform.isLinux) {
  // Linux 特定代码
}

if (GetPlatform.isMacOS) {
  // macOS 特定代码
}

10. 工具函数

10.1 GetUtils

提供各种实用工具函数:

// 验证函数
GetUtils.isEmail("test@example.com");           // 验证邮箱
GetUtils.isValidUrl("https://example.com");     // 验证URL
GetUtils.isNumeric("123");                      // 验证数字
GetUtils.isPhoneNumber("+1234567890");         // 验证电话号码

// 防抖和节流
GetUtils.debounce(() => print("Debounced"), time: Duration(milliseconds: 300));
GetUtils.throttle(() => print("Throttled"), time: Duration(milliseconds: 500));

// 其他工具
GetUtils.capitalize("hello");                   // 首字母大写
GetUtils.toTitleCase("hello world");            // 标题格式
GetUtils.random(1, 10);                         // 随机数

10.2 异步操作

Get.isSnackbarOpen;  // 检查 Snackbar 是否打开
Get.focusScope;      // 获取焦点范围
Get.rawPath;         // 获取原始路径
Get.routing;         // 获取当前路由信息

11. 数据持久化

11.1 GetStorage

轻量级本地存储解决方案:

import 'package:get_storage/get_storage.dart';

class StorageService extends GetxService {
  late final box = GetStorage();

  Future<void> init() async {
    await GetStorage.init(); // 初始化
  }

  // 保存数据
  Future<void> write(String key, dynamic value) async {
    await box.write(key, value);
  }

  // 读取数据
  T? read<T>(String key, [T? defaultValue]) {
    return box.read<T>(key) ?? defaultValue;
  }

  // 删除数据
  Future<void> remove(String key) async {
    await box.remove(key);
  }

  // 清空所有数据
  Future<void> erase() async {
    await box.erase();
  }
}

12. 导航深度控制

12.1 路由栈操作

// 获取当前路由信息
Get.currentRoute;

// 检查路由栈长度
Get.routing.isBackButtonActive;

// 直到某个条件为止
Get.until((route) => Get.currentRoute == '/home');

// 替换当前路由
Get.offAllNamed('/new-route');

// 按条件移除路由
Get.removeRoute(predicate: (route) => route.settings.name == '/temp');

12.2 嵌套路由

// 嵌套导航示例
class MainTabPage extends GetView<AppController> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: IndexedStack(
        index: controller.currentIndex.value,
        children: const [
          HomePage(),
          ProfilePage(),
          SettingsPage(),
        ],
      ),
      bottomNavigationBar: Obx(
        () => BottomNavigationBar(
          currentIndex: controller.currentIndex.value,
          onTap: controller.changeTabIndex,
          items: const [
            BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
            BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'),
            BottomNavigationBarItem(icon: Icon(Icons.settings), label: 'Settings'),
          ],
        ),
      ),
    );
  }
}

13. 最佳实践

13.1 组织架构

  • 将控制器、服务、模型分离
  • 使用 bindings 管理依赖关系
  • 合理使用响应式变量
  • 遵循单一职责原则

13.2 性能优化

  • 避免不必要的重建
  • 合理使用懒加载
  • 及时释放资源
  • 使用合适的监听器

13.3 代码组织

// 控制器绑定示例
class HomeBinding implements Bindings {
  @override
  void dependencies() {
    Get.lazyPut(() => HomeController());
    Get.lazyPut(() => UserService());
    Get.lazyPut(() => ProductService());
  }
}

// 在路由中使用
GetPage(
  name: '/home',
  page: () => HomeScreen(),
  binding: HomeBinding(),
)

14. 常见问题

14.1 内存泄漏

确保在 onClose() 中清理资源:

@override
void onClose() {
  // 关闭所有响应式变量
  count.close();
  list.close();
  
  // 取消所有订阅
  subscription?.cancel();
  
  // 关闭所有控制器
  controller.close();
  
  super.onClose();
}

14.2 路由错误

使用 GetPage 配置路由以避免错误:

GetPage(
  name: '/home',
  page: () => HomeScreen(),
  binding: HomeBinding(),
  middlewares: [AuthMiddleware()],
  transition: Transition.cupertino,
)

14.3 状态同步问题

使用响应式变量确保状态同步:

// 正确的方式
final count = 0.obs;

// 错误的方式
var count = 0; // 不会触发重建

15. 高级功能

15.1 条件路由

Get.toNamed(
  condition ? '/dashboard' : '/onboarding',
  arguments: data,
);

15.2 动态路由参数

// 动态构建路由
String buildRoute(String base, Map<String, String> params) {
  String route = base;
  params.forEach((key, value) {
    route = route.replaceAll(':$key', value);
  });
  return route;
}

15.3 路由拦截器

class RouteInterceptor extends GetMiddleware {
  @override
  RouteSettings? redirect(String? route) {
    // 根据条件重定向
    if (needsAuth(route) && !isAuthenticated()) {
      return const RouteSettings(name: '/login');
    }
    return null;
  }
}

16. 总结

GetX 是一个功能强大且易于使用的 Flutter 状态管理和路由管理解决方案。通过响应式编程、依赖注入和简化的路由管理,它可以显著提高开发效率和代码质量。

GetX 的核心优势包括:

  1. 简单易用: API 设计简洁,学习成本低
  2. 性能优秀: 响应式更新机制,只重建需要的部分
  3. 功能完整: 集成状态管理、依赖注入、路由管理
  4. 内存友好: 自动管理生命周期,防止内存泄漏
  5. 类型安全: 提供良好的类型检查支持

掌握 GetX 的各项功能有助于构建高效、可维护的 Flutter 应用。通过合理使用响应式变量、依赖注入和路由管理,可以构建出用户体验良好、性能优异的移动应用。