跳转到正文
YGJoon 技术博客

Flutter GetX 状态管理详解

发布于:
·1.3k 字 · 约 3 分钟·作者:
.obs 响应式变量配 Obx 自动刷新,Get.put 与 lazyPut 管依赖注入,顺带把 GetX 的路由跳转也讲了
Flutter GetX 状态管理详解文章封面
目录

简介

GetX 是一个轻量且强大的解决方案,集成了状态管理、路由管理和依赖注入。它提供了简单的语法和高性能的响应式编程。

基本概念

控制器

class CounterController extends GetxController {
  var count = 0.obs; // 创建可观察变量
  
  void increment() {
    count++;  // 自动通知UI更新
  }
  
  @override
  void onInit() {
    super.onInit();
    // 控制器初始化
  }
  
  @override
  void onClose() {
    // 控制器销毁
    super.onClose();
  }
}

响应式变量

// 基本类型
final name = 'GetX'.obs;
final count = 0.obs;
final isLogged = false.obs;

// 自定义类
final user = User().obs;

// 列表
final items = <String>[].obs;

基本用法

注册控制器

void main() {
  // 方式1: 全局注入
  Get.put(CounterController());
  
  // 方式2: 懒加载注入
  Get.lazyPut(() => CounterController());
  
  runApp(MyApp());
}

访问状态

// 方式1: Obx
Obx(() => Text('Count: ${controller.count}'));

// 方式2: GetX
GetX<CounterController>(
  builder: (controller) {
    return Text('Count: ${controller.count}');
  },
);

// 方式3: GetBuilder
GetBuilder<CounterController>(
  builder: (controller) {
    return Text('Count: ${controller.count}');
  },
);

完整示例

// 用户模型
class User {
  final String name;
  final int age;
  
  User({required this.name, required this.age});
}

// 用户控制器
class UserController extends GetxController {
  final user = User(name: 'Guest', age: 0).obs;
  final isLoading = false.obs;
  
  void updateUser(String name, int age) {
    user.update((val) {
      val?.name = name;
      val?.age = age;
    });
  }
  
  Future<void> fetchUser() async {
    isLoading.value = true;
    try {
      await Future.delayed(Duration(seconds: 2)); // 模拟网络请求
      updateUser('John Doe', 25);
    } finally {
      isLoading.value = false;
    }
  }
}

// 页面组件
class UserPage extends StatelessWidget {
  final controller = Get.put(UserController());
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('GetX Demo')),
      body: Center(
        child: Obx(() {
          if (controller.isLoading.value) {
            return CircularProgressIndicator();
          }
          
          return Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text(
                'Name: ${controller.user.value.name}',
                style: TextStyle(fontSize: 24),
              ),
              Text(
                'Age: ${controller.user.value.age}',
                style: TextStyle(fontSize: 24),
              ),
              ElevatedButton(
                onPressed: () => controller.fetchUser(),
                child: Text('Fetch User'),
              ),
            ],
          );
        }),
      ),
    );
  }
}

// 主应用
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: 'GetX Demo',
      home: UserPage(),
    );
  }
}

路由管理

基本导航

// 导航到新页面
Get.to(() => SecondPage());

// 替换当前页面
Get.off(() => SecondPage());

// 清空所有页面并导航
Get.offAll(() => HomePage());

// 带参数导航
Get.to(
  () => DetailPage(),
  arguments: {'id': 1},
);

// 获取参数
final args = Get.arguments;

命名路由

GetMaterialApp(
  getPages: [
    GetPage(name: '/', page: () => HomePage()),
    GetPage(name: '/detail', page: () => DetailPage()),
  ],
);

// 导航
Get.toNamed('/detail', arguments: {'id': 1});

依赖注入

绑定控制器

class HomeBinding implements Bindings {
  @override
  void dependencies() {
    Get.put(HomeController());
    Get.put(UserController());
  }
}

GetMaterialApp(
  initialBinding: HomeBinding(),
  // ...
);

服务注入

// 定义服务接口
abstract class ApiClient {
  Future<User> getUser();
}

// 实现服务
class ApiClientImpl implements ApiClient {
  @override
  Future<User> getUser() async {
    // 实现
  }
}

// 注入服务
Get.put<ApiClient>(ApiClientImpl());

// 使用服务
final apiClient = Get.find<ApiClient>();

最佳实践

  1. controller 按页面/功能拆:一个页面一个 Controller,只管这个页面的状态和逻辑——别搞一个 GodController 管全 App,改一行抖三抖
  2. 搜索框/表单输入用 debounce:debounce(keyword, (_) => search(), time: 500.ms);用户每敲一个字母就发一次请求,服务端会先被你打挂
  3. 依赖注入分清楚 Get.put 和 Get.lazyPut:页面级 controller 用 Get.lazyPut,随路由创建、随页面销毁;Get.put 是立即创建常驻内存,只适合全局单例(如 UserController)
  4. onClose 里关资源:Timer、StreamSubscription、TextEditingController 都要在 onClose 里释放——退出登录切账号后,旧 controller 的 timer 还在跑就会写脏数据(freight 项目真实踩坑)
  5. Worker 只做副作用:ever(loginState, (_) => 跳登录页) 可以,别在 Worker 回调里再改被监听的变量,会触发循环更新

注意事项

  1. .obs 注意粒度:列表用 <Item>[].obs 整体替换,别给几百条数据的每个 item 都套 .obs——内存和通知开销都扛不住
  2. Obx 只包”会变的那一小块”:整个页面包一个 Obx,任何变量一变整页 rebuild;正确姿势是只把数量、价格那几行 Text 包起来
  3. Get.arguments 是 dynamic:取出来先判空再转类型,as String 在 null 上直接炸(参数传递篇的坑,这里再强调一次)
  4. 响应式是”引用比较”:list.add(item) 不会触发更新,要 list.refresh() 或重新赋值——新手 90% 的”数据变了 UI 不刷”都是这个原因

实战案例

我的五个 GetX 项目(旅行类应用、某物流冷链项目 等)统一用这套结构:pages/travel_select_city/ 下面三个文件——index.dart(只做绑定)、controller.dart(逻辑+状态)、view.dart(UI)。比如选城市页,controller 里 var cityList = <City>[].obs,view 里 Obx(() => ListView(...)),数据一变 UI 自动刷。index.dart 里 Get.lazyPut(() => TravelSelectCityController()) 做依赖注入。这个三件套我雷打不动:controller 绝不碰 widget,view 绝不写逻辑。有一次图省事在 view 里直接调了接口,后来这个页面要复用到另一个入口,逻辑抽不出来,重构花了一下午。从那以后严格执行分层。

总结

GetX 提供了一套完整的应用开发解决方案,通过简单的API可以快速实现状态管理、路由管理和依赖注入。理解并掌握 GetX 的使用可以显著提高 Flutter 应用的开发效率。


分享这篇文章:


相关文章

上一篇
Flutter Provider 状态管理详解
下一篇
Flutter Row 和 Column 组件详解