Flutter GetX实战:除了计数器,我们还能用它做什么?一个Todo App的完整开发日记
Flutter GetX实战从计数器到Todo App的全栈开发手记第一次接触GetX时我也像大多数Flutter开发者一样用它写了个计数器Demo就束之高阁。直到接手公司一个中型项目时才发现这个轻量级框架的惊人潜力——它不仅能优雅处理状态管理还能串联路由、依赖注入、主题切换等完整开发链路。本文将用Todo应用这个经典场景带你体验GetX如何在实际项目中大显身手。1. 项目初始化与基础架构在pubspec.yaml中添加最新依赖截至2024年Q2dependencies: get: ^4.6.6 get_storage: ^2.1.1 # 本地持久化 flutter_localizations: # 国际化支持 sdk: flutter创建项目核心结构lib/ ├── core/ │ ├── services/ # 服务层 │ ├── bindings/ # 依赖绑定 │ └── themes/ # 主题配置 ├── modules/ # 功能模块 │ ├── home/ # 主页 │ └── task/ # 任务管理 └── routes.dart # 路由配置提示使用GetMaterialApp替代默认MaterialApp这是启用GetX路由和国际化等高级功能的前提2. 状态管理的实战演进2.1 Todo模型与控制器设计不同于计数器简单的int类型Todo需要更复杂的模型class Todo { final String id; String title; bool completed; DateTime dueDate; Todo({ required this.id, required this.title, this.completed false, DateTime? dueDate, }) : dueDate dueDate ?? DateTime.now().add(const Duration(days: 3)); }对应的控制器采用响应式编程范式class TodoController extends GetxController { final todos Todo[].obs; // 响应式列表 final storage Get.findLocalStorageService(); // 依赖注入 void addTodo(Todo newTodo) { todos.add(newTodo); _saveToStorage(); } void toggleComplete(String id) { final index todos.indexWhere((t) t.id id); todos[index].completed !todos[index].completed; todos.refresh(); // 手动触发更新 _saveToStorage(); } Futurevoid _saveToStorage() async { await storage.write(todos, todos.toJson()); } }2.2 状态绑定的三种最佳实践根据场景选择不同的UI更新方式方式适用场景性能影响代码复杂度Obx频繁更新的简单状态低低GetBuilder精确控制的重建范围中中GetXType需要类型安全的复杂组件高高实际应用示例// 方式1响应式自动更新 Obx(() Text(未完成任务: ${controller.todos.where((t) !t.completed).length})); // 方式2手动控制更新 GetBuilderTodoController( builder: (ctrl) Checkbox( value: ctrl.todos[index].completed, onChanged: (_) ctrl.toggleComplete(ctrl.todos[index].id), ), );3. 路由与导航的进阶技巧3.1 带参数的路由配置在routes.dart中定义类型安全的路由final routes [ GetPage(name: HomeRoute.path, page: () HomeView()), GetPage( name: TaskDetailRoute.path, page: () TaskDetailView(), transition: Transition.cupertino, // 自定义转场动画 ), ]; abstract class TaskDetailRoute { static const path /task/:id; static String go(String id) /task/$id; static String? getId() Get.parameters[id]; }3.2 页面传参的优雅方案传递复杂对象时推荐使用arguments而非URL参数// 发起导航 Get.toNamed( TaskDetailRoute.go(task.id), arguments: {editMode: true, task: task}, ); // 目标页面获取参数 final args Get.arguments as MapString, dynamic; final isEditMode args[editMode] as bool;注意对于需要持久化的数据应该传递ID而非完整对象通过控制器获取最新数据4. 依赖注入与服务层设计4.1 分层架构实现创建本地存储服务抽象abstract class LocalStorageService { Futurevoid write(String key, dynamic value); FutureT? readT(String key); } class HiveStorageService implements LocalStorageService { override FutureT? readT(String key) async { return await Hive.box(appData).get(key) as T?; } }在应用启动时初始化依赖void main() async { await Get.putAsync(() HiveStorageService().init()); runApp(MyApp()); } class AppBindings implements Bindings { override void dependencies() { Get.lazyPut(() TodoController()); Get.put(ThemeService()); } }4.2 跨组件访问服务在任何Widget中获取服务实例final storage Get.findLocalStorageService(); final todos Get.findTodoController().todos;5. 主题切换与国际化实战5.1 动态主题实现方案创建主题控制器class ThemeService extends GetxService { final _isDarkMode false.obs; void toggleTheme() { _isDarkMode.toggle(); Get.changeTheme(_isDarkMode.value ? ThemeData.dark() : ThemeData.light()); } }在UI中响应主题变化Obx(() Switch( value: Get.findThemeService().isDarkMode, onChanged: (_) Get.findThemeService().toggleTheme(), ));5.2 多语言支持配置定义翻译文件class AppTranslations extends Translations { override MapString, MapString, String get keys { en_US: { title: Todo App, add_task: Add Task, }, zh_CN: { title: 待办事项, add_task: 添加任务, }, }; }在GetMaterialApp中启用return GetMaterialApp( translations: AppTranslations(), locale: Get.deviceLocale, fallbackLocale: const Locale(en, US), );6. 实用工具链深度整合6.1 智能对话框与Snackbar替代标准showDialog的方案Get.defaultDialog( title: 删除确认, middleText: 确定删除该任务吗, textConfirm: 确定, onConfirm: () { controller.deleteTask(task.id); Get.back(); }, );带图标的Snackbar提示Get.snackbar( 操作成功, 任务已添加到列表, icon: const Icon(Icons.check_circle, color: Colors.green), shouldIconPulse: true, );6.2 性能优化技巧使用workers自动取消异步操作class TodoController extends GetxController { final searchInput .obs; override void onInit() { debounce(searchInput, (_) filterTasks(), time: Duration(milliseconds: 500)); super.onInit(); } }在开发过程中我发现GetX的ever监听器特别适合处理表单验证场景。比如当需要实时检查任务标题是否为空时ever(todoTitle, (value) { isSubmitEnabled.value value.trim().isNotEmpty; });对于需要处理网络请求的场景GetX的GetConnect提供了开箱即用的HTTP客户端final api GetConnect(); final response await api.get(https://api.example.com/todos); if (response.hasError) { Get.snackbar(错误, 获取数据失败); return; }在项目收尾阶段通过Get.create替代Get.put可以实现按需创建控制器的效果这在多标签界面中特别有用GetPage( name: /task/:id, page: () TaskDetailView(), binding: BindingsBuilder(() { Get.create(() TaskController(Get.parameters[id]!)); }), );