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
相关推荐
程序员Ctrl喵15 小时前
异步编程:Event Loop 与 Isolate 的深层博弈
开发语言·flutter
前端不太难16 小时前
Flutter 如何设计可长期维护的模块边界?
flutter
小蜜蜂嗡嗡17 小时前
flutter列表中实现置顶动画
flutter
始持18 小时前
第十二讲 风格与主题统一
前端·flutter
始持18 小时前
第十一讲 界面导航与路由管理
flutter·vibecoding
始持18 小时前
第十三讲 异步操作与异步构建
前端·flutter
新镜18 小时前
【Flutter】 视频视频源横向、竖向问题
flutter
黄林晴19 小时前
Compose Multiplatform 1.10 发布:统一 Preview、Navigation 3、Hot Reload 三箭齐发
android·flutter
Swift社区19 小时前
Flutter 应该按功能拆,还是按技术层拆?
flutter
肠胃炎20 小时前
树形选择器组件封装
前端·flutter