Flutter中LateInitializationError求助:_controller未初始化
问题排查与修复方案
错误根源分析
1. 提前调用未初始化的控制器
在initState中直接执行_controller.initialize()和_controller.setVolume(1.0),但此时_controller仅通过late声明,未完成实例化赋值,直接触发LateInitializationError。
2. 单个控制器复用所有列表项
在ListView.builder的每个item中重复赋值全局_controller,会导致:
- 每次列表项构建都会覆盖控制器,状态完全混乱
- 多个视频共用一个控制器,播放/暂停操作互相干扰
- 控制器生命周期无法与列表项绑定,引发内存泄漏
3. FutureBuilder的不当使用
直接在FutureBuilder的future参数中调用getVideoStatus(),会导致页面每次build时重新发起API请求,浪费资源且易引发异常。
修复步骤与代码示例
第一步:创建独立的列表项Widget(管理自身视频控制器)
每个视频项需持有专属的VideoPlayerController,单独处理初始化与销毁逻辑:
class VideoItemWidget extends StatefulWidget { final String videoLink; final VideoData serverData; // 替换为你的VideoModel对应数据类型 final int progress; final Function(String) onDownloadTap; const VideoItemWidget({ Key? key, required this.videoLink, required this.serverData, required this.progress, required this.onDownloadTap, }) : super(key: key); @override State<VideoItemWidget> createState() => _VideoItemWidgetState(); } class _VideoItemWidgetState extends State<VideoItemWidget> { late VideoPlayerController _controller; late Future<void> _initializeFuture; Duration _position = Duration.zero; Duration _duration = Duration.zero; @override void initState() { super.initState(); // 初始化当前项的视频控制器 _controller = VideoPlayerController.network(widget.videoLink) ..addListener(_videoListener) ..setVolume(1.0); _initializeFuture = _controller.initialize().then((_) => setState(() {})); } void _videoListener() { if (mounted) { setState(() { _position = _controller.value.position; _duration = _controller.value.duration; }); } } String _formatDuration(Duration duration) { String twoDigits(int n) => n.toString().padLeft(2, '0'); final hours = twoDigits(duration.inHours); final minutes = twoDigits(duration.inMinutes.remainder(60)); final seconds = twoDigits(duration.inSeconds.remainder(60)); return [if (duration.inHours > 0) hours, minutes, seconds].join(':'); } @override void dispose() { _controller.removeListener(_videoListener); _controller.dispose(); // 销毁当前项控制器,避免内存泄漏 super.dispose(); } @override Widget build(BuildContext context) { return Card( elevation: 5, color: Colors.blueGrey, shape: Border.all(color: Colors.black), child: Column( children: [ Center( child: FutureBuilder<void>( future: _initializeFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { return AspectRatio( aspectRatio: _controller.value.aspectRatio, child: GestureDetector( onTap: () { setState(() { _controller.value.isPlaying ? _controller.pause() : _controller.play(); }); }, child: Stack( children: [ VideoPlayer(_controller), if (!_controller.value.isPlaying) Container( alignment: Alignment.center, color: Colors.black26, child: const Icon(Icons.play_arrow, color: Colors.white, size: 80), ), Positioned( bottom: 0, left: 0, right: 0, child: VideoProgressIndicator(_controller, allowScrubbing: true), ), Positioned( bottom: 0, right: 0, child: Padding( padding: const EdgeInsets.all(8.0), child: Row( children: [ Text( _formatDuration(_position), style: const TextStyle(fontSize: 11, color: Colors.white), ), const Text("/", style: TextStyle(fontSize: 12, color: Colors.white)), Text( _formatDuration(_duration), style: const TextStyle(fontSize: 11, color: Colors.white), ), ], ), ), ), Positioned( right: 0, top: 0, child: IconButton( onPressed: () => widget.onDownloadTap(widget.videoLink), icon: const Icon(Icons.download, color: Colors.white), ), ), ], ), ), ); } else { return const CircularProgressIndicator(); } }, ), ), const SizedBox(height: 5), Text(widget.serverData.title.toString()), Text("Download percentage ${widget.progress}") ], ), ); } }
第二步:修改原页面核心代码
- 移除全局
_controller和_initializeVideoPlayerFuture变量 - 将API请求Future存为成员变量,避免重复请求
- 重构ListView的itemBuilder,使用上述
VideoItemWidget
class VideoStatusPage extends StatefulWidget { const VideoStatusPage({Key? key}) : super(key: key); @override State<VideoStatusPage> createState() => _VideoStatusPageState(); } class _VideoStatusPageState extends State<VideoStatusPage> { int _progress = 0; ReceivePort receivePort = ReceivePort(); late Future<VideoModel> _videoFuture; // 存储API请求Future int currentPage = 1; final RefreshController refreshController = RefreshController(); VideoModel dummyData = VideoModel(); // 你的初始数据结构 @override void initState() { super.initState(); FlutterDownloader.registerCallback(downloadCallback); IsolateNameServer.registerPortWithName( receivePort.sendPort, "downloadingVideo"); receivePort.listen((message) { setState(() { _progress = message; }); }); _videoFuture = getVideoStatus(); // 初始化API请求Future } Future<VideoModel> getVideoStatus() async { var url = "${masterApiUrl}getVideoStatus.php?page=$currentPage"; var response = await http.get(Uri.parse(url)); var jsondata = jsonDecode(response.body.toString()); var _apiData = VideoModel.fromJson(jsondata); var newData = [...?dummyData.data, ...?_apiData.data]; if (response.statusCode == 200) { if (_apiData.hasNextPage == 0) { refreshController.loadNoData(); } else { refreshController.loadComplete(); } final refids = newData.map((e) => e.id).toSet(); newData.retainWhere((ids) => refids.remove(ids.id)); dummyData.data = newData; return dummyData; } else { showToastMessage("some thing went wrong"); return dummyData; } } static downloadCallback(id, status, progress) { SendPort? sendPort = IsolateNameServer.lookupPortByName("downloadingVideo"); sendPort?.send(progress); } void _downloadFile(String videoLink) { // 你的下载逻辑 print("download tap $videoLink"); } @override void dispose() { receivePort.close(); super.dispose(); } @override Widget build(BuildContext context) { return SafeArea( child: Scaffold( appBar: AppBar(title: const Text("Video Status")), body: Padding( padding: const EdgeInsets.all(8.0), child: FutureBuilder<VideoModel>( future: _videoFuture, builder: (context, snapshot) { if (snapshot.hasData) { return SmartRefresher( controller: refreshController, enablePullUp: true, onLoading: () async { if (snapshot.data!.hasNextPage == 0) { refreshController.loadNoData(); } else { setState(() { currentPage++; }); _videoFuture = getVideoStatus(); await Future.delayed(const Duration(milliseconds: 500)); refreshController.loadComplete(); } }, child: ListView.builder( itemCount: snapshot.data!.data!.length, itemBuilder: (context, index) { var serverData = snapshot.data!.data![index]; var globalLink = "https://Video_Status/"; var videoLink = globalLink + serverData.videoPath.toString(); return VideoItemWidget( videoLink: videoLink, serverData: serverData, progress: _progress, onDownloadTap: _downloadFile, ); }, ), ); } else if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } else if (snapshot.hasError) { return const Center(child: Text("Some thing went wrong !")); } else { return const Center(child: Text("Some thing went wrong")); } }, ), ), ), ); } }
关键修复点总结
- 每个列表项独立管理
VideoPlayerController,避免全局复用导致的状态混乱与初始化错误 - 移除
initState中对未初始化控制器的调用 - 将API请求Future存储为成员变量,避免每次build重复发起请求
- 正确管理控制器生命周期,在列表项销毁时执行dispose,防止内存泄漏
内容的提问来源于stack exchange,提问作者Taki Rajani
相关产品推荐
相关产品推荐

