flutter实现页面返回刷新实现类似uniapp小程序 OnShow效果

解决方案:Controller 提供刷新方法 + Tab 回调触发

HomeController

复制代码
class HomeController extends GetxController {
  var items = <String>[].obs;
  var isLoading = false.obs;

  @override
  void onInit() {
    super.onInit();
    loadData();
  }

  Future<void> loadData() async {
    isLoading.value = true;
    try {
      var res = await RequestUtil().get('/api/v1/pages');
      items.value = List<String>.from(res.data ?? []);
    } catch (e) {
      print("请求失败: $e");
    } finally {
      isLoading.value = false;
    }
  }

  void refresh() => loadData(); // 提供刷新方法
}

AppPage:监听 Tab 切换

复制代码
class AppPage extends StatefulWidget {
  const AppPage({super.key});

  @override
  State<AppPage> createState() => _AppPageState();
}

class _AppPageState extends State<AppPage> {
  int _currentIndex = 0;

  final List<Widget> _pages = [
    HomePage(),
    CatePage(),
    CartPage(),
    MyPage(),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: IndexedStack(
        index: _currentIndex,
        children: _pages,
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: (index) {
          setState(() {
            _currentIndex = index;
          });

          // 如果切回 HomePage,就刷新数据
          if (index == 0) {
            Get.find<HomeController>().refresh();
          }
        },
        items: const [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'),
          BottomNavigationBarItem(icon: Icon(Icons.grid_view), label: '分类'),
          BottomNavigationBarItem(icon: Icon(Icons.shopping_cart), label: '购物车'),
          BottomNavigationBarItem(icon: Icon(Icons.person), label: '我的'),
        ],
      ),
    );
  }
}

关键点:

  1. IndexedStack 保留页面,不会重新 build Controller
  2. 切 Tab 时,手动调用 Get.find().refresh()
  3. Controller 里的 refresh() 会触发请求,Obx 自动刷新 UI

放 store/ 也是可以的,而且很多团队为了区分 状态管理层,会专门用 store/ 或 controllers/ 目录。

复制代码
lib/
 ├─ pages/          // 页面目录
 │   ├─ home/
 │   │   └─ home_page.dart
 │   ├─ cate/
 │   │   └─ cate_page.dart
 │   └─ ...
 ├─ store/          // 状态管理目录
 │   ├─ home_controller.dart
 │   ├─ cate_controller.dart
 │   └─ ...
 ├─ utils/          // 工具类
 │   └─ request_util.dart
 ├─ config/         // 配置
 │   └─ env.dart
 └─ main.dart

报错

复制代码
"HomeController" not found. You need to call "Get.put(HomeController())" or "Get.lazyPut(()=>HomeController())"

这说明你在 调用 Get.find() 时,Controller 还没有被注册。

在 AppPage 里先注册 Controller

复制代码
 @override
  void initState() {
    super.initState();
    // 预先注册 HomeController
    Get.put(HomeController(), permanent: true);
  }
  1. 大厂和成熟项目的做法更倾向于 在 AppPage(或者模块入口)中注册 Controller,
  2. 单例管理:Controller 永久存在,状态不会被 IndexedStack 或 Tab 切换销毁
  3. 随时可用:无论 HomePage 是否 build,都可以 Get.find()
  4. Tab 切回刷新方便:直接在 onTap 调用 Get.find(...).refresh()
  5. 大厂风格:页面只负责 UI,Controller 负责状态和业务逻辑

稍微提前占用内存,但对单页 Controller 来说几乎忽略不计

Flutter 的特点

  1. Widget 是不可变的(immutable),每次状态变化都会重新构建 Widget 树
  2. 你不直接去改 Widget 属性,而是改 Controller/State
  3. 框架会高效地对比新旧 Widget,只更新必要的部分(类似 React 的虚拟 DOM diff)

非tabbar页面中使用

你是 从 A 页面跳转到 B 页面,然后返回 A 页面时,希望 A 页面重新请求接口刷新列表

. 在 main.dart 或全局声明 RouteObserver

复制代码
final RouteObserver<ModalRoute> routeObserver = RouteObserver<ModalRoute>();

然后在main.dart MaterialApp 中注册

复制代码
MaterialApp(
  title: 'Flutter Demo',
  navigatorObservers: [routeObserver],
  home: HomePage(),
);

页面 A 使用 RouteAware

复制代码
class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> with RouteAware {
  final HomeController controller = Get.put(HomeController());

  @override
  void initState() {
    super.initState();
    controller.loadData(); // 页面第一次进入也请求
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    //订阅 routeObserver.subscribe(this, ModalRoute.of(context)!);
    routeObserver.subscribe(this, ModalRoute.of(context)!);
  }

  @override
  void dispose() {
    routeObserver.unsubscribe(this);
    // 取消订阅
    super.dispose();
  }

  // 当从 B 页面 pop 回来时调用
  @override
  void didPopNext() {
    controller.loadData(); // 重新请求接口刷新列表
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("A 页面")),
      body: Obx(() {
        if (controller.isLoading.value) return const Center(child: CircularProgressIndicator());
        return ListView.builder(
          itemCount: controller.items.length,
          itemBuilder: (context, index) => ListTile(
            title: Text(controller.items[index]),
          ),
        );
      }),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.navigate_next),
        onPressed: () {
          Navigator.push(
            context,
            MaterialPageRoute(builder: (_) => PageB()),
          );
        },
      ),
    );
  }
}
  1. RouteObserver + RouteAware 可以监听 页面生命周期
  • didPush: 页面入栈
  • didPop: 页面出栈
  • didPopNext: 上一个页面 pop 回来 → 这里就是我们需要刷新列表的时机
  • didPushNext: 下一个页面入栈
  1. loadData() 在 didPopNext 调用 → 返回页面 A 时重新请求接口
  2. 页面使用 Obx 或 GetBuilder 来显示 Controller 的数据
  3. routeObserver.subscribe(this, ModalRoute.of(context)!); 这个必须要

routeObserver.subscribe(this, ModalRoute.of(context)!) 就是把当前页面注册给全局的

didPopNext() 会在上一个页面被 pop 回来时触发

dispose() 中取消订阅是标准写法,避免内存泄漏。

推荐你这样写 = 拥有完整生命周期监听

复制代码
class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> with WidgetsBindingObserver {

  @override
  void initState() {
    super.initState();
    print("onLoad = 页面创建");
    WidgetsBinding.instance.addObserver(this);

    WidgetsBinding.instance.addPostFrameCallback((_) {
      print("onReady = 页面渲染完毕");
    });
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print("onShow = 页面可见了");
  }

  @override
  void deactivate() {
    super.deactivate();
    print("onHide = 页面被覆盖或进入后台");
  }

  @override
  void dispose() {
    print("onUnload = 页面销毁");
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  // 监听应用前后台切换(能力比 UniApp 更强)
  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.resumed) {
      print("App回到前台 ≈ onShow");
    } else if (state == AppLifecycleState.paused) {
      print("App进入后台 ≈ onHide");
    }
  }
}

总结一下

1️⃣ TabBar 页面

  1. 页面一般 一直在内存中(用 IndexedStack 切换),不会被销毁
  2. 方案:使用 GetX Controller 管理状态和请求数据
  • Controller 全局注册 Get.put(HomeController(), permanent: true)
  • 页面用 Obx 或 GetBuilder 监听数据
  • 切换回该 tab 时手动调用 Controller 的刷新方法 loadData()

非 TabBar 页面(普通子页面跳转)

  1. 页面会被 push/pop 管理栈控制,返回时可能需要刷新数据。
  2. 方案:使用 RouteObserver + RouteAware
  • 页面 A 实现 RouteAware
  • 当 B 页面 pop 回来时,didPopNext() 会被触发 → 调用刷新方法
  • 页面 A 的 State 保留在内存中,但可以重新请求接口
    特点:无需依赖 TabBar,精准监听"页面可见"事件
场景 Flutter 生命周期 刷新数据方案
TabBar 页面 永驻内存 GetX Controller + 手动刷新方法
普通子页面 push/pop RouteObserver + RouteAware
响应式更新数据 数据变化触发 UI Obx / GetBuilder
相关推荐
子春一10 小时前
Flutter 架构演进:从单体到模块化,构建可扩展的大型应用体系
flutter
庄雨山10 小时前
Flutter 与开源鸿蒙混合工程开发实战指南
flutter·开源·openharmonyos
RollingPin10 小时前
React Native与Flutter的对比
android·flutter·react native·ios·js·移动端·跨平台开发
装不满的克莱因瓶11 小时前
【2026最新最全】Android Studio安装教程
android·ide·flutter·app·android studio·移动端
西西学代码11 小时前
Flutter---通用子项的图片个数不同(2)
flutter
song50111 小时前
鸿蒙 Flutter 语音交互进阶:TTS/STT 全离线部署与多语言适配
分布式·flutter·百度·华为·重构·electron·交互
克喵的水银蛇11 小时前
Flutter 通用进度条组件:ProgressWidget 一键实现多类型进度展示
flutter
庄雨山11 小时前
Flutter 与开源鸿蒙 底部弹窗多项选择实现方案全解析
flutter·开源·openharmonyos
笨小孩78711 小时前
Flutter深度解析:从核心原理到实战开发全攻略
flutter
笨小孩78711 小时前
Flutter深度解析:从原理到实战的跨平台开发指南
flutter