Over the past few weeks, I’ve taken on something that has been on my radar for a while: learning to build with Dart and Flutter. Mobile technologies have always fascinated me, and I wanted to know what it really meant to sit down, set up the environment, and start piecing widgets, logic, and state into something real.
Coming from a QA test automation background, I’m used to interacting with apps from the outside, probing them for weak points, automating their flows, and breaking things so we can fix them, and overall ensuring users get the best experience possible. Flutter flips that on its head. Instead of consuming an app, I’m learning how to compose one.
What is Flutter?
Flutter is a framework built from within Google for building cross-platform apps, and in Flutter you describe what the UI should look like given a specific state (state has suddenly become my favourite word).
Flutter to me feels like assembling Lego blocks but in code. As our tutors like to remind us: “Flutter is all about widgets.” Widgets stacked on widgets, combined and styled until they become a full interface.
Foundational Flutter: First Steps
The first step to mastery would be learning Dart. Learning Dart was quite familiar since I have a familiarisation with Java for the work I do with automation. Dart felt oddly similar but lighter and, in some ways, more fun.
Quizzla
Soon after an introduction to the Flutter framework and its basics, including handling navigation within Flutter, we were given a UI design to build with Flutter. I decided to take this on with an iterative format of building, learning, and rebuilding.
Starting the Flutter repo was the first step, which was with:
flutter create example_app
Starting a Flutter app, what you will usually see is a main.dart with some boilerplate code. If you are planning to build a mobile app with multiple screens, which I am sure most of us would be doing, the next step would be to go nuclear and then clear out the boilerplate code and leave only the important bits. Which would look like this:
import 'package:flutter/material.dart';
void main() {
runApp(
MyApp(),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
);
}
}
After this, the next step was building the splash screen, which I first built as a Stateless widget and then I had to switch it later on to a Stateful widget. Now what this means is for a Stateless widget (immutable), we do not expect the page to change over time based on certain actions, and for a Stateful widget (mutable), it means we expect the page to change over time.
Now why I had to do this was that after building my splash screen and then going on to build my onboarding screen, I quickly realised that for the best user experience it would be best for the splash screen to show for only a few seconds and then the onboarding screen to show. But after running my app by using the command:
flutter run
I discovered that it was not so. I had to research and discovered that to have this work properly would be to do a Future.delayed and set it to about 3,000 milliseconds, which is the generally recommended amount of time for which a splash screen should show by UX standards. This required a Stateful widget implementation.
Future.delayed(const Duration(milliseconds: 300), () {
Navigator.pushReplacementNamed(context, "/onboarding");
});
Oh, and lest I forget, a key thing to handling navigation—or the most efficient path I find—is handling routes through the main.dart, which is why you see in the Future.delayed snippet the string “/onboarding”. The initialRoute and routes properties define the navigation structure of the app. initialRoute sets the first screen shown when the app starts, which in my case is the splash screen, and the routes map connects the route names as strings to the widget builders like “/home” does for the Home screen.
return MaterialApp(
initialRoute: "/",
routes: {
"/": (context) => const SplashScreen(),
"/onboarding": (context) => const OnboardingScreen(),
"/about-me": (context) => const AboutMe(),
"/home": (context) => HomeScreen(),
"/quiz-categories": (context) => QuizCategoriesScreen(),
},
);
Next is building the onboarding screen, and this is where I started having some serious fun with widgets and styling widgets. During this stage, I came across a new favorite widget of mine, which is called SafeArea. I just make the body of all my screens a SafeArea. I liked SafeArea more when I first tried running one Flutter app on various devices using the command:
flutter run -d all
What SafeArea does is it wraps the whole screen under an area so you do not have to worry about widgets overflowing on other screen sizes.
I also discovered styling widgets and texts using style:. For example, in this child widget, I used style to design an ElevatedButton to make the button background color orange and give it a shape with a border radius:
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFFE950B),
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
The next screen I had to build was the About Me page, which introduced me to TextField widgets and using them to create forms. So what I decided to do was wrap my TextField widgets under a ListView widget.
ListView(
children: [
const Text("Enter your first name"),
const SizedBox(height: 8),
TextField(decoration: _fieldDecoration("First Name")),
const Text("Enter your last name"),
const SizedBox(height: 8),
TextField(decoration: _fieldDecoration("Last name")),
const Text("Descrbe Yourself"),
const SizedBox(height: 16),
TextField(
decoration: _fieldDecoration(
"Briefly Describe yourself and your interests",
),
),
],
),
Next was the Home screen, and this is where I hit my first real wall.
At first, I had a Container with a Column child that displayed the message “You have no score recorded yet.” On my emulator, this kept throwing overflow errors. At first, I thought it was padding or a missing SizedBox, but after some research I learned that the issue was with how Flutter was trying to fit everything into the screen height.
The quick fix was to wrap the whole body in a SingleChildScrollView, so the content could scroll vertically instead of being forced into a fixed height. That solved the overflow.
But then another issue appeared: the user had to start scrolling from the very top of the page, and the header (avatar and username) disappeared behind the emulator’s dynamic island. It worked, but it didn’t feel right. That’s when I realized Flutter requires you to think carefully about layouts, scrollables, and user experience.
I’ve learned that Flutter really is about stacking and styling widgets until they become something real.
Quizzla is still evolving, but every new screen I build teaches me something new about how Flutter apps come together.
This is where I’ll pause Part One of my journey. In Part Two, I’ll be diving into state management and exploring local storage and shared preferences.
And before I close, a special thanks to Victoria, Eyram, and Elvis for their time and effort in guiding us through Flutter.
Top comments (0)