Flutter Project: Build a Digital Business Card | Flutter Course (Ch. 5)
Welcome to Project 1! This is the chapter you've been working towards. We've learned the "why" (Chapter 1), the "language" (Chapter 2), the "tools" (Chapter 3), and the "logic" (Chapter 4). It's time to put it all together.
We're going to do something that might feel scary:
we're going to delete all the code in lib/main.dart
and build a new app from a completely blank file.
This is the most important step in your journey. The demo app is a great learning tool, but you don't *truly* learn until you've faced a blank screen and made something appear.
Our Goal: Build a beautiful, static "Digital Business
Card." This app will be 100% Stateless (it won't change,
it's just a "poster"). It will teach you the fundamentals of layout,
styling text, adding icons, and making your UI look clean and
professional.
By the end of this, you will have a beautiful app that you built entirely from scratch.
Ready? Let's build.
📖 Project 1: Table of Contents
- Step 1: The "Great Deletion" (Starting Fresh)
- Step 2: The "Boilerplate" (main, runApp, MyApp)
- Step 3: Our Home Page (A New StatelessWidget)
- Step 4: Setting the Scene (Scaffold & Background)
- Step 5: Layout is Everything (The Main `Column`)
- Step 6: The "Header" (Avatar & Text)
- Step 7: The "Info Cards" (Card & ListTile)
- The Final Code: Putting It All Together
- Conclusion & Your Challenge
Step 1: The "Great Deletion" (Starting Fresh)
Let's start. Open the project you made in Chapter 3 (e.g.,
my_first_app). If you want to start a new one, feel free
to run flutter create digital_business_card and open that
folder in VS Code.
Once it's open, go to the lib/main.dart file.
Select all the code (Ctrl+A or Cmd+A) and
press the Delete key.
Congratulations. You're now staring at a blank file. Don't panic. We're going to rebuild it, one piece at a time.
Step 2: The "Boilerplate" (main, runApp, MyApp)
First, we need to add back the "skeleton" of every Flutter app. This is
the main() function and the root MyApp
widget. This part should look familiar from Chapter 4.
Type (don't copy-paste!) this into your blank main.dart
file:
// 1. Import the Material library
import 'package:flutter/material.dart';
// 2. The "main" function, our app's entry point
void main() {
// 3. "runApp" tells Flutter to run our app
runApp(const MyApp());
}
// 4. Our "root" widget, the top of our widget tree
class MyApp extends StatelessWidget {
const MyApp({super.key});
// 5. The "build" method, the recipe for our app's UI
@override
Widget build(BuildContext context) {
// 6. Return MaterialApp
return MaterialApp(
// 7. This "home" property is what our app will show
home: Text('Hello, World!'),
);
}
}
Run your app (Ctrl+F5 or "Run Without Debugging"). You
should see a black screen with "Hello, World!" at the top-left.
This is ugly, but it's *working*. We've successfully built an app from scratch. Now, let's make it beautiful.
Step 3: Our Home Page (A New StatelessWidget)
It's bad practice to put all our code inside MyApp. Let's
create a new, separate widget for our business card.
A business card is a "poster." It's static. It doesn't need to count or change. Therefore, it will be a StatelessWidget.
Below your MyApp class, add a new class:
class BusinessCardApp extends StatelessWidget {
const BusinessCardApp({super.key});
@override
Widget build(BuildContext context) {
// We'll fill this in next
return Text('This is our business card app!');
}
}
Now, let's "wire up" this new widget. Go back to MyApp and
change the home property to use our new
BusinessCardApp widget.
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
// REMOVE the "debug" banner from the top-right
debugShowCheckedModeBanner: false,
// USE our new widget as the home screen!
home: const BusinessCardApp(),
);
}
}
I also added
debugShowCheckedModeBanner: false, which is a nice little
trick to remove that annoying "Debug" banner.
Save your file (Hot Reload!). You should now see "This is our business card app!" This proves our new widget is hooked up correctly.
Step 4: Setting the Scene (Scaffold & Background)
"Hello, World" is fine, but we need a real "page" to build on. In
Chapter 4, we learned the Scaffold widget is the
perfect "blank page" template.
Let's change the build method of
BusinessCardApp to return a Scaffold. We'll
also give it a background color.
class BusinessCardApp extends StatelessWidget {
const BusinessCardApp({super.key});
@override
Widget build(BuildContext context) {
// A Scaffold is a blank page layout
return Scaffold(
// We can set a background color for the whole page
backgroundColor: Colors.teal,
// The "body" is the main content of the page
body: Text('This is the body'),
);
}
}
Save this. You'll see the screen turn teal, and the text will be at
the top-left. We're getting somewhere! Colors.teal is a
great, classic color for this project.
Step 5: Layout is Everything (The Main `Column`)
Our business card needs to stack items vertically:
- The Profile Picture
- The Name
- The Title
- The Phone Number
- The Email Address
The perfect widget for stacking things vertically is the
Column widget. It's one of the most
important layout widgets you will ever learn.
A Column takes a children property, which is
a List of other widgets.
Let's replace our Text in the body with a
Column.
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.teal,
body: Column(
children: <Widget>[
Text('Child 1'),
Text('Child 2'),
Text('Child 3'),
],
),
);
}
Save and look at your app. You'll see the three Text
widgets stacked at the top-left of the screen.
This is good, but we want them in the center of the
screen. A Column has a property called
mainAxisAlignment that controls its vertical alignment.
body: Column(
// This tells the Column to center its children vertically
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Child 1'),
Text('Child 2'),
Text('Child 3'),
],
),
Save this. You'll see your text jump to the vertical center of the screen! (It's still stuck to the left, but we'll fix that soon).
Step 6: The "Header" (Avatar & Text)
Now let's replace our "Child 1, 2, 3" placeholders with our actual content.
Adding the `CircleAvatar`
First, we need a profile picture. Flutter has a wonderful widget for
this called CircleAvatar.
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CircleAvatar(
radius: 50.0,
// You can use a real image from the internet
// backgroundImage: NetworkImage('YOUR_IMAGE_URL_HERE'),
// Or, for a fallback, a simple icon
backgroundColor: Colors.white,
child: Icon(
Icons.person,
size: 50.0,
color: Colors.teal,
),
),
],
),
I've included two options here. The backgroundImage with
a NetworkImage is the best option if you have a URL to a
picture of yourself. If not, the backgroundColor and
child: Icon(...) is a great placeholder.
Save this. You'll see a big, 50-point-radius circle in the middle of your screen. But it's still stuck to the left! Why?
Because the Column itself is only as wide as its
children. It's centered *vertically*, but it's not centered
*horizontally*. To make its children center, we need to tell the
Column to stretch to fill the whole width of the screen.
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
// This tells the Column to stretch horizontally
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[ ... ]
),
This won't look like it did anything... *yet*. But now, any new
children (like Text) will be centered.
Wait, an easier way for now is to just center the CircleAvatar
itself. Let's do that. We can wrap our CircleAvatar
in a Center widget.
Pro Tip: In VS Code, click on the
CircleAvatar widget. A small lightbulb (💡) will appear.
Click it and select "Wrap with Center". This is a huge time-saver!
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(
child: CircleAvatar(
radius: 50.0,
backgroundColor: Colors.white,
child: Icon(
Icons.person,
size: 50.0,
color: Colors.teal,
),
),
),
],
),
Save! Now your avatar is in the perfect center.
Adding the Name `Text` & `TextStyle`
Right after the Center widget (but still inside the
Column's children list), let's add our name.
We'll introduce the style property of the
Text widget, which takes a TextStyle.
), // This is the closing parenthesis of the Center widget
Text(
'Your Name',
style: TextStyle(
fontSize: 40.0,
color: Colors.white,
fontWeight: FontWeight.bold,
fontFamily: 'Pacifico', // A fun, cursive font
),
),
],
Save this. You'll see your name appear under the avatar in a
beautiful, large, white, bold font. The 'Pacifico'
font is built into the Flutter Material library, so it's a fun one
to use without any setup.
Adding the Title `Text`
Now let's add a title right under the name. This one will be simpler,
but we'll add letterSpacing to make it look professional.
), // This is the closing parenthesis of the Name Text widget
Text(
'FLUTTER DEVELOPER',
style: TextStyle(
fontFamily: 'Source Sans Pro', // A clean, pro font
color: Colors.teal.shade100, // A lighter shade of teal
fontSize: 20.0,
letterSpacing: 2.5,
fontWeight: FontWeight.bold,
),
),
],
Save and look! It looks amazing. Colors.teal.shade100 is
a great way to get a lighter version of our main color. The
letterSpacing really makes it pop.
Step 7: The "Info Cards" (Card & ListTile)
Now for our contact info. We could just use more Text
widgets, but that would be boring. We could use a Row
with an Icon, but that's complicated (we'll learn it
later).
The *best* way to do this is with a Card widget and a
ListTile widget.
-
Card: A Material Design widget that creates a container with rounded corners and a drop shadow. -
ListTile: A "batteries-included" row. It has pre-defined "slots" for an icon (leading) and text (title). It's perfect for this.
Adding a `SizedBox` & `Divider`
Before we add the cards, let's add a bit of separation. A
SizedBox is the simplest layout widget. It's just an
empty, invisible box of a specific size. We'll use it as a "spacer."
We'll also add a Divider.
), // This is the closing parenthesis of the Title Text widget
SizedBox(
height: 20.0,
width: 150.0,
child: Divider(
color: Colors.teal.shade100,
),
),
],
Save, and you'll see a nice horizontal line under your title, giving it a very professional separation.
Creating the Phone `Card`
Now, let's add our first Card. We'll add it to the
Column's children list, right after the
SizedBox.
), // This is the closing parenthesis of the SizedBox
Card(
// Cards are white by default
color: Colors.white,
// Add some margin so it doesn't touch the screen edges
margin: EdgeInsets.symmetric(vertical: 10.0, horizontal: 25.0),
child: ListTile(
leading: Icon(
Icons.phone,
color: Colors.teal,
),
title: Text(
'+1 23 456 7890',
style: TextStyle(
color: Colors.teal.shade900,
fontFamily: 'Source Sans Pro',
fontSize: 20.0,
),
),
),
),
],
Let's break this down:
-
Card(...): We create the card. -
margin: ...: This is so important. It adds 10 pixels of space on the top/bottom (vertical) and 25 pixels on the left/right (horizontal). This is what keeps it from touching the edges of the screen. -
child: ListTile(...): The card's child is aListTile. -
leading: Icon(...): The "leading" slot (on the left) gets anIcon. We use the built-inIcons.phone. -
title: Text(...): The "title" slot gets ourTextwidget with the phone number.
Save and look! You have a beautiful, professional-looking info card.
Creating the Email `Card`
This is the best part. How do we make the email card? We just
copy-paste the phone Card and change the icon and text!
Add this right after the first Card:
), // This is the closing parenthesis of the first Card
Card(
color: Colors.white,
margin: EdgeInsets.symmetric(vertical: 10.0, horizontal: 25.0),
child: ListTile(
leading: Icon(
Icons.email,
color: Colors.teal,
),
title: Text(
'your.email@here.com',
style: TextStyle(
color: Colors.teal.shade900,
fontFamily: 'Source Sans Pro',
fontSize: 20.0,
),
),
),
),
],
Save your file. And you are done.
You have a complete, beautiful, professional-looking Digital Business
Card app. You built it from scratch. You've learned how to lay out
widgets with Column, Center, and
SizedBox. You've learned how to display images with
CircleAvatar, style Text with
TextStyle, and build beautiful rows with
Card and ListTile.
The Final Code: Putting It All Together
Here is the complete, final lib/main.dart file. You can
use this to check your work.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: const BusinessCardApp(),
);
}
}
class BusinessCardApp extends StatelessWidget {
const BusinessCardApp({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.teal,
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(
child: CircleAvatar(
radius: 50.0,
backgroundColor: Colors.white,
child: Icon(
Icons.person,
size: 50.0,
color: Colors.teal,
),
// OR (if you have a URL):
// backgroundImage: NetworkImage('https://...your-image.jpg'),
),
),
Text(
'Your Name',
style: TextStyle(
fontSize: 40.0,
color: Colors.white,
fontWeight: FontWeight.bold,
fontFamily: 'Pacifico',
),
),
Text(
'FLUTTER DEVELOPER',
style: TextStyle(
fontFamily: 'Source Sans Pro',
color: Colors.teal.shade100,
fontSize: 20.0,
letterSpacing: 2.5,
fontWeight: FontWeight.bold,
),
),
SizedBox(
height: 20.0,
width: 150.0,
child: Divider(
color: Colors.teal.shade100,
),
),
Card(
color: Colors.white,
margin: EdgeInsets.symmetric(vertical: 10.0, horizontal: 25.0),
child: ListTile(
leading: Icon(
Icons.phone,
color: Colors.teal,
),
title: Text(
'+1 23 456 7890',
style: TextStyle(
color: Colors.teal.shade900,
fontFamily: 'Source Sans Pro',
fontSize: 20.0,
),
),
),
),
Card(
color: Colors.white,
margin: EdgeInsets.symmetric(vertical: 10.0, horizontal: 25.0),
child: ListTile(
leading: Icon(
Icons.email,
color: Colors.teal,
),
title: Text(
'your.email@here.com',
style: TextStyle(
color: Colors.teal.shade900,
fontFamily: 'Source Sans Pro',
fontSize: 20.0,
),
),
),
),
],
),
);
}
}
Conclusion & Your Challenge
This is a massive milestone. You've now proven that you don't need the demo app. You can build your own UIs from scratch. You've learned more in this one chapter about *practical* layout than in all the previous chapters combined.
You are no longer just "learning" Flutter. You are *building* with Flutter.
Your Challenge
Don't stop here. Take this project and make it your own. Here are a few ideas:
-
Add a third
Cardfor your social media (like LinkedIn or GitHub). Find the rightIconfor it! -
Find a real profile picture of yourself online (you can upload one
to Imgur for
a quick, free URL) and use the
NetworkImageproperty on theCircleAvatar. -
Go to Google Fonts
and find two fonts you *really* like. Change the
fontFamilystrings in yourTextStylewidgets to use their names. (Note: For this to work on a real device, you'd have to install them inpubspec.yaml, but for web/some emulators, just changing the name sometimes works!)
In the next chapter, we'll formally dive into the
concept we've been using: Stateless vs.
Stateful widgets. We'll understand *why* this app was a
StatelessWidget and *when* we would need to use a
StatefulWidget again.
Great work. I'm proud of you. Keep building!
Comments
Post a Comment