Creating Custom Reusable Widgets in Flutter

Sep 14 2022 · Dart 2.18.0, Flutter 3.3.0, Android Studio Chipmunk 2021.2.1 & VS Code 1.70.2 Universal

Part 1: Creating Custom Reusable Widgets in Flutter

06. Code the Seek Bar Interaction

Episode complete

Play next episode

Next
About this episode
Leave a rating/review
See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 05. Implement the Play Button Next episode: 07. Update the Labels

Get immediate access to this and 4,000+ other videos and books.

Take your career further with a Kodeco Personal Plan. With unlimited access to over 40+ books and 4,000+ professional videos in a single subscription, it's simply the best investment you can make in your development career.

Learn more Already a subscriber? Sign in.

Notes: 06. Code the Seek Bar Interaction

The values can also be initialised in the initState() method. So if you create a variable without value or initialisation. Initialise them inside the initState() function, or use late keyword. Late keyword helps in initalisation of the varibale later in the apps lifecycle. Be cautious when you use late keyword.

The students materials have been reviewed and are updated as of September 2022.

The update material uses null safety and also proper use of const and final keywords as per the latest Flutter guidelines. This is to encourage the students to use the latest Flutter best practices.

Transcript: 06. Code the Seek Bar Interaction

Demo

Let’s implement the seek bar which is the slider. We’ll be doing this now because the current and total times labels depend on it. For this, we’ll add two state variables to the _AudioWidgetState class. (Do that now)

// The values should be initialised when they are declared. 
// If you not initialising them here, you can initialise them in the initState() method.
// Right now to create a variable without the value it can either be an optional
// or a late variable. We are using optional variable here.

double? _sliderValue;
bool? _userIsMovingSlider;

The _sliderValue stores the current value of the slider and a slider ranges from 0.0 to 1.0. The _userIsMovingSlider boolean keeps track of if the user is moving the slider.

Let’s add a method to calculate the slider value. Add the following above the build method.

double _getSliderValue() {
  if (widget.currentTime == null) {
    return 0;
  }
  return widget.currentTime.inMilliseconds! / widget.totalTime.inMilliseconds;
}

The method checks to see if the currentTime is null and if it is, then we return 0 which shows that the track is not yet mounted. We also add a “!” to the inMilliseconds property because we want to make sure that currentTime is not null. Else, it calculates the slider value from the currentTime and totalTime. And they are both in milliseconds so that we can have a smooth shorth movement as the slider value changes. Using something like seconds would provide bigger gaps as the slider moves.

Now, whenever the user moves the slider manually, you’ll need a way to get the current duration. Add the following method to calculate the current time based on the slider value.

Duration _getCurrentDuration(double sliderValue) {
  final seconds = widget.totalTime.inSeconds * sliderValue;
  return Duration(seconds: seconds.toInt());
}

Now that we’ve created methods that would help us get the slider value and the current duration, let’s go ahead and initialize the state variables we created earlier like so:

@override
void initState() {
  super.initState();
  _sliderValue = _getSliderValue();
  _userIsMovingSlider = false;
}

We do the initialization inside the initState method of the state class. This method is called only once and that is as soon as the widget is mounted. So we simply want to get the sliderValue and also set the _userIsMovingSlider to false at the start of the widget.

Now, whenever your audio is playing and the user starts moving the seek bar, we don’t want a war between the sliderValue and the currentTime. And that’s what the _userIsMovingSlider flag was created for. Add the following at the start of the build method:

if (!_userIsMovingSlider) {
  _sliderValue = _getSliderValue();
}

Remember, the widget always rebuilds itself whenever the slider value changes. That’s the whole idea of using a stateful widget for this. The stateful widget rebuilds itself whenevr the state changes. So this check we added, prevents the current duration from updating whenever the user is manually moving the seek bar. If this is confusing, just rememebr the _getSliderValue() method calculates the sliderValue from both the currentTime and the totalTime of the audio file. But this time around, the user is manually affecting the slider value and we only want to resume playing the track after the user is done moving the seek bar.

Okay, now let’s extract the Slider into a method just like we did for the play and pause IconButton. (Extract It) Then name it _buildSeekBar and add the following code:

...
Expanded _buildSeekBar(BuildContext context) {
  return Expanded(
    child: Slider(
      value: _sliderValue,
      activeColor: Theme.of(context).textTheme.bodyText2.color,
      inactiveColor: Theme.of(context).disabledColor,

      onChangeStart: (value) {
        _userIsMovingSlider = true;
      },

      onChanged: (value) {
        setState(() {
          _sliderValue = value;
        });
      },
      
      onChangeEnd: (value) {
        _userIsMovingSlider = false;
        if (widget.onSeekBarMoved != null) {
          final currentTime = _getDuration(value);
          widget.onSeekBarMoved!(currentTime);
        }
      },
    ),
  );
}
...

This seems like quite a lot but its simpler than it looks. Let’s break it down. (Highlight the sections)

  • When the user starts moving the slider thumb, we set _userIsMovingSlider to true.
  • As the thumb moves we need to update the value of the slider so we call setState to set the value of the slider to the current position of the thumb. This create the seek effect.
  • Then when the user is done moving the thumb to new position, we set _userIsMovingSlider to false and then we notify the onSeekBarMoved listener with the new currentTime. We add the “!” because the onSeekBarMoved is a nullable function. We have make sure that this function is not null before we call it.

Do a hot restart. And let’s try it out. The seek bar moves but if you notice, the labels for the currentTime is not updating. Let’s tackle that in the next episode.