Flutter:在构建期间调用 setState() 或 markNeedsBuild()

Posted

技术标签:

【中文标题】Flutter:在构建期间调用 setState() 或 markNeedsBuild()【英文标题】:Flutter: setState() or markNeedsBuild() called during build 【发布时间】:2020-12-28 17:23:27 【问题描述】:

我有一个显示视频的页面。但是当我完成视频并返回上一页时,我收到了以下错误。我的应用程序运行良好,没有中断,但是下面的这个错误让我很困扰。

在构建期间调用的 setState() 或 markNeedsBuild() 是我无法修复的错误

我需要帮助来修复此错误。

这是我的错误:

══╡ EXCEPTION CAUGHT BY FOUNDATION LIBRARY ╞════════════════════════════════════════════════════════
The following assertion was thrown while dispatching notifications for VideoPlayerController:
setState() or markNeedsBuild() called during build.
This VideoPlayerScreen widget cannot be marked as needing to build because the framework is already
in the process of building widgets.  A widget can be marked as needing to be built during the build
phase only if one of its ancestors is currently building. This exception is allowed because the
framework builds parent widgets before children, which means a dirty descendant will always be
built. Otherwise, the framework might not visit this widget during this build phase.
The widget on which setState() or markNeedsBuild() was called was:
  VideoPlayerScreen
The widget which was currently being built when the offending call was made was:
  Overlay-[LabeledGlobalKey<OverlayState>#e44a5]

When the exception was thrown, this was the stack:
#0      Element.markNeedsBuild.<anonymous closure> (package:flutter/src/widgets/framework.dart:4211:11)
#1      Element.markNeedsBuild (package:flutter/src/widgets/framework.dart:4226:6)
#2      State.setState (package:flutter/src/widgets/framework.dart:1260:14)
#3      _VideoPlayerScreenState._listener (package:flow/widget/page/video_player.dart:52:7)
#4      ChangeNotifier.notifyListeners (package:flutter/src/foundation/change_notifier.dart:209:21)
#5      ValueNotifier.value= (package:flutter/src/foundation/change_notifier.dart:276:5)
#6      VideoPlayerController.pause (package:video_player/video_player.dart:360:5)
#7      _VideoPlayerScreenState.deactivate (package:/widget/page/video_player.dart:175:34)
(elided 3 frames from dart:async)

The VideoPlayerController sending notification was:
  VideoPlayerController#619c5(VideoPlayerValue(duration: 0:10:55.430000, size: Size(1920.0, 1080.0),
  position: 0:10:55.430000, caption: Instance of 'Caption', buffered: [DurationRange(start:
  0:00:00.000000, end: 0:10:55.430000)], isPlaying: false, isLooping: false, isBuffering:
  falsevolume: 1.0, errorDescription: null))
════════════════════════════════════════════════════════════════════════════════════════════════════

════════ Exception caught by foundation library ════════════════════════════════════════════════════
The following assertion was thrown while dispatching notifications for VideoPlayerController:
setState() or markNeedsBuild() called during build.

This VideoPlayerScreen widget cannot be marked as needing to build because the framework is already in the process of building widgets.  A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
The widget on which setState() or markNeedsBuild() was called was: VideoPlayerScreen
  dependencies: [_InheritedTheme, _LocalizationsScope-[GlobalKey#fe7c2], MediaQuery]
  state: _VideoPlayerScreenState#4559e
The widget which was currently being built when the offending call was made was: Overlay-[LabeledGlobalKey<OverlayState>#e44a5]
  state: OverlayState#cf68b(entries: [OverlayEntry#517dd(opaque: true; maintainState: false), OverlayEntry#87d9a(opaque: false; maintainState: true)])
When the exception was thrown, this was the stack: 
#0      Element.markNeedsBuild.<anonymous closure> (package:flutter/src/widgets/framework.dart:4211:11)
#1      Element.markNeedsBuild (package:flutter/src/widgets/framework.dart:4226:6)
#2      State.setState (package:flutter/src/widgets/framework.dart:1260:14)
#3      _VideoPlayerScreenState._listener (package:flow/widget/page/video_player.dart:52:7)
#4      ChangeNotifier.notifyListeners (package:flutter/src/foundation/change_notifier.dart:209:21)
...
The VideoPlayerController sending notification was: VideoPlayerController#619c5(VideoPlayerValue(duration: 0:10:55.430000, size: Size(1920.0, 1080.0), position: 0:10:55.430000, caption: Instance of 'Caption', buffered: [DurationRange(start: 0:00:00.000000, end: 0:10:55.430000)], isPlaying: false, isLooping: false, isBuffering: falsevolume: 1.0, errorDescription: null))
════════════════════════════════════════════════════════════════════════════════════════════════════
I/ExoPlayerImpl(24022): Release 569e95d [ExoPlayerLib/2.9.6] [tissot_sprout, Mi A1, Xiaomi, 28] [goog.exo.core]
E/BufferQueueProducer(24022): [SurfaceTexture-0-24022-9] cancelBuffer: BufferQueue has been abandoned
D/SurfaceUtils(24022): disconnecting from surface 0x70a0b7e010, reason disconnectFromSurface

这是我的代码:

class VideoPlayerScreen extends StatefulWidget 
  final VideoPlayerController videoPlayerController;

  const VideoPlayerScreen(
    Key key,
    @required this.videoPlayerController,
  ) : super(key: key);

  @override
  _VideoPlayerScreenState createState() => _VideoPlayerScreenState();


class _VideoPlayerScreenState extends State<VideoPlayerScreen> 
  bool _buttonEnabled = false;
  VideoPlayerController _videoPlayerController;
  int playBackTime = 0;
  //VoidCallback _listener;
  void _listener() 
    if (_videoPlayerController.value.position ==
        _videoPlayerController.value.duration) 
      WidgetsBinding.instance.addPostFrameCallback((_) 
        if (mounted) 
          setState(() 
            _buttonEnabled = true;
          );
        
      );
    
    if (mounted) 
      setState(() 
        playBackTime = _videoPlayerController.value.position.inSeconds;
      );
    
  

  @override
  void initState() 
    _videoPlayerController = widget.videoPlayerController;
    _videoPlayerController
      ..addListener(_listener)
      ..initialize().then((_) 
        _videoPlayerController.play();
        setState(() );
      );
    SystemChrome.setPreferredOrientations([
      DeviceOrientation.landscapeLeft,
      //DeviceOrientation.landscapeRight,
    ]);
    AutoOrientation.landscapeAutoMode();
    super.initState();
  

  @override
  Widget build(BuildContext context) 
    //final length = MediaQuery.of(context).size;
    return SingleChildScrollView(
      child: Stack(
        children: <Widget>[
          _videoPlayerController.value.initialized
              ? _playerWidget()
              : Center(
                  child: CircularProgressIndicator(),
                ),
          Visibility(
            visible: _buttonEnabled,
            child: Container(),
          
          ),
        ],
      ),
    );
  


  _playAndPauseButton() 
    return InkWell(
      child: Icon(
        _videoPlayerController.value.isPlaying ? Icons.pause : Icons.play_arrow,
        color: Theme.of(context).buttonColor,
        size: 25,
      ),
      onTap: () 
        _videoPlayerController.value.isPlaying
            ? _videoPlayerController.pause()
            : _videoPlayerController.play();
      ,
    );
  

  _slider() 
    final length = MediaQuery.of(context).size;
    return Container(
      width: length.width * 0.85,
      child: SliderTheme(
        data: SliderTheme.of(context).copyWith(
          activeTrackColor: Colors.black,
          inactiveTrackColor: Colors.grey,
          trackHeight: 1.0,
          thumbColor: Colors.yellow,
          thumbShape: RoundSliderThumbShape(enabledThumbRadius: 6.0),
        ),
        child: Slider(
            max: _videoPlayerController.value.duration.inSeconds.toDouble(),
            min: 0.0,
            value: playBackTime.toDouble(),
            onChanged: (v) 
              _videoPlayerController.seekTo(Duration(seconds: v.toInt()));
            ),
      ),
    );
  

  _playerWidget() 
    final length = MediaQuery.of(context).size;
    return GestureDetector(
      onTap: () 
        final snackBar = SnackBar(
          content: Container(
            height: length.height * 0.02,
            width: length.width,
            child: Row(
              children: [
                _playAndPauseButton(),
                _slider(),
              ],
            ),
          ),
        );
        Scaffold.of(context).showSnackBar(snackBar);
      ,
      child: AspectRatio(
        aspectRatio: _videoPlayerController.value.aspectRatio,
        child: VideoPlayer(_videoPlayerController),
      ),
    );
  

  @override
  void deactivate() 
    widget.videoPlayerController.pause();
    super.deactivate();
  

  @override
  void dispose() 
    widget.videoPlayerController.dispose();
    SystemChrome.setPreferredOrientations([
      DeviceOrientation.landscapeRight,
      DeviceOrientation.landscapeLeft,
      DeviceOrientation.portraitUp,
      DeviceOrientation.portraitDown,
    ]);
    AutoOrientation.landscapeAutoMode();
    AutoOrientation.portraitUpMode();
    super.dispose();
  

【问题讨论】:

【参考方案1】:

尝试处置videoController:

@override
  void dispose() 
    widget.videoPlayerController?.dispose();
    _videoPlayerController.removeListener(_listener);
    _videoPlayerController.dispose();
    super.dispose();
  

最后把void _listener()改成:

void _listener() 
    if (_videoPlayerController.value.position ==
        _videoPlayerController.value.duration) 
      WidgetsBinding.instance.addPostFrameCallback((_) 
        if (mounted) 
          setState(() 
            _buttonEnabled = true;
          );
        
      );
    
    WidgetsBinding.instance.addPostFrameCallback((__) 
      if (mounted) 
        setState(() 
          playBackTime = _videoPlayerController.value.position.inSeconds;
        );
      
    );
  

【讨论】:

我已经更新了我的代码。你能检查一下我的代码是否有问题。或者提供一个示例代码,您在其中使用视频播放器,当单击按钮时,视频必须以横向模式播放,当弹回上一页时,应用程序应处于纵向模式。 完美!非常感谢你的帮助。错误消失了,我的应用程序运行正常。这样的解脱。

以上是关于Flutter:在构建期间调用 setState() 或 markNeedsBuild()的主要内容,如果未能解决你的问题,请参考以下文章

Flutter - 在构建期间调用 setState() 或 markNeedsBuild()

在构建期间调用 setState() 或 markNeedsBuild() - Flutter

Flutter:在构建错误期间调用了 setState() 或 markNeedsBuild()

Flutter :setState() 或 markNeedsBuild() 在构建期间调用

Flutter 错误 - 在构建期间调用了 setState() 或 markNeedsBuild()

如何修复 Flutter 构建问题期间调用的 setState