Search code examples
flutterdartstateblocflutter-bloc

Adding custom duration to flutter_bloc's timer example


I am learning flutter_bloc and did the timer example at https://bloclibrary.dev/#/fluttertimertutorial?id=flutter-timer-tutorial

However the timer always resets to 60 seconds, so I wanted to the ability to set a custom initial duration.

My approach:

  • Add a field initialDuration to TimerState and all subclass constructors
  • Add a TimerNewDuration event to timer_event.dart
  • Update timer_bloc.dart to handle this with the following emit lines in each state handler:
constructor: super(TimerInitial(_defaultDuration)) # i.e. 60 seconds

_onNewDuration: emit(TimerInitial(event.duration))

_onStarted: emit(TimerRunInProgress(event.duration, state.initialDuration))

_onReset: emit(TimerInitial(state.initialDuration))

_onPaused: emit(TimerRunPause(state.duration, state.initialDuration))

_onTicked:     emit(event.duration > 0
        ? TimerRunInProgress(event.duration, state.initialDuration)
        : TimerRunComplete(state.initialDuration));

...and so on. However, this is a bit cumbersome: the timer state and the initial duration are not really related (except updating initial duration should reset the timer) and so I have to pass this value through every event handler in the BLoC.

If more state values are added, this would obviously get very messy.

Is there a more idiomatic way to handle this situation? Any github examples would be appreciated.


Solution

  • You could use one state with copyWith(...) method:

    import 'package:equatable/equatable.dart';
    
    class TimerState extends Equatable {
      const TimerState({
        this.isPause = false,
        this.isInProgress = false,
        this.isComplete = false,
        this.duration = 0,
      });
    
      final bool isPause;
      final bool isInProgress;
      final bool isComplete;
      final int duration;
    
      @override
      List<Object?> get props => [isPause, isInProgress, isComplete, duration];
    
      TimerState copyWith({
        bool? isPause,
        bool? isInProgress,
        bool? isComplete,
        int? duration,
      }) {
        return TimerState(
          isPause: isPause ?? this.isPause,
          isInProgress: isInProgress ?? this.isInProgress,
          isComplete: isComplete ?? this.isComplete,
          duration: duration ?? this.duration,
        );
      }
    }
    

    then your code will be something like:

    constructor: super(TimerState(duration: _defaultDuration)) # i.e. 60 seconds
    
    _onNewDuration: emit(state.copyWith(duration: event.duration))
    
    _onStarted: emit(state.copyWith(isInProgress: true, isPaused: false))
    
    _onReset: emit(TimerState(duration: _defaultDuration))
    
    _onPaused: emit(state.copyWith(isPaused: true, isInProgress: false))
    
    _onTicked: emit(state.copyWith(isCompleted: event.duration > 0);
    

    p.s. TimerState could be sealed and for state complete could be used state:

    final class TimerRunComplete extends TimerState {
      const TimerRunComplete();
    }