解决方案: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: '我的'),
],
),
);
}
}
关键点:
- IndexedStack 保留页面,不会重新 build Controller
- 切 Tab 时,手动调用 Get.find().refresh()
- 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);
}
- 大厂和成熟项目的做法更倾向于 在 AppPage(或者模块入口)中注册 Controller,
- 单例管理:Controller 永久存在,状态不会被 IndexedStack 或 Tab 切换销毁
- 随时可用:无论 HomePage 是否 build,都可以 Get.find()
- Tab 切回刷新方便:直接在 onTap 调用 Get.find(...).refresh()
- 大厂风格:页面只负责 UI,Controller 负责状态和业务逻辑
稍微提前占用内存,但对单页 Controller 来说几乎忽略不计
Flutter 的特点
- Widget 是不可变的(immutable),每次状态变化都会重新构建 Widget 树
- 你不直接去改 Widget 属性,而是改 Controller/State
- 框架会高效地对比新旧 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()),
);
},
),
);
}
}
- RouteObserver + RouteAware 可以监听 页面生命周期
- didPush: 页面入栈
- didPop: 页面出栈
- didPopNext: 上一个页面 pop 回来 → 这里就是我们需要刷新列表的时机
- didPushNext: 下一个页面入栈
- loadData() 在 didPopNext 调用 → 返回页面 A 时重新请求接口
- 页面使用 Obx 或 GetBuilder 来显示 Controller 的数据
- 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 页面
- 页面一般 一直在内存中(用 IndexedStack 切换),不会被销毁
- 方案:使用 GetX Controller 管理状态和请求数据
- Controller 全局注册 Get.put(HomeController(), permanent: true)
- 页面用 Obx 或 GetBuilder 监听数据
- 切换回该 tab 时手动调用 Controller 的刷新方法 loadData()
非 TabBar 页面(普通子页面跳转)
- 页面会被 push/pop 管理栈控制,返回时可能需要刷新数据。
- 方案:使用 RouteObserver + RouteAware
- 页面 A 实现 RouteAware
- 当 B 页面 pop 回来时,didPopNext() 会被触发 → 调用刷新方法
- 页面 A 的 State 保留在内存中,但可以重新请求接口
特点:无需依赖 TabBar,精准监听"页面可见"事件
| 场景 | Flutter 生命周期 | 刷新数据方案 |
|---|---|---|
| TabBar 页面 | 永驻内存 | GetX Controller + 手动刷新方法 |
| 普通子页面 | push/pop | RouteObserver + RouteAware |
| 响应式更新数据 | 数据变化触发 UI | Obx / GetBuilder |