Old dog, New Flutter trick.(Part 2)

Pieter Venter
DVT Software Engineering
13 min readMay 11, 2021

Ready to learn some more tricks with Flutter?

Let’s get started.

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.

In my previous article, we started our journey with Flutter — nothing too complicated, but a solid understanding of the basics. We all have to start somewhere, right?

As this series continues, the complexity will also be ramped up slightly, but I’ll try my best to keep it simple.

As always, all the code will be available on my Github, if you’re only interested in reading along.

So, previously we made a really simple app with an appBar and an image (a totally valid app) but, for a select few, I suppose there might be additional functionality you want to add to your apps.

In this article, I’ll focus on how to write a Flutter app that has content that can change.

What do I mean by change?

Well, remember in our previous article how we discussed the difference between a Stateless widget and a Stateful widget?

No?

That’s fine, we’ll go through it again thoroughly in this article.

So, when building the UI for our apps, Flutter has two types of Widgets: either a Stateless widget or a Stateful widget. Stateless widgets just stay the same throughout your app. While it might sound ridiculous that there are parts of your app that won’t change, it happens more often than you’d think: perhaps your app has a few images that won’t be changing, maybe a disclaimer or some other type of static text or you just have a really simple title you want to display in your appBar — the possibilities are endless.

Whether or not you end up using Stateless widgets will depend on your app and how you want to structure it, however, Flutter allows you to use both of these types of widgets in your app, so it’s up to you.

Do you know what’s also constant? Change. So let’s focus this article around some content we want to change, which means we’ll be having a look at the Stateful widget. Stateful widgets, as their name suggests, change what they display based on their state. Once their state changes, they’ll update themselves, allowing us to create dynamic apps. Changes in state is how Flutter handles updating the User Interface (UI). In other frameworks, you’d probably target a specific component by the ID or name of that component and then change a specific value, but Flutter handles updates a bit differently by redrawing stateful widgets. Don’t worry about it for now. We’ll cross the stateful bridge when we get there.

In this article, we’ll create a simple app where we focus on changing some pictures by using some buttons.

Back to square one. Inside our main.dart file, we’ll start again with our basic app setup:

import 'package:flutter/material.dart';

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

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

and we’ll be creating our home page soon, namely my_home_page.dart.

First, we’re going to have a look at our surroundings, take a bit of a detour and then we’ll continue on our Flutter journey. Let’s start at the beginning. As you can see, we’ll be running MyApp, with MyApp extending StatelessWidget. So… does this mean our entire app won’t be able to change? That’s what StatelessWidget means, right?

Let’s clear up the mystery and try to keep it simple. Once MyApp has been created, it’ll be pulling up our home page. We’ll see our home page and, because MyApp is a stateless widget, we won’t ever be able to change which page will be loaded as our home. This means that we can still change the content of our home page, we just won’t ever be able to swap it out with another home page after our app is running. Make sense?

To summarise:

Can we change what is shown inside MyHomePage? Yes.
Can we change which page is shown after the app is running? No.

If you’re still a bit unsure, don’t worry about it, it should make more sense once we add some logic to our app.

Let’s journey on.

Create a new dart file inside our lib folder, my_home_page.dart and add the following:

import 'package:flutter/material.dart';

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

We’re starting with something simple, just a nice starting point to our app — a simple appBar with some centred text. Still, this isn’t what we’re trying to achieve, right? The people want change! We’ll get to it, I promise, but for now, let’s build a nice UI and focus on changing it later. One problem at a time.

We’ll start off with the most difficult task first: finding images we want to use in our app. This part could take several hours, but once you have some images you want to use, we’ll add those to the project the same we did in the previous article, by adding the following to our pubspec.yaml file:

assets:
- assets/images/

Here’s an image as a reference if you’re struggling to get the correct alignment in pubspec:

Alignment made simple

After making changes to our pubspec.yaml file and doing a sync of our dependencies, we can now add in our images in the appropriate folder:

Nothing new at this point. Let’s build the body of our UI real quick:

import 'package:flutter/material.dart';

class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("The Flutter Series: Part 2"),
centerTitle: true,
),
body: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
//aligns the children of the column to the center, vertically
children: [
Container(
width: double.infinity,
height: 500,
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
image: AssetImage("assets/images/image1.jpg")),
),
),
],
),
),
);
}
}

Let’s continue our journey by going over the mountain of code. What’s happening here?

Well, it’s simple really:

mainAxisAlignment: MainAxisAlignment.center

This aligns everything inside our Column widget to the centre, vertically.

width: double.infinity,

This tells Flutter to use the maximum available width when creating our image (which is, technically, our container widget).

decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
image: AssetImage("assets/images/image2.jpg")),
)

The rest of the code is simply setting up our container to use an image as a decoration.

Running the app at this point, you should be seeing something like this:

Nothing at this point should be new to you if you’ve followed along in the previous article. You could use the previous app we made as a starting point for the new content, which begins…

Now.

What we’re going to do is add two buttons at the bottom of our image, so that we can use them to change our content. I want to add these buttons horizontally, so we’ll make use of a widget that should be quite familiar to you — the Row widget.

The Row widget functions the same as a Column widget, so it can also take in multiple other widgets, but it adds these widgets horizontally, not vertically.

We’ll make use of ElevatedButtons inside our Row so that the user has something to tap or click.

How does this all look in code?

Something like this:

Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
child:
ElevatedButton(child: Text("Previous"), onPressed: () {}),
margin: EdgeInsets.only(right: 8, top: 8),
),
Container(
child: ElevatedButton(child: Text("Next"), onPressed: () {}),
margin: EdgeInsets.only(left: 8, top: 8),
)
],
)

Breaking it down…

mainAxisAlignment: MainAxisAlignment.center,

This aligns the content of our Row widget into the centre, horizontally.

Inside our Row widget, I’ve added in ElevatedButtons, which are wrapped by containers.

ElevatedButton(child: Text("Previous"), onPressed: () {}),

This is our button. At the moment pressing on it won’t do anything. The strange syntax you see next to onPressed just tells the button that, for the moment, we don’t want to do anything… yet.

margin: EdgeInsets.only(right: 8, top: 8),

This is a simple margin I applied to the first button, very similar to the second button, just to make some space between the two of them and to get our buttons a bit further away from the image.

(EdgeInsets.only means that we only want to apply margins to specific positions, but there are other options available when using EdgeInsits too if you wanted something a bit more custom)

Combining this all together:

import 'package:flutter/material.dart';

class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("The Flutter Series: Part 2"),
centerTitle: true,
),
body: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
//aligns the children of the column to the center, vertically
children: [
Container(
width: double.infinity,
height: 500,
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
image: AssetImage("assets/images/image2.jpg")),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
child:
ElevatedButton(child: Text("Previous"), onPressed: () {}),
margin: EdgeInsets.only(right: 8, top: 8),
),
Container(
child: ElevatedButton(child: Text("Next"), onPressed: () {}),
margin: EdgeInsets.only(left: 8, top: 8),
)
],
)
],
),
),
);
}
}

Quite a lot of code, but everything up to this point should be simple to understand. Running the app, you’ll see your picture and some simple buttons:

But…

At the moment, our app doesn’t really change, right?

While you might be happy pressing buttons and staring at your picture (nothing wrong with that), ideally you want something to change, right?

Let’s climb the last part of the mountain.

class MyHomePage extends StatelessWidget {

At the moment, our class is extending StatelessWidget, but we’re going to change this to StatefulWidget:

class MyHomePage extends StatefulWidget {

Immediately, you’ll see that your favourite IDE will probably complain that there’s something wrong with MyHomePage now, so let’s add the missing piece:

class MyHomePage extends StatefulWidget {

@override
State<StatefulWidget> createState() {

}
...

You might be thinking that we’ll move all our other code inside this createState method and you’d be almost correct. At this point, we’re going to define exactly what our widget should be doing when the state changes.

At the bottom of our MyHomePage class, we’re going to start off by defining a new class, specifically MyHomePageState:

class MyHomePageState extends State<MyHomePage> {


}

Notice here how we’re extending State<MyHomePage>, because this class will be responsible for the state of MyHomePage.

Next, we’re going to move all our previous widget building code over into this new class we created, MyHomePageState, so everything should now look something like this:

import 'package:flutter/material.dart';

class MyHomePage extends StatefulWidget {

@override
State<StatefulWidget> createState() {

}

}

class MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("The Flutter Series: Part 2"),
centerTitle: true,
),
body: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
//aligns the children of the column to the center, vertically
children: [
Container(
width: double.infinity,
height: 500,
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
image: AssetImage("assets/images/image2.jpg")),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
child:
ElevatedButton(child: Text("Previous"), onPressed: () {}),
margin: EdgeInsets.only(right: 8, top: 8),
),
Container(
child: ElevatedButton(child: Text("Next"), onPressed: () {}),
margin: EdgeInsets.only(left: 8, top: 8),
)
],
)
],
),
),
);
}
}

But what about our empty createState method?

We’ll finish that off now, by just telling Flutter that the state we want for our widget, is determined by our MyHomePageState class:

class MyHomePage extends StatefulWidget {

@override
State<StatefulWidget> createState() {
return MyHomePageState();
}

}

And now we’ve converted our Stateless widget to a Stateful widget.

Let’s summarise before we continue. What did we just do?

  1. We changed our Widget to extend from StatefulWidget, instead of StatelessWidget.
  2. We created a new class, MyHomePageState, which extends State<MyHomePage> and we moved all our previous widget building code to this new class.
  3. We added the createState method to our MyHomePage class and inside we are now returning MyHomePageState().

Complicated Stuff!

If you’re feeling like Stateful widgets are a bit too complicated, don’t worry about it. It becomes easier the more you practice, but this is the same approach you’ll follow for all of them. So once you get one to work, the rest should be a lot simpler.

At this point, we’ve rearranged our entire class, but we’re still not changing our image, right? A state of disaster…

Now that we have a stateful widget, we can finish off our app, so let’s get going.

Back inside MyHomePageState, we’re going to declare our very first variable. This variable will either have a value of 1, 2 or 3 (depending on the number of images you have or want) and we’ll use that number to load up our images, which we’ve named “image1.jpg”, “image2.jpg” and “image3.jpg”.

class MyHomePageState extends State<MyHomePage> {
int _pictureNumber = 1;

This is how you’d define your variable, which is an integer variable we’ve called _pictureNumber. For anyone who isn’t sure, int or integer just means that our variable will have numeric values.

(What does the underscore do? Why declare it as _pictureNumber? Things we prefix with an underscore are marked as private in dart . For anyone new to programming, don’t worry about this too much, for now, it basically means that we won’t be able to use this variable outside our current class.)

Next up, we’re going to use this variable when we load our images, so we’re going to make some changes to our container’s decoration:

Container(
width: double.infinity,
height: 500,
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
image: AssetImage("assets/images/image$_pictureNumber.jpg")),
),
),

By adding our variable into the path, by making use of $_pictureNumber, we can now load up different images, based on our variable.

(The dollar sign symbol is how we reference our variable inside strings or text, but everywhere outside of strings you’ll have to reference it by making use of _pictureNumber)

When the app starts up, our variable has a value of 1, which means we’ll be loading up:

"assets/images/image1.jpg"

Almost there. Our logic is coming together, but we still haven’t changed the value of our variable anywhere, so that’s exactly what we’ll do now.

Remember those cool buttons we added? At the moment they’re not doing much because we haven’t told them what they should be doing. But, like the content of our app, that will now change!

Let’s change those onPressed listeners of our buttons:

ElevatedButton(
child: Text("Previous"),
onPressed: () {
_pictureNumber = _pictureNumber -1; // or _pictureNumber --;
})

Here we’ll be decreasing the value of our variable by one, right? We’re saying here that our variable, _pictureNumber, will be equal to what it was previously, minus one.

If we’re increasing and decreasing our images by 1, then we’ll get the next and previous image, right?

It’s true that we’re decreasing the value of the variable here, but if you run this, your app still won’t change…

Why?

Well, we’ve changed the value of our variable, but we haven’t really asked Flutter to update our content, and that’s the final piece of the puzzle:

ElevatedButton(
child: Text("Previous"),
onPressed: () {
setState(() {
_pictureNumber = _pictureNumber - 1;
});
})

When we call the setState method, this is what tells Flutter that it needs to update the content of our app, so after updating our Next button accordingly:

ElevatedButton(
child: Text("Next"),
onPressed: () {
setState(() {
_pictureNumber = _pictureNumber + 1; // or _pictureNumber++;
});
})

Our app is finally complete!

Clicking on the buttons of our app will now update the variable and Flutter will update our images for us accordingly.

And now our journey has come to an end.

I didn’t create individual widgets for the components in this article — you could definitely refactor these components into individual widgets if you wanted to, but for the sake of simplicity this is as far as we’ll go.

For anyone who’s followed the journey this far, thank you! Hopefully, you’ll be able to apply some of these concepts in your own apps easily now too.

Keen to learn more about Flutter?

You could join one of the Flutter communities for more insight into what’s happening, by joining on Slack, Discord, Reddit, Twitter or any of the other platforms available. If you’re interested in seeing what Flutter is capable of, you can check out this page for more info on the latest available widgets you can use, API documentation, codelabs, videos, some samples and a whole lot more.

Want to watch some videos instead? You can head over to the Flutter channel on YouTube and learn some new tricks there. Google’s UI toolkit Flutter uses a rich set of customisable widgets to build apps with almost zero effort. Flutter makes it really simple to develop for both Android and iOS and a whole bunch of other platforms too, with really no prerequisite required to get started. There isn’t a better time for you to start learning a new trick.

--

--