Flutter GetX 状态管理完全指南

引言

GetX 是 Flutter 社区中最流行的状态管理库之一,它提供了简单、高效且功能丰富的状态管理解决方案。本文将深入探讨 GetX 的各种用法和高级技巧。

基础概念回顾

GetX 的核心功能

  • 状态管理: 响应式状态管理
  • 路由管理: 无需上下文的导航
  • 依赖注入: 简单的依赖管理
  • 国际化: 多语言支持
  • 主题管理: 动态主题切换

核心概念

  • GetX Controller: 管理状态的控制器
  • Obx: 响应式观察者组件
  • Get.put(): 注入依赖
  • Get.find(): 获取依赖

高级技巧一:基础状态管理

创建 Controller

import 'package:get/get.dart';

class CounterController extends GetxController {
  final count = 0.obs;
  
  void increment() => count.value++;
  
  void decrement() => count.value--;
  
  void reset() => count.value = 0;
}

使用 Controller

class CounterScreen extends StatelessWidget {
  final controller = Get.put(CounterController());
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Counter')),
      body: Center(
        child: Column(
          children: [
            Obx(() => Text('Count: ${controller.count.value}')),
            ElevatedButton(
              onPressed: controller.increment,
              child: const Text('Increment'),
            ),
          ],
        ),
      ),
    );
  }
}

响应式变量

class UserController extends GetxController {
  final user = User().obs;
  
  void updateUser(User newUser) {
    user.value = newUser;
  }
  
  void updateUserName(String name) {
    user.update((user) {
      user?.name = name;
    });
  }
}

高级技巧二:路由管理

基本导航

// 导航到新页面
Get.to(NextScreen());

// 导航并返回数据
final result = await Get.to(NextScreen());

// 弹出当前页面
Get.back();

// 弹出并返回数据
Get.back(result: 'result');

// 清除所有页面并导航
Get.offAll(HomeScreen());

命名路由

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

// 使用命名路由
Get.toNamed('/detail');

// 带参数的命名路由
Get.toNamed('/detail', arguments: {'id': 1});

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

自定义过渡动画

GetPage(
  name: '/detail',
  page: () => DetailScreen(),
  transition: Transition.fade,
  duration: const Duration(milliseconds: 500),
);

高级技巧三:依赖注入

注入依赖

void main() {
  Get.put(ApiService());
  Get.put(UserController());
  runApp(const MyApp());
}

// 在任意地方获取
final apiService = Get.find<ApiService>();
final userController = Get.find<UserController>();

懒加载

Get.lazyPut(() => ApiService());
Get.lazyPut(() => UserController());

单例模式

Get.put<ApiService>(ApiService(), permanent: true);

高级技巧四:国际化

配置国际化

void main() {
  runApp(GetMaterialApp(
    translations: Messages(),
    locale: const Locale('zh', 'CN'),
    fallbackLocale: const Locale('en', 'US'),
  ));
}

class Messages extends Translations {
  @override
  Map<String, Map<String, String>> get keys => {
        'en_US': {
          'hello': 'Hello',
          'welcome': 'Welcome',
        },
        'zh_CN': {
          'hello': '你好',
          'welcome': '欢迎',
        },
      };
}

使用国际化

Text('hello'.tr);
Text('welcome'.tr);

实战案例:购物车管理

class CartController extends GetxController {
  final items = <CartItem>[].obs;
  
  int get totalItems => items.length;
  
  double get totalPrice => items.fold(0, (sum, item) => sum + item.price);
  
  void addItem(CartItem item) {
    items.add(item);
  }
  
  void removeItem(CartItem item) {
    items.remove(item);
  }
  
  void clearCart() {
    items.clear();
  }
}

class CartItem {
  final String id;
  final String name;
  final double price;
  
  CartItem({required this.id, required this.name, required this.price});
}

class CartScreen extends StatelessWidget {
  final controller = Get.find<CartController>();
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Cart')),
      body: Obx(() {
        if (controller.items.isEmpty) {
          return const Center(child: Text('Cart is empty'));
        }
        
        return ListView.builder(
          itemCount: controller.items.length,
          itemBuilder: (context, index) {
            final item = controller.items[index];
            return ListTile(
              title: Text(item.name),
              subtitle: Text('\$${item.price}'),
              trailing: IconButton(
                icon: const Icon(Icons.remove),
                onPressed: () => controller.removeItem(item),
              ),
            );
          },
        );
      }),
      bottomNavigationBar: Obx(() => Container(
        padding: const EdgeInsets.all(16),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Text('Total: \$${controller.totalPrice}'),
            ElevatedButton(
              onPressed: controller.totalItems > 0 ? controller.clearCart : null,
              child: const Text('Clear Cart'),
            ),
          ],
        ),
      )),
    );
  }
}

实战案例:主题切换

class ThemeController extends GetxController {
  final isDarkMode = false.obs;
  
  ThemeData get theme => isDarkMode.value ? ThemeData.dark() : ThemeData.light();
  
  void toggleTheme() {
    isDarkMode.value = !isDarkMode.value;
  }
}

class ThemeToggle extends StatelessWidget {
  final controller = Get.find<ThemeController>();
  
  @override
  Widget build(BuildContext context) {
    return Obx(() => Switch(
      value: controller.isDarkMode.value,
      onChanged: (_) => controller.toggleTheme(),
    ));
  }
}

void main() {
  Get.put(ThemeController());
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  
  @override
  Widget build(BuildContext context) {
    final controller = Get.find<ThemeController>();
    
    return Obx(() => GetMaterialApp(
      theme: controller.theme,
      home: const HomePage(),
    ));
  }
}

实战案例:用户认证

class AuthController extends GetxController {
  final user = Rxn<User>();
  
  bool get isLoggedIn => user.value != null;
  
  Future<void> login(String email, String password) async {
    user.value = await authService.login(email, password);
  }
  
  void logout() {
    user.value = null;
  }
}

class AuthGuard extends StatelessWidget {
  final Widget child;
  
  const AuthGuard({super.key, required this.child});
  
  @override
  Widget build(BuildContext context) {
    final controller = Get.find<AuthController>();
    
    return Obx(() {
      if (!controller.isLoggedIn) {
        return const LoginPage();
      }
      return child;
    });
  }
}

// 使用
void main() {
  Get.put(AuthController());
  runApp(GetMaterialApp(
    home: AuthGuard(child: const HomePage()),
  ));
}

常见问题与解决方案

Q1:状态不更新?

A:确保使用 .obsObx()

// 正确
final count = 0.obs;
Obx(() => Text('${count.value}'));

// 错误
int count = 0; // 没有使用 .obs

Q2:Get.find() 找不到?

A:确保先注入依赖:

// 在main中注入
Get.put(MyController());

// 然后在组件中使用
final controller = Get.find<MyController>();

Q3:路由导航没有上下文?

A:使用 GetMaterialApp 替代 MaterialApp

// 正确
GetMaterialApp(home: HomePage());

// 错误
MaterialApp(home: HomePage()); // 无法使用 Get.to()

最佳实践

1. 分离业务逻辑

class UserRepository {
  Future<User> getUser(int id) => api.getUser(id);
}

class UserController extends GetxController {
  final repository = Get.find<UserRepository>();
  final user = Rxn<User>();
  
  Future<void> loadUser(int id) async {
    user.value = await repository.getUser(id);
  }
}

2. 使用 Rxn 处理空值

// 推荐
final user = Rxn<User>();

// 不推荐
final user = User().obs; // 需要处理空值

3. 及时释放资源

class MyController extends GetxController {
  late StreamSubscription subscription;
  
  @override
  void onInit() {
    super.onInit();
    subscription = stream.listen((data) {
      // 处理数据
    });
  }
  
  @override
  void onClose() {
    subscription.cancel();
    super.onClose();
  }
}

总结

GetX 是一个功能强大的状态管理库。通过本文的学习,你应该能够:

  1. 创建和使用 GetX Controller
  2. 使用响应式状态管理
  3. 实现路由导航
  4. 处理依赖注入
  5. 实现国际化和主题切换

掌握这些技巧,能够帮助你创建更加高效和可维护的应用程序。

更多推荐