有时候不同页面之间不是上下层路由关系而是堆叠,子页面状态改变时不会返回,父页面不会重新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,
    );
  }
}