Flutter秒表重载后StreamBuilder显示waiting状态空数据问题
问题分析
你的核心问题在于:
- 普通
BroadcastStreamController没有缓存历史值,页面重建后StreamBuilder订阅流时会先进入waiting状态,直到流有新事件触发。 loadState是异步操作,页面初始化时StreamBuilder已完成构建,此时本地状态还未加载完成,导致UI显示默认的Duration.zero。
解决方案
方案1:使用BehaviorSubject(推荐,基于rxdart)
BehaviorSubject会自动缓存最后一次发送的值,新订阅者一订阅就能收到最新值,完美匹配秒表恢复状态的需求。
步骤1:添加依赖
在pubspec.yaml中加入rxdart:
dependencies: flutter: sdk: flutter rxdart: ^0.27.7 # 或最新版本
步骤2:修改StopwatchService
import 'package:rxdart/rxdart.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'dart:async'; import 'dart:core'; class StopwatchService { // 用BehaviorSubject替代普通StreamController,初始化时传入默认值 final BehaviorSubject<Duration> _elapsedTimeController = BehaviorSubject.seeded(Duration.zero); final BehaviorSubject<Duration> _lapTimeController = BehaviorSubject.seeded(Duration.zero); Duration _pausedDuration = Duration.zero; int? _startTimeEpoch; int? _lastStopTimeEpoch; Stream<Duration> get elapsedTimeStream => _elapsedTimeController.stream; Stream<Duration> get lapTimeStream => _lapTimeController.stream; void start() { _startTimeEpoch ??= DateTime.now().millisecondsSinceEpoch; _tick(); } void stop() async { _lastStopTimeEpoch = DateTime.now().millisecondsSinceEpoch; _pausedDuration += Duration(milliseconds: _lastStopTimeEpoch! - _startTimeEpoch!); _elapsedTimeController.add(_pausedDuration); _lapTimeController.add(Duration.zero); // 新增:保存状态到本地 final prefs = await SharedPreferences.getInstance(); _saveState(prefs); } void reset() async { _elapsedTimeController.add(Duration.zero); _lapTimeController.add(Duration.zero); // 重置内部状态 _pausedDuration = Duration.zero; _startTimeEpoch = null; _lastStopTimeEpoch = null; // 清空本地保存 final prefs = await SharedPreferences.getInstance(); await prefs.remove('lastStopTimeEpoch'); await prefs.remove('startTimeEpoch'); await prefs.remove('pausedDuration'); } Future<void> loadState(SharedPreferences prefs) async { final lastStopTimeEpoch = prefs.getInt('lastStopTimeEpoch') ?? DateTime.now().millisecondsSinceEpoch; final startTimeEpoch = prefs.getInt('startTimeEpoch') ?? 0; final pausedDurationMs = prefs.getInt('pausedDuration') ?? 0; _pausedDuration = Duration(milliseconds: pausedDurationMs); if (startTimeEpoch != 0) { final elapsedTime = Duration(milliseconds: lastStopTimeEpoch - startTimeEpoch - pausedDurationMs); _elapsedTimeController.add(elapsedTime); _lapTimeController.add(Duration.zero); } } void _tick() { Timer.periodic(const Duration(milliseconds: 10), (timer) { if (_startTimeEpoch == null) { timer.cancel(); return; } final now = DateTime.now().millisecondsSinceEpoch; final elapsedTime = Duration(milliseconds: now - _startTimeEpoch! - _pausedDuration.inMilliseconds); _elapsedTimeController.add(elapsedTime); }); } // 新增:保存状态的私有方法 void _saveState(SharedPreferences prefs) async { await prefs.setInt('lastStopTimeEpoch', _lastStopTimeEpoch ?? DateTime.now().millisecondsSinceEpoch); await prefs.setInt('startTimeEpoch', _startTimeEpoch ?? 0); await prefs.setInt('pausedDuration', _pausedDuration.inMilliseconds); } // 新增:释放资源,防止内存泄漏 void dispose() { _elapsedTimeController.close(); _lapTimeController.close(); } }
步骤3:修改页面State
import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; class StopwatchPage extends StatefulWidget { const StopwatchPage({super.key}); @override StopwatchPageState createState() => StopwatchPageState(); } class StopwatchPageState extends State<StopwatchPage> { final StopwatchService _stopwatchService = StopwatchService(); @override void initState() { super.initState(); _loadState(); } Future<void> _loadState() async { final prefs = await SharedPreferences.getInstance(); await _stopwatchService.loadState(prefs); } @override void dispose() { _stopwatchService.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: StreamBuilder<Duration>( stream: _stopwatchService.elapsedTimeStream, builder: (context, snapshot) { final elapsed = snapshot.data ?? Duration.zero; return Text( _formatDuration(elapsed), style: const TextStyle(fontSize: 40), ); }, ), ), floatingActionButton: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ FloatingActionButton( onPressed: () => _stopwatchService.stop(), child: const Icon(Icons.pause), ), const SizedBox(width: 10), FloatingActionButton( onPressed: () => _stopwatchService.start(), child: const Icon(Icons.play_arrow), ), const SizedBox(width: 10), FloatingActionButton( onPressed: () => _stopwatchService.reset(), child: const Icon(Icons.refresh), ), ], ), ); } // 可选:格式化时长为00:00.00的友好格式 String _formatDuration(Duration duration) { String twoDigits(int n) => n.toString().padLeft(2, '0'); final minutes = twoDigits(duration.inMinutes.remainder(60)); final seconds = twoDigits(duration.inSeconds.remainder(60)); final milliseconds = twoDigits((duration.inMilliseconds.remainder(1000)) ~/ 10); return "$minutes:$seconds.$milliseconds"; } }
方案2:原生Flutter实现(不依赖第三方库)
如果不想引入rxdart,可以通过缓存最后值+合并初始流的方式实现类似效果:
class StopwatchService { final StreamController<Duration> _elapsedTimeController = StreamController.broadcast(); Duration? _lastElapsedTime; final StreamController<Duration> _lapTimeController = StreamController.broadcast(); // 合并初始值和原流,确保新订阅者立即获取最新值 Stream<Duration> get elapsedTimeStream { final initialValue = _lastElapsedTime ?? Duration.zero; return Stream.value(initialValue).concatWith([_elapsedTimeController.stream]); } // 封装add方法,同时缓存最后值 void _addElapsedTime(Duration value) { _lastElapsedTime = value; _elapsedTimeController.add(value); } // 其余方法和方案1一致,仅把所有_elapsedTimeController.add替换为_addElapsedTime }
初始化StreamController的最佳实践
- 优先使用BehaviorSubject:如果需要新订阅者能立即获取最新状态,这是最简洁可靠的方案。
- 缓存最后值:原生实现时,手动缓存流的最后一次发送值,在订阅时先发送该值。
- 必须关闭控制器:在组件销毁时调用
close()方法,避免内存泄漏。 - 同步初始值:确保流在初始化时就有默认值,避免StreamBuilder进入waiting状态。
内容的提问来源于stack exchange,提问作者Gurple
相关产品推荐
相关产品推荐

