最新消息:Welcome to the puzzle paradise for programmers! Here, a well-designed puzzle awaits you. From code logic puzzles to algorithmic challenges, each level is closely centered on the programmer's expertise and skills. Whether you're a novice programmer or an experienced tech guru, you'll find your own challenges on this site. In the process of solving puzzles, you can not only exercise your thinking skills, but also deepen your understanding and application of programming knowledge. Come to start this puzzle journey full of wisdom and challenges, with many programmers to compete with each other and show your programming wisdom! Translated with DeepL.com (free version)

flutter - Stopwatch StreamBuilder Shows Snapshot in waiting State with Null Data After Reload - Stack Overflow

matteradmin8PV0评论

I'm building a stopwatch app in Flutter that uses a StreamBuilder to update elapsed and lap times. The app works fine when the stopwatch is running, but after pausing the stopwatch, leaving the page, and reloading it, the StreamBuilder for elapsed and lap times outputs this:

AsyncSnapshot<Duration>(ConnectionState.waiting, null, null, null)

Because of this, the stopwatch UI resets to 00:00.00 until I start the stopwatch again. I expect it to load and display the correct paused elapsed and lap times immediately after the page is reloaded.

Here's a simplified version of the code for the stopwatch service and UI:

class StopwatchService {
  final StreamController<Duration> _elapsedTimeController = StreamController<Duration>.broadcast();
  final StreamController<Duration> _lapTimeController = StreamController<Duration>.broadcast();

  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() {
    _lastStopTimeEpoch = DateTime.now().millisecondsSinceEpoch;
    _pausedDuration += Duration(milliseconds: _lastStopTimeEpoch! - _startTimeEpoch!);
    _elapsedTimeController.add(_pausedDuration);
    _lapTimeController.add(Duration.zero); // For simplicity
  }

  void reset() {
    _elapsedTimeController.add(Duration.zero);
    _lapTimeController.add(Duration.zero);
  }

  Future<void> loadState(SharedPreferences prefs) async {
    final lastStopTimeEpoch = prefs.getInt('lastStopTimeEpoch') ?? DateTime.now().millisecondsSinceEpoch;
    final startTimeEpoch = prefs.getInt('startTimeEpoch') ?? 0;
    final pausedDuration = Duration(milliseconds: prefs.getInt('pausedDuration') ?? 0);

    if (startTimeEpoch != 0) {
      final elapsedTime = Duration(milliseconds: lastStopTimeEpoch - startTimeEpoch - pausedDuration.inMilliseconds);
      _elapsedTimeController.add(elapsedTime);
      _lapTimeController.add(Duration.zero);
    }
  }

  void _tick() {
    Timer.periodic(Duration(milliseconds: 10), (timer) {
      if (_startTimeEpoch == null) return;
      final now = DateTime.now().millisecondsSinceEpoch;
      final elapsedTime = Duration(milliseconds: now - _startTimeEpoch! - _pausedDuration.inMilliseconds);
      _elapsedTimeController.add(elapsedTime);
    });
  }
}

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
  Widget build(BuildContext context) {
    return Scaffold(
      body: StreamBuilder<Duration>(
        stream: _stopwatchService.elapsedTimeStream,
        builder: (context, snapshot) {
          final elapsed = snapshot.data ?? Duration.zero;
          return Text(elapsed.toString());
        },
      ),
    );
  }
}

I have tried:

Adding Initial Values to Streams: I explicitly emit Duration.zero in _elapsedTimeController and _lapTimeController when loading the state or resetting the stopwatch.

Debugging the StreamBuilder: The StreamBuilder still shows waiting and null data immediately after the page is reloaded.

Forcing UI Updates: Tried manually triggering state updates with setState after emitting values to streams.

Expected Behavior: When the stopwatch page reloads, the StreamBuilder should display the correct paused elapsed and lap times without requiring user input.

Actual Behavior: The StreamBuilder shows AsyncSnapshot<Duration>(ConnectionState.waiting, null, null, null) and resets the display to 00:00.00.

How can I ensure that the StreamBuilder displays the correct paused elapsed and lap times immediately after the page reloads? Is there a best practice for initializing StreamController with previously saved data to avoid this issue?

Post a comment

comment list (0)

  1. No comments so far