Notes: 03. Make the Widget Reusable
As per the new lint rules by Flutter. There is no need mention the type of the variable if the varibale is locally declared. Also final keyword is used if the variable wont be reassigned.
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.
- Design the widget
- Decompose the design
- Build the basic widget
- Customize the look
- Determine the user interaction
- Define the dependencies
- Implement the dependencies
- Test the widget
In the previous episode, we covered the first four steps of this list. Now its to complete the remaining steps. The two main interactions for this widget is
- when the device orientation changes
- and when the user or developer in this case wants to display the wide widget.
For device orientation changes, we want to be able to display the wide card when the device is in landscape mode. We would reuse the episode card widget instead of creating a different widget for the lanscape mode.
Another place we would reuse this widget is inside the episode page. In here, we want to list out top episodes in the podcast. In this page, we would display the cards in a ListView and we would be using the wide card version. Let’s go ahead and do that now.
Demo
Back in Android Studio, we would switch the type of card to display based on the device orientation. Update your code to the following:
@override
Widget build(BuildContext context) {
// New code
// As per the new lint rule by Flutter. There is no need mention the type of the variable if the
// varibale is locally declared. Also final keyword is used if the variable wont be reassigned.
final mediaQuery = MediaQuery.of(context);
final isLandscape = mediaQuery.orientation == Orientation.landscape;
...
child: isLandscape
? WideCard(episode: episode)
: TallCard(episode: episode),
We get the mediaquery data using the mediaquery.of method. We then check if the orientation is in landscape mode and then assign the boolean to the isLandscape variable. We then use this boolean to conditonally render the cards. So, we return the widecard if it is in landscape mode, else, we return the tall card.
Let’s do a hot restart. And then we try it out. You can see that the card design is switched based on the device orientation.
Next, let’s head over to the episode screen.
In here, we have the top episodes section.
We want to display the episode cards in a horizontal listview in here.
Let’s head over to the episode_screen.dart file.
Scroll to the “Top episodes” section and paste the following code under the text widget.
Container(
height: 150,
child: ListView(
scrollDirection: Axis.horizontal,
children: [
...episodes
.map((Episode e) => EpisodeCard(
episode: e,
))
.toList()
],
),
),
We wrap the listview with a container and give it a height of 150. We give it a fixed height to prevent the unbounded height error that occurs when you nest a listview inside another listview. Then for the listview, we set it to a horizontal listview and map the episodes list to display the episode cards. This is pretty much the same code used in the home scree.
Save your work. And you can see, we have an overflow error. And this occurs simply because we are currently displaying the tall card which is taller that 150.
We need to finish up our custom widget now. But before we do that let’s take a look at where we are in steps we’ve been following.
- Design the widget
- Decompose the design
- Build the basic widget
- Customize the look
- Determine the user interaction
- Define the dependencies
- Implement the dependencies
- Test the widget
We have successfully completed all the steps. For the device orientation setup, the dependency was the isLandscape boolean. We then implemented this dependency by using it for a conditional check to show a different UI based on the device orientation. Finally, we tested it by rotating our device.
Now, you’re not just limited to internal dependencies just like we did here. Dependencies can be external. For this, we use the widget’s parameters to accept configurations wherever the widget is used.
This is the core of custom reusable widgets: Accepting arguments from outside sources when calling the widget. We’ll be using parameters to customize the widget which in turn solves the overflow error.
Demo
Inside the episode card widget, we’ll be defining an isWideCard boolean parameter like so:
...
final bool isWideCard;
const EpisodeCard({
Key? key,
required this.episode,
this.isWideCard = false,
}) : super(key: key);
We added the boolean and set it to false by default inside the constructor. Next, let’s make use of this parameter. Update your code to the following:
Container(
width: isWideCard ? 350 : mediaQuery.size.width,
...
child: isLandscape || isWideCard
? WideCard(episode: episode)
: TallCard(episode: episode),
We added a width property to the card because horizontal listviews need you to give its children a fixed width. The absense of the width property is why the widget doesnt layout because flutter doesnt know how to much which to allocate to it in the horizontal space. We give it a width of 350 if it is a wide card else, it takes up the width of the screen. Next, we add it to our conditional check. So now, the wide card would be displayed if the device is in landscape mode or when we set isWideCard to true.
Now, we need to set isWideCard value to true where it is used inside the episode screen.
Let’s head over to the episode screen.
Then add the isWideCard argument and set it to true: isWideCard: true.
Save your work. And you can see, the wide card is displayed in the episode screen.