You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用GetX开发APP:登录后Obx不刷新、搜索失效,仅页面刷新后恢复

问题分析

你遇到的核心问题是GetX状态管理与Flutter原生State机制冲突,加上Obx监听逻辑不完整,导致登录后UI无法自动响应状态变化:

  1. 混用StatefulWidget和setState,干扰了GetX的Observable监听机制;
  2. Obx仅包裹UI结构,但未正确关联search变量的变化,导致搜索功能失效;
  3. 控制器初始化与API调用的顺序问题,导致首次数据加载后未触发Obx刷新;
  4. 任务状态更新、删除操作未通过GetX的Observable变量触发刷新,依赖手动setState才能生效。
解决方案

1. 移除StatefulWidget依赖,改用StatelessWidget

彻底放弃Flutter原生状态管理,完全依赖GetX的Observable机制。

2. 修正Obx监听逻辑

确保Obx内部同时监听todo列表和search变量的变化,过滤逻辑需在Obx范围内执行。

3. 规范控制器状态更新方式

所有数据更新(加载、删除、状态修改)都通过修改Observable变量的.value触发Obx自动刷新,禁止使用setState。

4. 调整API调用时机

在页面构建完成后调用fetchTodo,避免初始化顺序问题。

修改后的代码

UI代码

class LandingPage extends StatelessWidget {
  const LandingPage({super.key});

  @override
  Widget build(BuildContext context) {
    final controller = Get.put(ToDoController());
    final usercontroller = Get.put(UserController());
    final dateInput = TextEditingController();
    final scaffoldKey = GlobalKey<ScaffoldState>();
    final isselected = false.obs;

    // 页面构建完成后,仅在列表为空时触发数据加载
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (controller.todo.isEmpty) {
        controller.fetchTodo();
      }
    });

    return Obx(() {
      // 同时监听todo列表和search变量的变化,实时过滤数据
      final filteredTodos = controller.todo.where((element) {
        if (controller.search.value.isNotEmpty) {
          return element.title?.toLowerCase().contains(controller.search.value.toLowerCase()) ?? false;
        }
        return true;
      }).toList();

      return Column(
        children: filteredTodos.map((todo) {
          return Stack(
            children: [
              Card(
                child: ExpansionTile(
                  initiallyExpanded: false,
                  subtitle: Padding(
                    padding: const EdgeInsets.fromLTRB(7, 3, 0, 6),
                    child: Wrap(
                      spacing: 8,
                      runSpacing: 8,
                      children: todo.labels!.map((label) {
                        return ChipMv(lable: label);
                      }).toList(),
                    ),
                  ),
                  title: Align(
                    alignment: Alignment.topLeft,
                    child: TextButton(
                      child: Text(
                        GetUtils.capitalizeFirst(todo.title ?? "").toString(),
                        style: Theme.of(context).primaryTextTheme.headline5,
                      ),
                      onPressed: () {
                        // 补充按钮点击逻辑(如果需要)
                      },
                    ),
                  ),
                  children: [
                    Align(
                      alignment: Alignment.topLeft,
                      child: Container(
                        child: ListTile(
                          title: Form(
                            child: TextFormField(
                              controller: controller.task,
                              decoration: InputDecoration(
                                suffixIcon: IconButton(
                                  icon: const Icon(Icons.add),
                                  onPressed: () {
                                    controller.taskCreate(todo.id);
                                  },
                                ),
                                hintText: "Add Task",
                              ),
                            ),
                          ),
                        ),
                      ),
                    ),
                    Column(
                      children: todo.tasks!.map((task) {
                        return Align(
                          alignment: Alignment.topLeft,
                          child: Container(
                            child: CheckboxListTile(
                              checkboxShape: const CircleBorder(),
                              onChanged: (value) {
                                controller.todoStatus(todo.id, task.tid, value);
                              },
                              value: task.complete,
                              controlAffinity: ListTileControlAffinity.trailing,
                              secondary: IconButton(
                                icon: const Icon(Icons.delete_outlined),
                                color: Colors.redAccent.withOpacity(0.6),
                                onPressed: () {
                                  Get.defaultDialog(
                                    content: const Text("Are you want to remove task?"),
                                    actions: <Widget>[
                                      TextButton(
                                        onPressed: () => Get.back(),
                                        child: const Text('Cancel'),
                                      ),
                                      TextButton(
                                        onPressed: () {
                                          controller.taskDelete(todo.id, task.tid);
                                          Get.back();
                                        },
                                        child: const Text('OK'),
                                      ),
                                    ],
                                  );
                                },
                              ),
                              title: Text(task.task ?? ""),
                            ),
                          ),
                        );
                      }).toList(),
                    ),
                  ],
                ),
              ),
            ],
          );
        }).toList(),
      );
    });
  }
}

控制器代码

class ToDoController extends GetxController {
  RxList<Todo> todo = <Todo>[].obs;
  RxString search = "".obs;
  RxBool isloading = false.obs;
  TextEditingController task = TextEditingController();

  Future<List<Todo>> fetchTodo() async {
    isloading.value = true;
    Uri url = Uri.parse('$dev/todo/todo/list');

    SharedPreferences preferences = await SharedPreferences.getInstance();
    var token = preferences.getString("token");
    print(token);

    var response = await http.post(
      url,
      headers: <String, String>{
        'Content-Type': 'application/json; charset=UTF-8',
        'Authorization': 'Bearer $token',
      },
    );

    if (response.statusCode == 200) {
      final datas = json.decode(response.body).cast<Map<String, dynamic>>();
      final fetchedTodos = datas.map<Todo>((json) => Todo.fromJson(json)).toList();
      // 通过.value赋值触发Obx刷新
      todo.value = fetchedTodos;
      isloading.value = false;
      return todo;
    } else {
      isloading.value = false;
      return todo;
    }
  }

  // 删除任务后更新Observable列表
  void taskDelete(String todoId, String taskId) async {
    // 补充删除任务的API调用逻辑
    // 更新本地列表,触发UI刷新
    final updatedTodos = todo.map((t) {
      if (t.id == todoId) {
        t.tasks?.removeWhere((task) => task.tid == taskId);
      }
      return t;
    }).toList();
    todo.value = updatedTodos;
  }

  // 更新任务状态后刷新列表
  void todoStatus(String todoId, String taskId, bool? complete) async {
    // 补充状态更新的API调用逻辑
    // 更新本地列表,触发UI刷新
    final updatedTodos = todo.map((t) {
      if (t.id == todoId) {
        t.tasks?.forEach((task) {
          if (task.tid == taskId) {
            task.complete = complete;
          }
        });
      }
      return t;
    }).toList();
    todo.value = updatedTodos;
  }
}
关键修改总结
  1. 改用StatelessWidget,彻底移除setState,避免状态冲突;
  2. 在Obx内部执行过滤逻辑,确保todo和search的变化都能触发UI刷新;
  3. 所有数据更新操作(加载、删除、状态修改)都通过修改todo.value触发Obx自动刷新;
  4. 调整API调用时机,确保页面初始化完成后再加载数据,避免顺序问题。

内容的提问来源于stack exchange,提问作者Izaz ahamed

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 06:05:24