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

05. Implement the Play Button

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: 04. Setup the Audio Widget Next episode: 06. Code the Seek Bar Interaction

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: 05. Implement the Play Button

Final variables should be initialized when declared before use. When declaring variables without initialisation, they can be initalised in the constuctor. The constructor is called when the widget is created. The parameter can either be required or optional. The required keyword is used to make the parameter as required and “?” is used to make the parameter optional.

To use the optional paramters we need to check if they are null or not. Usually done by if else case. When using the optional variable you have tell the compiler that you are sure the value is not null. That is done by appending a bang operator “!” with the variable.

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: 05. Implement the Play Button

Now that we’re done with the design and have determined the possible interactions for this widget, we need to start defining the parameters that we would expose to developers that would be using it.

Take a look at the parameters we’ll be defining for this widget. isPlaying allows you to toggle the Play or Pause button icon. onPlayStateChanged takes a function that is going to be called whenever the user presses the the play or pause button.

currentTime is going to handle both the current time label and the slider position. If you notice, it is of type duration so we could easily calculate the slider position from it. So there no need for a parameter like slider value. onSeekBarMoved takes a function that is called when the user chooses a new location from the slider. totalTime gets the length of the audio track and like currentTime, this is also be a Duration. Now that we have the parameters we want to add in mind, let’s go ahead and add them up.

Demo

The AudioWidget is currently a stateless widget. We need a way to keep track of the slider’s state internally. A stateless widget doesn’t have a mutable state so we’ll convert the AudioWidget to a stateful widget.

I’ll place my cursor on the class name and press Alt + Enter or Option + Return if you’re on a Mac. Then select “Convert to StatefulWidget.” These creates two classes. The first one is the stateful widget while the second class is the state class which is responsible for managing the state object.

Now, let’s add the parameters. Add the following code to the first class which in the AudioWidget class:

...
// As per the Flutter lint rules the final variables should be initialized.
// That is why they are initialised in the constructor.
// The constructor is called when the widget is created.
// The parameter can either be required or optional.
// The required keyword is used to make the parameter required.
// And "?" is used to make the parameter optional.

final bool? isPlaying;
final ValueChanged<bool>? onPlayStateChanged;
final Duration? currentTime;
final ValueChanged<Duration>? onSeekBarMoved;
final Duration totalTime;

const AudioWidget({
  Key key,
  this.isPlaying = false,
  this.onPlayStateChanged,
  this.currentTime,
  this.onSeekBarMoved,
  required this.totalTime,
}) : super(key: key);
...

We discussed about these parameters earlier. So you should be already familiar with why you need them and what they’re going to be used for. But let’s talk about some knowledge that might come in handy.

First, the default value of isPlaying to false. This makes sense because we dont want the UI to be in a playing state at first.

Now take a look at the types for onPlayStateChanged and onSeekBarMoved. They have a ValueChanged signature which is a callback function. This is the same thing having a Function with a generic type as its argument. But ValueChanged is the signature we use when we want the callback function to be triggered whenever the value it depends on changes. And it is used for widgets like slider, ckeckbox, radio button or any widget that a change in value triggers a function.

The constructor contains a key which the flutter plugin added for us when we converted the widget to a stateful widget. Keys are used to indentify widgets in Flutter.

Finally, we added the required annotation to the totalTime parameter. Think about it, an audio track without a total time doesnt make sense. So if this is required, we must pass it where the AudioWidget is called in order to prevent Flutter from complaning. Let’s go do that now.

I’ll head over to episode_screen.dart file. And i’ll add the total time to AudioWidget like so:

return AudioWidget(
  totalTime: Duration(minutes: 1, seconds: 15),
);

I just gave it a temporary duration. We’ll change this later on to the real duration gotten from the audio file.

I’ll head back to the AudioWidget class. Now, let’s handle the logic for the play and pause button. For this, we want to do two things whenever the button is clicked:

  • first, toggle the icon
  • and change the play state

First, let’s extract the icon button to a separate method so that we would have a clean layout. (Extract It) I’ll name it _buildPlayPauseButton. Then update it to the following:

IconButton _buildPlayPauseButton() {
  return IconButton(
    icon:
    (widget.isPlaying)
        ? Icon(Icons.pause)
        : Icon(Icons.play_arrow),
    color: Colors.white,
    onPressed: () {
      // TO use optional parameters we need to check if they are null or not.
      // We are using if else case to check if the value is null or not.
      // We need to use bang operator "!" with the optional value
      // To tell the compiler that we are sure that the value is not null.
      // If this value is found null, you code will break.
    
      if (widget.onPlayStateChanged != null) {
        widget.onPlayStateChanged!(!widget.isPlaying!);
      }
    },
  );
}

We extract the button to a method instead of a stateless widget since we’ll be using it only inside the AudioWidget class. Plus, it is not composed on many widgets.

Inside it, we toggle the icon to display based on the isPlaying boolean. So is shows a pause icon if isPlaying is true, else it show a play icon.

Next, inside the onPressed callback method, we check if the onPlayStateChanged is not null. And if it isn’t, we execute it and pass the opposite value of isPlaying which would be either true or false. The onPlayStateChanged function is responsible for changing the play state of the audio widget. A disadvantage of extracting to method rather than a widget is that hot reload doesnt reflect the update.

So we’ll do a hot restart. Now go ahead and press the play button. This doesnt update as expected and that’s because we haven’t hooked up any logic to change the isPlaying value yet Not to worry, we’ll hook up a model when we’re done with the logic of other widgets.