You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

设备方向变化时更新Riverpod状态报错的解决求助

问题:Flutter中使用Riverpod监听设备方向变化时更新StateNotifier报错

错误信息

StateNotifierListenerError(当StateNotifier实例'KeyBoardResize'尝试更新状态时,至少有一个监听器抛出异常。抛出的异常为:
尝试在Widget树构建期间修改Provider。
出现此错误的原因可能是你在Widget生命周期(包括但不限于build、initState、dispose、didUpdateWidget、didChangeDepedencies)中修改了Provider。不允许在这些生命周期内修改Provider,否则可能导致UI状态不一致,例如两个监听同一Provider的Widget可能接收到不同的状态。

问题代码

StateNotifier逻辑

class KeyBoardResize extends StateNotifier<bool> {
  KeyBoardResize() : super(false);

  changeBool(mode) {
    if (mode == Orientation.landscape) {
      return state = true;
    } else if (mode == Orientation.portrait) {
      return state = false;
    }
  }
}

final keyboardResizeNotifier =
    StateNotifierProvider<KeyBoardResize, bool>((ref) => KeyBoardResize());

Widget使用代码

class LoginScreen extends ConsumerWidget {
  const LoginScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final bool keyboardEnabled =
        WidgetsBinding.instance.window.viewInsets.bottom <= 0.0;

    final resize = ref.read(keyboardResizeNotifier);

    return Scaffold(
      resizeToAvoidBottomInset: resize,
      body: OrientationBuilder(
        builder: (context, orientation) {
          if (orientation == Orientation.portrait) {
            ref.read(keyboardResizeNotifier.notifier).changeBool(orientation);
            return portraitMode(context, keyboardEnabled);
          } else if (orientation == Orientation.landscape) {
            ref.read(keyboardResizeNotifier.notifier).changeBool(orientation);
            return landscapeMode(context, keyboardEnabled);
          } else {
            return Container();
          }
        },
      ),
    );
  }
}

解决方法

核心原因

错误本质是在Widget构建过程中(OrientationBuilder的builder方法属于build生命周期)直接修改了StateNotifier的状态,违反了Riverpod的规则——不允许在构建阶段更新Provider状态,否则会导致UI状态不一致。


方法1:延迟到构建完成后更新状态

使用WidgetsBinding.instance.addPostFrameCallback,将状态更新操作推迟到当前帧构建完成后执行:

修改OrientationBuilder的builder逻辑:

body: OrientationBuilder(
  builder: (context, orientation) {
    // 延迟到构建完成后更新状态
    WidgetsBinding.instance.addPostFrameCallback((_) {
      ref.read(keyboardResizeNotifier.notifier).changeBool(orientation);
    });

    if (orientation == Orientation.portrait) {
      return portraitMode(context, keyboardEnabled);
    } else if (orientation == Orientation.landscape) {
      return landscapeMode(context, keyboardEnabled);
    } else {
      return Container();
    }
  },
),

方法2:让StateNotifier自动监听方向变化(推荐)

把方向监听逻辑封装到StateNotifier内部,无需在Widget中手动触发状态更新,更符合Riverpod的设计思想:

修改StateNotifier:

class KeyBoardResize extends StateNotifier<bool> {
  final Ref ref;
  late final VoidCallback _orientationListener;

  KeyBoardResize(this.ref) : super(false) {
    // 初始化时设置方向监听
    _orientationListener = () {
      final orientation = MediaQuery.of(ref.context).orientation;
      // 直接根据方向更新状态
      state = orientation == Orientation.landscape;
    };
    WidgetsBinding.instance.addOrientationListener(_orientationListener);
  }

  // 销毁时移除监听,避免内存泄漏
  @override
  void dispose() {
    WidgetsBinding.instance.removeOrientationListener(_orientationListener);
    super.dispose();
  }
}

更新Provider定义:

final keyboardResizeNotifier = StateNotifierProvider<KeyBoardResize, bool>((ref) => KeyBoardResize(ref));

修改Widget代码:

无需在OrientationBuilder中手动调用changeBool,直接监听状态即可:

class LoginScreen extends ConsumerWidget {
  const LoginScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final bool keyboardEnabled =
        WidgetsBinding.instance.window.viewInsets.bottom <= 0.0;

    // 监听StateNotifier的状态变化
    final resize = ref.watch(keyboardResizeNotifier);

    return Scaffold(
      resizeToAvoidBottomInset: resize,
      body: OrientationBuilder(
        builder: (context, orientation) {
          if (orientation == Orientation.portrait) {
            return portraitMode(context, keyboardEnabled);
          } else if (orientation == Orientation.landscape) {
            return landscapeMode(context, keyboardEnabled);
          } else {
            return Container();
          }
        },
      ),
    );
  }
}

方法3:直接在Widget中计算值(最简方案)

如果逻辑只是根据方向设置resizeToAvoidBottomInset,完全可以不用StateNotifier,直接在build中计算:

class LoginScreen extends ConsumerWidget {
  const LoginScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final bool keyboardEnabled =
        WidgetsBinding.instance.window.viewInsets.bottom <= 0.0;
    final orientation = MediaQuery.of(context).orientation;

    return Scaffold(
      // 直接根据方向设置值
      resizeToAvoidBottomInset: orientation == Orientation.portrait,
      body: OrientationBuilder(
        builder: (context, orientation) {
          if (orientation == Orientation.portrait) {
            return portraitMode(context, keyboardEnabled);
          } else if (orientation == Orientation.landscape) {
            return landscapeMode(context, keyboardEnabled);
          } else {
            return Container();
          }
        },
      ),
    );
  }
}

内容的提问来源于stack exchange,提问作者Rakul Agn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 19:15:07