Software Development: Old Dog, New Flutter Trick. (Part 3)

Pieter Venter
DVT Software Engineering
13 min readJun 7, 2021

Well, so far in this series, we’ve looked at making use of stateless and stateful widgets to build some basic apps, with some concepts you should hopefully be able to apply to your own apps in future. For this article, I thought we could have a look at something a bit more technical, but still make it relevant and simple.

At this point, we’ve learned how to create different types of widgets, but we haven’t looked at how we can pass in some data to those widgets so that we can reuse them. What do I mean by this? Well, you’ll have to follow along for the journey. Let’s get going. As always, all the code will be available here if you need anything.

So, let’s consider a really basic setup, something we’ve seen previously:

import 'package:flutter/material.dart';
import 'my_home_page.dart';

void main() {
runApp( MyApp());
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}

As usual, I’ve cleared out our main.dart file, and now I’ll be creating the usual setup inside our home page:

First, we create a new file called my_home_page.dart, after that we’ll add the following:

class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("The Flutter Series: Part 3"),
centerTitle: true,
),
body: Container(
child: Text("Hello"),
),
);
}
}

The standard setup. Let’s consider our simple Text widget. Let’s replace it with a custom widget, so we can style it and make some changes. I made a new file called custom_text_widget.dart. The name is quite irrelevant so you can name it anything you like. Similarly, I’ve created a custom widget class. Nothing special really:

class CustomTextWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text(
"Hello",
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
);
}
}

I just added some random styling so that our custom widget at least looks a bit different, but let’s just focus on the fact that we have a custom widget for now. This custom widget can be anything you want. You could add some images or any combination of other widgets, but before we continue, let’s remember to use this widget on our home page. So let’s change it to look like this:

class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("The Flutter Series: Part 3"),
centerTitle: true,
),
body: Container(
child: CustomTextWidget(),
),
);
}
}

We now have a custom widget that we’re using — we know that widgets are components that help us to build the UI, but we also know that a widget is a reusable component, right?

Well, our widget is definitely reusable. We could create this same widget several different times and it would look the same for all of them, however, what if we wanted to use this same widget, yet change what we were displaying with it?

While you could create different custom widgets for every single thing you wanted to display, that would be quite tedious, so let’s see how we can pass in some values to our custom widget. Well, that’s quite simple. First, though, it’s important to understand what our widget is expecting. At the moment, it happily accepts our value of "Hello" , with that being a String (Strings are text). That means if we were able to pass in our own string value to our custom widget somehow, then it would display that, right?

Well, to give a variable to our custom widget, it would probably make sense that we need to define it somewhere. It would make the most sense to pass this value to our widget when it gets created — so we’ll be using a constructor for our custom widget. (What’s a constructor? Well, consider a constructor as a useful function that helps us out when creating a specific object). How does all of this look?

class CustomTextWidget extends StatelessWidget {
final String widgetTitle;

CustomTextWidget(this.widgetTitle);

@override
Widget build(BuildContext context) {
return Text(
widgetTitle,
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
);
}
}

So what’s happening here?

We start off by defining a string variable that will hold the value we want to display:

final String widgetTitle;

(Why use final? The final keyword means that we will be assigning a value to this variable once and only once — this makes the most sense for us, considering that we are making use of a really simple Stateless widget here)

Next up, we create our constructor method. This method might look a bit strange, but it’s actually pretty convenient.

CustomTextWidget(this.widgetTitle);

What this method is saying is that when this object is created, in our case our custom widget, it will expect a string value to be passed to it, and then it will assign that value into our widgetTitle variable which we just spoke about.

And now finally, all we have to change in our custom widget is to replace our text with the value of the variable we passed in, as simple as replacing our hardcoded text with our variable. Let’s get to it.

Back on our home page, we can now make use of our custom widget repeatedly, by change our code to look like this:

class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("The Flutter Series: Part 3"),
centerTitle: true,
),
body: Container(
child: Column(
children: [
CustomTextWidget("We can now create a whole bunch of these"),
CustomTextWidget("Awesome"),
],
)),
);
}
}

I added a column widget here, just as an example, but we can now make use of our custom widget repeatedly and pass in any text that we want. That’s pretty useful!

This is just a simple example of passing in a String, but you can pass through any type of value you might need in your app. Hopefully, up to this point, everything makes sense. Let’s build a super simple app to make use of what we just learned.

So, what’s next?

Well, let’s make use of what we just learned to create a simple list of items. You might be thinking that a column widget will be fine for this, but we’ll actually be making use of another widget. We’ll make use of a ListView.

ListView makes our job of showing a list of scrollable items really simple, but how does it work? Well, usually, when we want to create a list of items, we create one component which we will use as the template for all the entries in our list. For us in Flutter, that component will be a widget. So, conveniently, we’ve already started making our CustomTextWidget at the start of the article. How would this work?

Back in our MyHomePage file, we can change it to look like this:

class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("The Flutter Series: Part 3"),
centerTitle: true,
),
body: Container(
child: ListView.builder(
itemCount: 7,
itemBuilder: (context, index) {
return CustomTextWidget("Hello");
},
)),
);
}
}

We’ve now changed the content of our body to have a ListView. This ListView will create 7 items for us — what will it be creating? Well, at the moment, we are telling it to return our CustomTextWidget, so if we were to run this, we’d have 7 of our CustomTextWidgets stacked vertically. Quite simple, right?

But, again, what if we wanted to show different values in our list of items? At the moment, we’ll just be creating a list saying Hello 7 times, right?

Well, that wouldn’t be too hard, so let’s see how we’d do it.

Let’s suppose we had some unique data that we wanted to display — one of the ways we could declare this data, would be like this:

final data = ["One", "Two", "Three", "Four", "Five", "Six", "Seven"];

What’s going on here?

Well, we’re creating a List of data.

(If you’re new to programming, you can consider a List as a convenient way to store a bunch of data together into a single container — all of our text is now stored inside data. How would we get our text out of our list? Well, you can access a specific index in a list to get the data out of it. If we asked our list for index 0 here, it would return “One”, index 1 would give us “Two”, etc. because Lists start at index 0)

Ok, that’s cool, but how are we going to access what’s inside data ? Well, ListView makes it really simple to do exactly that, let’s have a look at our ListView again:

ListView.builder(
itemCount: 7,
itemBuilder: (context, index) {
...
},
)

The index there actually represents the index of the item the itemBuilder will be creating. What does this mean?

Well, if we now used this index to get items out of our array, we’ve achieved our goal:

class MyHomePage extends StatelessWidget {
final data = ["One", "Two", "Three", "Four", "Five", "Six", "Seven"];

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("The Flutter Series: Part 3"),
centerTitle: true,
),
body: Container(
child: ListView.builder(
itemCount: 7,
itemBuilder: (context, index) {
return CustomTextWidget(data[index]);
},
)),
);
}
}

By making use of data[index] we are now going to access the specific index of the item as the ListBuilder creates the entries into our list.

At this point, we can run our app and see our data being displayed:

Nice! Now we know how to create a list of custom widgets and we can pass in data to these widgets as we needed.

I’ll admit, this list doesn’t look too impressive, but we can easily change our custom widget to make it look how we wanted it to. At this point, adding images or doing some styling to a widget shouldn’t be something you’re struggling with if you followed the rest of the series, so let’s get started with improving our custom widget.

First, find some images you want to use, then add those to the project. Flutter works really well with branded apps, so that’s why you’ll always see an image somewhere in these articles. So, I’ve found some images to use, I’ve added them to a folder inside assets/images and after that, I’ve updated the pubspec file.

Shouldn’t be anything new:

Simple images, ranging from 0 to 6

Pubspec:

Cool, nothing new. Now, let’s improve our Custom widget a bit. I want to show an image, a header and some subtext.

Because I want to pass in a whole bunch of custom data now, I’m going to create our own class to represent the data we want to pass over. Here’s our class:

class CustomTextModel {
final String headingText;
final String subtext;
final String imagePath;

CustomTextModel(this.headingText, this.subtext, this.imagePath);
}

Nothing too complicated, this just shows that we need a header, a subtext and an image when we want to use our CustomTextWidget. Let’s update our CustomTextWidget to make use of this model:

class CustomTextWidget extends StatelessWidget {
final CustomTextModel customTextModel;

CustomTextWidget(this.customTextModel);

@override
Widget build(BuildContext context) {
return Text(
customTextModel.headingText,
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
);
}
}

Now our CustomTextWidget expects the new class we just created to provide it with data. Let’s start with styling this widget, knowing that we’ll be getting a heading, an image and a subtext. First, I’m going to create a widget to represent our image:

class LogoWidget extends StatelessWidget {
final String imageName;

LogoWidget(this.imageName);

@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.all(12),
height: 160,
width: 160,
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
image: AssetImage("assets/images/$imageName")),
),
);
}
}

A simple widget I’ve made to represent our Logo, it takes in the name of our image and then it’ll display it. Nice.

Let’s add some magic to our CustomTextWidget now.

This is what I came up with:

class CustomTextWidget extends StatelessWidget {
final CustomTextModel customTextModel;

CustomTextWidget(this.customTextModel);

@override
Widget build(BuildContext context) {
return Card(
margin: EdgeInsets.all(8),
child: Row(
children: [
LogoWidget(customTextModel.imagePath),
Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
width: 100,
margin: EdgeInsets.only(left: 8, bottom: 8),
child: Text(
customTextModel.headingText,
style: TextStyle(fontSize: 24),
),
),
Text(customTextModel.subtext),
],
)
],
),
);
}
}

So, let’s start at the top. Making use of a Card widget as our container, we’ve now added an image, some text as a heading and then some subtext underneath that. We’ve added our LogoWidget we just made and gave it the data it will need. After that, we’ve added a column so that we can display our text data vertically, and then we’ve just styled some basic text widgets so they can act as a heading and as subtext. Nothing too new or too complicated. You can really style this any way you’d like at this point.

At this point, we’re not done yet, we still need to go fix the issues on our home page, so let’s go and fix that quickly.

At the moment, this is how our file will look:

class MyHomePage extends StatelessWidget {
final data = ["One", "Two", "Three", "Four", "Five", "Six", "Seven"];

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("The Flutter Series: Part 3"),
centerTitle: true,
),
body: Container(
child: ListView.builder(
itemCount: 7,
itemBuilder: (context, index) {
return CustomTextWidget(data[index]);
},
)),
);
}
}

But, this won’t really work anymore, because our CustomText widget doesn’t take in a String anymore, instead, it now expects a CustomTextModel. We know that CustomTextModel requires some text for the heading, some text to use as subtext, and the name of an image. So, we can fix our MyHomePage quite simply by declaring the data we need, let me show you how to fix it and then we can go over it:

class MyHomePage extends StatelessWidget {
final heading = ["One", "Two", "Three", "Four", "Five", "Six", "Seven"];
final subtext = [
"Subtext for One",
"Subtext for Two",
"Subtext for Three",
"Subtext for Four",
"Subtext for Five",
"Subtext for Six",
"Subtext for Seven"
];

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("The Flutter Series: Part 3"),
centerTitle: true,
),
body: Container(
child: ListView.builder(
itemCount: 7,
itemBuilder: (context, index) {
final customTextModel =
CustomTextModel(heading[index], subtext[index], "$index.jpg");
return CustomTextWidget(customTextModel);
},
)),
);
}
}

So what’s going on here?

I declared an additional list to use for retrieving our subtext, exactly the same as the list for our heading, then inside our itemBuilder:

itemBuilder: (context, index) {
final customTextModel =
CustomTextModel(heading[index], subtext[index], "$index.jpg");
return CustomTextWidget(customTextModel);
}

Here, I’m creating an instance of our CustomTextModel to pass to the item our builder will be creating. I’m taking data from the heading list, the subtext list, and then using the index as the name of the image. So, if index is 0, we will be passing in “0.jpg” as the name of our image.

Let’s run our app:

Awesome, it works!

Using what we’ve learned from this, you should now be able to create apps with a list of items, something you’ll find quite common in almost any app you’ll work with.

Once again, if you’ve followed along this far, thanks for reading and being part of the journey. Hopefully, you’ve learned a few new tricks you can now use in your Flutter career.

For anyone who hasn’t heard of Flutter before, Flutter is an open-source Software Development Kit (SDK) backed by Google — a free framework that allows you to write code and deploy it to various different platforms — mobile, web and desktop too, all from a single codebase. Not only does the Flutter framework allow you to develop beautiful looking mobile apps, but it also keeps native performance in mind, by making sure that everything works as you’d expect it to, for your specific platform and hardware.

Flutter makes use of Dart, which enables compilation to 32-bit and 64-bit ARM machine code for iOS and Android, as well as JavaScript for the web and Intel x64 for desktop devices. If you’ve used something like Kotlin, Swift or even Javascript before, you should be able to learn Dart really quickly.

Flutter includes the contributions of hundreds of developers from around the world and has a vibrant ecosystem of thousands of plug-ins. Every Flutter app is a native app that uses the standard Android and iOS build tools, therefore you can access everything from the underlying operating system, including code and UIs written in Kotlin or Java on Android, and Swift or Objective-C on iOS. Put this all together, combine it with best-in-class tooling for Visual Studio Code, Android Studio, IntelliJ or the programmer’s editor of your choice, and you have Flutter — a development environment for building beautiful native experiences for iOS or Android from a single codebase.

Flutter has been adopted by the broader community quite quickly, as evidenced by the thousands of Flutter apps that are already published to the Apple and Google Play stores. It’s clear that developers are ready for a new approach to UI development in order to create impressive iOS and Android apps.

So in a nutshell, Flutter makes it simple to deliver quality apps to any platform you want — not only that, but it allows you to do this really quickly, with techniques specifically designed to boost productivity, such as allowing you to quickly reload to see the changes you’ve made. You can have a look here if you’re interested in learning more. If you’ve ever considered something like React Native, perhaps you should give Flutter a chance as well.

--

--