有时候不同页面之间不是上下层路由关系而是堆叠,子页面状态改变时不会返回,父页面不会重新build,这时需要从子页面去调用父页面的setState来更新。
子页面调用父页面setState
最简单的方法,在子页面定义一个回调函数。这里是notifyParent,在需要时调用即可。
class ToolsPage extends ConsumerStatefulWidget {
ToolsPage({Key? key, required this.notifyParent})
: super(key: key);
final VoidCallback notifyParent;
@override
ConsumerState<ToolsPage> createState() => _ToolsPageState();
}
父页面声明时这样声明。
ToolsPage(notifyParent: () => setState(() {}));
任意页面之间互相调用setState
页面关系复杂时,可能堆叠、路由都存在,要想让两个不同的孙节点互相更新状态或者导航时,最简单的方法是用provider来管理页面的方法。
下面是使用riverpod的例子。定义一个页面的provider来管理该页面的context和setState方法。就可以在任意页面完成对其他页面的状态更新和导航了。但是这个方法并不好,尤其需要注意被调用的页面是否会被注销,否则会造成内存泄露。还是尽量少用这样的奇技淫巧吧。
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'navigator_provider.g.dart';
@riverpod
class FunctionLayerNavigator extends _$FunctionLayerNavigator {
late BuildContext context;
late Function setState;
@override
FunctionLayerNavigator build() {
return this;
}
void init(BuildContext context, Function setState) {
state.context = context;
state.setState = setState;
}
void push(Widget widget) =>
Navigator.of(state.context).push(CupertinoPageRoute<Widget>(
builder: (BuildContext context) {
return widget;
},
));
void pop<T extends Object?>([T? result]) =>
Navigator.of(state.context).pop<T>(result);
}
使用时,在要管理的页面对provider进行声明和初始化。
Widget build(BuildContext context) {
final navigator = ref.watch(functionLayerNavigatorProvider);
navigator.init(context, () => setState(() {}));
调用provider,使用时最好还是在provider内部定义调用state的方法,这样方便在调用前检查是否有初始化,避免产生异常。例如:
void changeIndex(int index) {
state.index = index;
state.setState();
}
navigator.changeIndex(1); // GOOD
navigator.setState(); //BAD
最佳方案
上面的方法实际上并不是provider的正确使用方法。
利用riverpod解决这个问题有更合理的方法,我们为什么要在其他widget调用另一个widget的setState呢?因为我们想刷新内部数据,那么我们使用StateProvider就能轻松解决这一问题。例如下面实现themeProvider,就能实在任何组件内容现主题的切换了。
final themeProvider = StateProvider<ThemeMode>((ref) {
return ThemeMode.system;
});
class MyApp extends ConsumerWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final themeMode = ref.watch(themeProvider);
return MaterialApp(
themeMode: themeMode,
);
}
}