Flutter widgets
A Beginner’s Introduction to Widgets in Flutter
Welcome, fresh meat. Today, we’ll be talking about what widgets really are, so you can get a new perspective because being told that “Everything is a widget” is a really vague description, don’t you think? That’s why I’m here to do my best to demystify them.
Dart Classes
Coming from a web dev background, the concept of widgets is gonna hit harder than a truck moving at relativistic speeds (ok, that’s a bit too much, but you get the point). I want you to just drop your current mental models of widgets for a bit, except if they're already solid.
At its core, a widget is an instance of a class or in more formal terms, an object. Flutter works hand in hand with the Dart programming language but with a twist. Enough talking, let’s create our own class to see the concept in action.
class Player {
final String name;
final int health;
const Player({
required this.name,
required this.health,
});
}
Create a new file called player.dart, then let’s talk about the code above.
Note: That Player class isn’t a Widget, just a normal Dart class.
Classes in Dart begin with writing the “class” keyword followed by the name of the class then a set of curly braces. Classes have variables called parameters — they’re essentially the things that describe or give us info about the class. In Dart, we use the “final” keyword when we’re very sure we are not going to use the assigning operator “=”(it’s the equal sign for assigning values to variables) to change the value of that parameter.
Then there’s that “const Player({}).” part of the code. That is something called a constructor. A constructor is a function that runs immediately a class is initialized, we’ll talk about initialization soon. The “required” keyword means the constructor will not run unless you pass in arguments for each of its parameters. To top it all off, the “this” keyword is referring to the class itself which in our case is Player. So when we write this.name, we’re actually referring to Player.name, etc.
Initialization Of Classes
So, you’ve made a class, but how do you actually use it, you ask? This isn’t a widget that allows us to draw things in the UI, but we’re gonna use a workaround. Just a heads-up, but, Flutter won’t allow you to directly write anything other than a widget in the UI. Let’s create a new page called home_page.dart:
import 'package:flutter/material.dart';
import 'package:widget_article/player.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Player(name: "Nasir", health: 50)
);
}
}
If you put that in your code editor, you’ll receive an argument error. The body parameter of the Scaffold widget expects a widget and anything else gets rejected. So how do we go about this? Well, we use something called an IIFE or Immediately Invoked Function Expression.
Don’t panic it’s actually an intuitive name for this expression when we break it down. An IIFE has this structure:
import 'package:flutter/material.dart';
import 'package:widget_article/player.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
body:(){}()
);
}
}
The thing is, when you create a function, it’s dormant until you “call” or as it’s generally called “Invoke” it. When we wrote Player(”Nasir”, 50), we invoked the constructor function. Before we did that, the constructor function wasn’t doing anything.
import 'package:flutter/material.dart';
import 'package:widget_article/player.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
body:(){
final player = Player(name: "Nasir", health: 10000000);
return Text(player.name + player.health.toString());
}()
);
}
}
With the IIFE, we can write logic, assign variables and just do normal coding and then directly assign widgets the variables we made. In the IIFE we made, we created an instance of the Player class we made called player then passed the hardcoded parameters, name and health to a text widget so we can actually see it in our UI (Heh, wondering why my health increased from 50 to that large number? I levelled up big time).
Note: Technically, you could just declare the player variable in the build method right above the Scaffold widget, but…After a few variables, you’ll get what I like to call “variable hell” in your build method.
Actual Widgets
Well, using Dart classes in our UI required quite the setup, but actual custom widgets just make life easier, when it comes to drawing stuff in the UI at least. Now, you might ask, “Why create custom widgets when we can just use the standard ones Flutter offers”, and the answer is simple “To be lazy”.
As developers, we hate repetition, as it deprives us of room to be lazy. Enough talking, let me explain with an illustration. Let’s create a nice-looking container in our home_page.dart file:
import 'package:flutter/material.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
body:Column(
children: [
Container(
width: 200,
height: 100,
padding: EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(12.0)
),
child: Center(
child:Text(
"Overly-styled box",
style: TextStyle(color: Colors.white)
)),
),
],
)
);
}
}
Also, don’t forget to make sure your main.dart file looks something similar to this:
import 'package:flutter/material.dart';
import 'package:widget_article/home_page.dart';
void main(){
runApp(
MaterialApp(
home: HomePage(),
)
);
}
Wow, that’s a really nice box. But what if we need five more? We’d have to copy and paste our Container widget five more times. Yeah, your home_page.dart would get bloated and wouldn’t be clean in any way whatsoever; that’s why we create a custom widget.
Create a new file called overly_styled_box.dart like so:
import 'package:flutter/material.dart';
class OverlyStyledBox extends StatelessWidget {
const OverlyStyledBox({super.key});
@override
Widget build(BuildContext context) {
return Container();
}
}
I don’t want to talk about the technical side of this too much so I’ll explain the key things you need to know for now. When creating custom widgets, about 80% of the time, they’ll always be StatelessWidgets. Think about Stateless Widgets as widgets that don’t have variables in their build method for now. You can assign them parameters because they’re just glorified classes that return widgets directly, so we don’t have to use IIFEs.
Using Custom Widgets In The UI
So, we’ve made our custom widget, but it returns a plain container — let’s change that by making it return our overly-styled box. Just copy and paste the container inside our home_page.dart file and paste it like so:
import 'package:flutter/material.dart';
class OverlyStyledBox extends StatelessWidget {
const OverlyStyledBox({super.key});
@override
Widget build(BuildContext context) {
return Container(
width: 200,
height: 100,
padding: EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(12.0)
),
child: Center(
child:Text(
"Fancy box",
style: TextStyle(color: Colors.white)
)),
);
}
}
Now that we have our complete custom container, we can delete the ugly container in our main.dart and replace it with an instance of OverlyStyledBox:
import 'package:flutter/material.dart';
import 'package:widget_article/overly_styled_box.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
body:Column(
children: [
OverlyStyledBox()
],
)
);
}
}
Isn’t it just beautiful? Well, not more beautiful than me, but you get the point. It makes our code look so clean. But what if we want to change the color or text of each OverlystyledBox? Our current setup hardcodes everything, but this is where your newly-attained knowledge of Dart classes comes in.
We’ll create two parameters for the OverlyStyledBox so we can pass in different arguments.
import 'package:flutter/material.dart';
class OverlyStyledBox extends StatelessWidget {
final String text;
final Color backgroundColor;
const OverlyStyledBox({
super.key,
required this.text,
required this.backgroundColor
});
@override
Widget build(BuildContext context) {
return Container(
width: 200,
height: 100,
padding: EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: backgroundColor, //Replaced "Colors.blue" with the backgroundColor parameter
borderRadius: BorderRadius.circular(12.0)
),
child: Center(
child:Text(
text, //Replaced "Fancy box" with the text parameter
style: TextStyle(color: Colors.white)
)),
);
}
}
Now, our OverlyStyledBox expects two arguments for its color and text parameters, so you’ll probably wanna check the home_page.dart file because, it won’t work unless you pass in the required arguments.
Note: Remember to always save your pages/files after every new change.
import 'package:flutter/material.dart';
import 'package:widget_article/overly_styled_box.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
body:Column(
children: [
OverlyStyledBox(text: "Fancy Box 1", backgroundColor: Colors.red,),
SizedBox(height: 8,),
OverlyStyledBox(text: "Fancy box 2", backgroundColor: Colors.purple)
],
)
);
}
}
Now, we can customize the text in the container and background color without writing any more boilerplate. I mean, just look at this:
I hope you were able to follow along and understand the basic concept of widgets in Flutter. Congratulations, you are now 10x lazier and can make reusable widgets with ease for your next projects. If this article helped, drop a comment! See you soon!


Top comments (0)