Experiment 1: Setup and Language Fundamentals
- Objective: To set up a complete
Flutter development environment and write a simple program to understand
the core syntax and features of the Dart programming language.
Part A: Install Flutter and Dart SDK
This part
involves setting up all the necessary tools on your computer to build and run
Flutter applications. The Dart SDK is included with Flutter, so you only need
to install Flutter.
Procedure:
Step 1:
Get the Flutter SDK
- Go to the official Flutter
installation page: https://flutter.dev/get-started/install
- Select your operating
system: Windows, macOS, or Linux.
- Follow the instructions to
download the Flutter SDK zip file.
- Extract the zip file to a
permanent location on your computer where you want to store Flutter
(e.g., C:\flutter on Windows,
or ~/development/flutter on macOS/Linux). Do not install
it in a directory that requires elevated privileges, like C:\Program
Files\.
Step 2:
Update Your PATH Environment Variable
This is a critical step that allows you to run Flutter commands from any
terminal window.
- Find
the bin directory inside your extracted Flutter folder
(e.g., C:\flutter\bin).
- Add this full path to your
user PATH environment variable. The instructions for this are on
the official Flutter site, as it varies by OS.
Step 3:
Install an Editor and Plugins
You need a code editor to write your apps. The two most popular choices are VS
Code and Android Studio.
- For VS Code (Recommended for
beginners):
- Install Visual
Studio Code.
- Open VS Code, go to the Extensions view
(click the icon on the left sidebar).
- Search for and install
the Flutter extension (by Dart Code). This will also
automatically install the required Dart extension.
- For Android Studio:
- Install Android
Studio.
- Open Android Studio, go
to Settings/Preferences > Plugins.
- Search for and install
the Flutter plugin. This will also prompt you to install
the Dart plugin.
Step 4:
Run flutter doctor
This command checks your environment and displays a report of the status of
your Flutter installation.
- Open a new terminal
or command prompt window (to ensure it has the updated PATH).
- Run the command:
Generated
sh
flutter
doctor
- Review the output. It will
tell you if any required components are missing (like the Android SDK,
Chrome for web development, or Xcode for iOS development). Follow the
instructions it provides to install any missing dependencies until all
relevant sections have a green checkmark [✓].
Deliverable
for Part A:
- A screenshot of your terminal
after running the flutter doctor command, showing that the
"Flutter" and "Dart" sections are correctly set up.
Part B: Write a Simple Dart Program
This part
focuses purely on the Dart language, without any Flutter UI code. This helps
you understand the foundation upon which Flutter is built.
Procedure:
Step 1:
Create a Dart File
- Create a new folder on your
computer for this experiment (e.g., dart_basics).
- Open this folder in VS Code
(or your chosen editor).
- Create a new file
named basics.dart.
Step 2:
Write the Dart Code
Copy and paste the following code into your basics.dart file. Read
the comments carefully to understand what each line does.
Generated
dart
// The
main function is the entry point for all Dart programs.
void
main() {
// 1. VARIABLES and DATA TYPES
// Dart can infer types with 'var', but you
can also be explicit.
String name = "Alice"; //
Explicitly a String.
var age = 30; // Inferred as an integer (int).
double height = 5.9; // A floating-point number.
bool isStudent = true; // A boolean value.
// 'final' means the variable can only be set
once.
final String university = "Flutter
University";
// university = "Dart University";
// This would cause an error.
print("--- Variables ---");
print("Name: $name, Age: $age, Height:
$height, Is Student: $isStudent");
print("University: $university");
print(""); // For spacing
// 2. COLLECTIONS
// A List (an ordered group of items, similar
to an array).
List<String> subjects =
["Math", "Science", "History"];
// A Map (a collection of key-value pairs).
Map<String, int> scores = {
"Math": 95,
"Science": 88,
"History": 92,
};
print("--- Collections ---");
print("Subjects: $subjects");
print("Score in Math:
${scores['Math']}"); // Accessing a value by its key.
print("");
// 3. CONTROL FLOW (Loops and Conditionals)
print("--- Control Flow ---");
// A 'for-in' loop to iterate over the list
of subjects.
print("Student's Subjects:");
for (var subject in subjects) {
print("- $subject");
}
// An 'if-else' statement to check a
condition.
if (age >= 18) {
print("This person is an
adult.");
} else {
print("This person is a minor.");
}
print("");
// 4. FUNCTIONS
// Calling a custom function defined below.
int sum = addNumbers(10, 5);
print("--- Functions ---");
print("The sum of 10 and 5 is:
$sum");
// Calling a function with a named parameter.
greetUser(name: "Bob", greeting:
"Hi");
}
// A
simple function that takes two integers and returns their sum.
int
addNumbers(int a, int b) {
return a + b;
}
// A
function with named parameters.
void
greetUser({required String name, required String greeting}) {
print("$greeting, $name!");
}
Step 3:
Run the Program
- Open a terminal within VS Code
(Terminal > New Terminal).
- Make sure you are in the
correct directory (dart_basics).
- Run the program using
the dart command:
dart
basics.dart
You should
see the following output in your terminal:
OUTPUT:
---
Variables ---
Name:
Alice, Age: 30, Height: 5.9, Is Student: true
University:
Flutter University
---
Collections ---
Subjects:
[Math, Science, History]
Score in
Math: 95
---
Control Flow ---
Student's
Subjects:
- Math
- Science
- History
This
person is an adult.
---
Functions ---
The sum of
10 and 5 is: 15
Hi, Bob!
2. a)
Explore various Flutter widgets (Text, Image, Container, etc.).
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:
AllWidgetsScreen(),
);
}
}
class AllWidgetsScreen extends StatelessWidget {
const
AllWidgetsScreen({super.key});
@override
Widget build(BuildContext
context) {
// We use a Scaffold as
the main structure for our screen.
// This is taken from
your example #6.
return Scaffold(
backgroundColor:
Colors.grey[100],
appBar: AppBar(
title: const
Text('Fundamental Widgets Demo'),
centerTitle: true,
backgroundColor:
Colors.indigo,
),
// The body contains
the main content.
// We use
SingleChildScrollView to make sure our content is scrollable.
body:
SingleChildScrollView(
// We add some
padding around all the content.
padding: const
EdgeInsets.all(16.0),
// The Column widget
arranges its children vertically.
child: Column(
// We align
children to the start (left side) of the column.
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
// --- 1.
CONTAINER WIDGET ---
const Text('1.
Container', style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold)),
const
SizedBox(height: 10), // A little space
Container(
width: 250,
height: 150,
margin: const
EdgeInsets.all(20.0),
padding: const
EdgeInsets.all(10.0),
decoration:
BoxDecoration(
color:
Colors.amber[100],
borderRadius:
BorderRadius.circular(12.0),
border:
Border.all(
color:
Colors.amber,
width: 3,
),
boxShadow: [
BoxShadow(
color:
Colors.grey.withOpacity(0.5),
spreadRadius: 5,
blurRadius: 7,
offset:
const Offset(0, 3),
),
],
),
child: const
Center(
child:
Text('This is a styled Container'),
),
),
const
SizedBox(height: 30), // A larger space between sections
// --- 2. TEXT
WIDGET ---
const Text('2.
Text', style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold)),
const
SizedBox(height: 10),
const Text(
'Hello, Flutter
Developers!',
textAlign:
TextAlign.center,
style:
TextStyle(
color:
Colors.deepPurple,
fontSize: 28,
fontWeight:
FontWeight.bold,
fontStyle:
FontStyle.italic,
letterSpacing:
2.0,
),
),
const
SizedBox(height: 30),
// --- 3. IMAGE
WIDGET ---
const Text('3.
Image', style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold)),
const
SizedBox(height: 10),
const Text('From
Network:', style: TextStyle(fontStyle: FontStyle.italic)),
Image.network(
'https://flutter.dev/images/flutter-logo-sharing.png',
width: 200,
fit:
BoxFit.contain,
),
const
SizedBox(height: 30),
// --- 4. BUTTON
WIDGETS ---
const Text('4.
Buttons', style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold)),
const
SizedBox(height: 10),
// We use a Row
to show buttons side-by-side if there's space
Wrap(
spacing: 10,
children: [
ElevatedButton(
onPressed:
() {
print('ElevatedButton Tapped!');
},
style:
ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
),
child:
const Text('Tap Me'),
),
TextButton(
onPressed:
() {
print('TextButton Tapped!');
},
child:
const Text('Learn More'),
),
IconButton(
onPressed:
() {
print('IconButton Tapped!');
},
icon: const
Icon(Icons.favorite),
color:
Colors.red,
iconSize:
30,
),
],
),
const
SizedBox(height: 30),
// --- 5. ICON
WIDGET ---
const Text('5.
Icon', style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold)),
const
SizedBox(height: 10),
Icon(
Icons.settings_applications,
color:
Colors.grey[700],
size: 48.0,
),
const
SizedBox(height: 30),
],
),
),
// This is the
FloatingActionButton from your example #6.
floatingActionButton:
FloatingActionButton(
onPressed: () {
print('FloatingActionButton Tapped!');
},
child: const
Icon(Icons.add),
),
);
}
}
Output: Web
Mobile
B) Implement different layout structures using
Row, Column, and Stack widgets.
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(
title: 'Flutter Social
Post Demo', // Changed title
debugShowCheckedModeBanner: false, // Hide the debug banner
theme: ThemeData(
colorScheme:
ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
// Use Material 3 design if preferred
),
// Here, we're
replacing the MyHomePage with your SocialPostCard
home: Scaffold( // We
use a Scaffold directly here for the basic app structure
backgroundColor:
Colors.grey[300], // Background color for the whole screen
appBar: AppBar(
title: const
Text('Combined Layout Demo'), // Title for the app bar
backgroundColor:
Theme.of(context).colorScheme.inversePrimary, // Use theme color
),
body: const Center(
child:
SocialPostCard(), // Your SocialPostCard is now the main content
),
),
);
}
}
// Your SocialPostCard widget definition starts here
class SocialPostCard extends StatelessWidget {
const
SocialPostCard({super.key});
@override
Widget build(BuildContext
context) {
return Container(
constraints: const
BoxConstraints(maxWidth: 400),
margin: const
EdgeInsets.all(16.0),
padding: const
EdgeInsets.all(8.0),
decoration:
BoxDecoration(
color: Colors.white,
borderRadius:
BorderRadius.circular(12.0),
boxShadow: const [
BoxShadow(
color:
Colors.black12,
blurRadius: 10.0,
spreadRadius:
2.0,
)
],
),
child: Column(
mainAxisSize:
MainAxisSize.min,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Padding(
padding: const
EdgeInsets.all(8.0),
child: Row(
children: const
[
CircleAvatar(
backgroundImage: NetworkImage('https://i.pravatar.cc/150?img=3'),
),
SizedBox(width: 10),
Text('John
Doe', style: TextStyle(fontWeight: FontWeight.bold)),
Spacer(),
Icon(Icons.more_horiz),
],
),
),
Stack(
children: [
ClipRRect(
borderRadius:
BorderRadius.circular(8.0),
child:
Image.network(
'https://images.unsplash.com/photo-1579532537598-459ecdaf39cc',
fit:
BoxFit.cover,
),
),
],
),
Padding(
padding: const
EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
IconButton(icon: const Icon(Icons.favorite_border), onPressed: () {}),
const
Text('1,234'),
],
),
Row(
children: [
IconButton(icon: const Icon(Icons.mode_comment_outlined), onPressed: ()
{}),
const
Text('56'),
],
),
IconButton(icon: const Icon(Icons.send_outlined), onPressed: () {}),
],
),
),
],
),
);
}
}
OUTPUT: WEB
MOBILE
3.
Responsive UI and Media Queries
a) Design a
responsive UI that adapts to different screen sizes.
b)
Implement media queries and breakpoints for responsiveness.
import 'package:flutter/material.dart';
// The main function is the entry point for the app.
void main() {
runApp(const MyApp());
}
// MyApp is the root widget of the application.
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext
context) {
// MaterialApp provides
the core app structure and theming.
return MaterialApp(
debugShowCheckedModeBanner: false, // Hides the debug banner
title: 'Responsive
Demo', // Title for the app
theme: ThemeData(
// You can customize
your app's theme here.
// For example, using
a seed color for Material 3 design.
colorScheme:
ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
// Opt-in to Material 3
),
// We set the home
property to your responsive screen widget.
home: const
ResponsiveLayoutScreen(),
);
}
}
// ▼▼▼ YOUR RESPONSIVE LAYOUT SCREEN WIDGET STARTS HERE ▼▼▼
class ResponsiveLayoutScreen extends StatelessWidget {
const
ResponsiveLayoutScreen({super.key});
@override
Widget build(BuildContext
context) {
// Define a breakpoint.
600 logical pixels is a common breakpoint for phone/tablet.
const int
mobileBreakpoint = 600;
return Scaffold(
appBar: AppBar(
title: const
Text('Responsive Layout'),
backgroundColor:
Theme.of(context).colorScheme.inversePrimary, // Uses theme color
),
body: LayoutBuilder(
builder:
(BuildContext context, BoxConstraints constraints) {
// Use the
constraints from LayoutBuilder to check the width
if
(constraints.maxWidth > mobileBreakpoint) {
// WIDER screen:
Use a Row layout (e.g., tablet, desktop)
return
_buildWideLayout();
} else {
// NARROWER
screen: Use a Column layout (e.g., mobile phone)
return
_buildNarrowLayout();
}
},
),
);
}
// This widget is built for
screens WIDER than 600px
Widget _buildWideLayout() {
return Row(
children: [
Expanded(
flex: 1, // Takes
up 1/3 of the space
child: Container(
color:
Colors.blue,
child: const
Center(
child: Text(
'Sidebar',
style:
TextStyle(color: Colors.white, fontSize: 24),
),
),
),
),
Expanded(
flex: 2, // Takes
up 2/3 of the space
child: Container(
color:
Colors.white,
child: const
Center(
child: Text(
'Main
Content',
style:
TextStyle(fontSize: 24),
),
),
),
),
],
);
}
// This widget is built for
screens NARROWER than 600px
Widget _buildNarrowLayout()
{
return Column(
children: [
Container(
height: 200, //
Fixed height for sidebar in narrow layout
color: Colors.blue,
child: const
Center(
child: Text(
'Sidebar',
style:
TextStyle(color: Colors.white, fontSize: 24),
),
),
),
Expanded(
child: Container(
color:
Colors.white,
child: const
Center(
child: Text(
'Main
Content',
style:
TextStyle(fontSize: 24),
),
),
),
),
],
);
}
}
OUTPUT: WEB
MOBILE
4.
Navigation with Navigator and Named Routes
a) Set up
navigation between different screens using Navigator.
b)
Implement navigation with named routes.
import 'package:flutter/material.dart';
// The main function is the entry point for the app.
void main() {
runApp(const MyApp());
}
// MyApp is the root widget of the application.
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext
context) {
// MaterialApp provides
the core app structure and theming.
return MaterialApp(
debugShowCheckedModeBanner: false, // Hides the debug banner
title: 'Navigation
Demo', // Title for the app
theme: ThemeData(
colorScheme:
ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
// Opt-in to Material 3 design
),
// The `home` property
defines the very first screen of the app.
home: const
HomeScreen(),
);
}
}
// --- SCREEN 1: The Home Screen ---
class HomeScreen extends StatelessWidget {
const
HomeScreen({super.key});
@override
Widget build(BuildContext
context) {
return Scaffold(
appBar: AppBar(
title: const
Text('Home Screen (Page 1)'),
backgroundColor:
Theme.of(context).colorScheme.inversePrimary, // Use theme color
),
body: Center(
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
const Text(
'Welcome to the
first screen!',
style:
TextStyle(fontSize: 18),
),
const
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
// This is
the core navigation logic.
//
Navigator.push() adds a new screen to the top of the "stack".
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const DetailScreen()),
);
},
child: const
Text('Go to Details'),
),
],
),
),
);
}
}
// --- SCREEN 2: The Detail Screen ---
class DetailScreen extends StatelessWidget {
const
DetailScreen({super.key});
@override
Widget build(BuildContext
context) {
return Scaffold(
appBar: AppBar(
// The AppBar in
MaterialPageRoute automatically includes a back button!
title: const
Text('Detail Screen (Page 2)'),
backgroundColor:
Theme.of(context).colorScheme.inversePrimary, // Use theme color
),
body: Center(
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
const Text(
'You have
successfully navigated to the second screen.',
textAlign:
TextAlign.center,
style:
TextStyle(fontSize: 18),
),
const
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
//
Navigator.pop() removes the current screen from the "stack",
// revealing
the screen that was below it (the HomeScreen).
Navigator.pop(context);
},
child: const
Text('Go Back'),
),
],
),
),
);
}
}
OUTPUT: WEB
MOBILE:
b)
Navigation with Named Routes
Named routes are cleaner for larger apps. You define all your
routes in one place and navigate using a string name.
import 'package:flutter/material.dart';
// The main function is the entry point for the
app.
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const
MyApp({super.key});
@override
Widget
build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false, // Hides the debug banner
title: 'Named Navigation Demo', // Title for the app
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true, // Opt-in to Material 3 design
),
//
The first screen to show when the app starts.
initialRoute: '/',
//
The map of all available named routes and the widgets they map to.
routes: {
'/': (context) => const HomeScreen(),
'/details': (context) => const DetailScreen(),
},
);
}
}
// --- Screen Definition for the '/' route
(Home Screen) ---
class HomeScreen extends StatelessWidget {
const
HomeScreen({super.key});
@override
Widget
build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Home Screen (/)'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary, // Use
theme color
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Welcome to the home screen!',
style: TextStyle(fontSize: 18),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
// Navigate using the route's name.
// Flutter looks up '/details' in the `routes` map and builds a
DetailScreen.
Navigator.pushNamed(context, '/details');
},
child: const Text('Go to Details with Named Route'),
),
],
),
),
);
}
}
// --- Screen Definition for the '/details'
route (Detail Screen) ---
class DetailScreen extends StatelessWidget {
const
DetailScreen({super.key});
@override
Widget
build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// The back button is automatically added by Flutter when using
push/pushNamed
title: const Text('Detail Screen (/details)'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary, // Use
theme color
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'You have arrived at the details screen.',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 18),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
// The back button works the same way: it pops the current route.
Navigator.pop(context);
},
child: const Text('Go Back'),
),
],
),
),
);
}
}
OUTPUT: WEB
MOBILE:
5. Stateful
and Stateless Widgets & State Management
a) Learn
about stateful and stateless widgets.
b)
Implement state management using setState and Provider.
Concept:
StatelessWidget: A widget whose state cannot change over time. Its
properties are final and are set by its parent. It is rebuilt only when its
input properties change. Use it for static UI elements.
StatefulWidget: A widget that has mutable state. When its internal
state changes, it can be redrawn on the screen by calling the setState()
method. Use it for any UI that needs to be updated dynamically (e.g., a
counter, a checkbox, form input).
b) Implementation with setState (Local State)
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(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme:
ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: const
CounterScreen(title: 'setState Demo'), // Changed to CounterScreen
);
}
}
class CounterScreen extends StatefulWidget { // Renamed from
MyHomePage to CounterScreen
const CounterScreen({Key?
key, required this.title}) : super(key: key); // Added title
final String title; //
Added title
@override
State<CounterScreen>
createState() => _CounterScreenState(); // Changed state class name
}
class _CounterScreenState extends State<CounterScreen> { //
Renamed from _MyHomePageState to _CounterScreenState
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext
context) {
return Scaffold(
appBar: AppBar(
backgroundColor:
Theme.of(context).colorScheme.inversePrimary,
title:
Text(widget.title), // Using widget.title
),
body: Center(
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
children:
<Widget>[
const Text('You
have pushed the button this many times:'),
Text(
'$_counter',
style:
Theme.of(context).textTheme.headlineMedium, // Changed from headline4 to
headlineMedium
),
],
),
),
floatingActionButton:
FloatingActionButton(
onPressed:
_incrementCounter,
tooltip: 'Increment',
// Added tooltip
child: const
Icon(Icons.add),
),
);
}
}
OUTPUT: WEB
MOBILE:
b)
Implementation with Provider (App-wide State)
Provider is a state management solution that allows you to share
state across multiple widgets without passing it down the tree manually.
Step 1: Add the provider package to
pubspec.yaml
dependencies:
flutter:
sdk: flutter
provider: ^6.0.3 # use latest version
Step 2: create file image_visibility_model.dart
import 'package:flutter/foundation.dart';
class ImageVisibilityModel with ChangeNotifier {
bool _isVisible = true; // Initial state: image is visible
bool get isVisible => _isVisible;
void toggleVisibility() {
_isVisible = !_isVisible; // Toggle the state
notifyListeners(); // Notify all listening widgets to
rebuild
}
}
Step 3: add file main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'image_visibility_model.dart'; // Import our model
void main() {
runApp(
// Provide the ImageVisibilityModel to the entire app
ChangeNotifierProvider(
create: (context) =>
ImageVisibilityModel(),
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Image Provider Demo',
theme: ThemeData(
colorScheme:
ColorScheme.fromSeed(seedColor: Colors.teal),
useMaterial3: true,
),
home: const ImageScreen(), // Our main screen
for the image
);
}
}
// Our ImageScreen will be Stateless, relying on Provider for state
class ImageScreen extends StatelessWidget {
const ImageScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Image Provider
Demo'),
backgroundColor:
Theme.of(context).colorScheme.inversePrimary,
),
body: Center(
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
children: <Widget>[
const Text(
'Tap the button to
toggle image visibility:',
style:
TextStyle(fontSize: 18),
),
const SizedBox(height:
20),
// Step 4: Use Consumer
to listen for changes and display the image
Consumer<ImageVisibilityModel>(
builder: (context,
imageModel, child) {
return
AnimatedOpacity( // Adds a nice fade animation
opacity: imageModel.isVisible ? 1.0 : 0.0,
duration: const Duration(milliseconds: 500),
child: Image.network(
'https://flutter.dev/assets/images/dash/dash-square-white.png', // A
simple Flutter image
width: 200,
height: 200,
fit: BoxFit.contain,
),
);
},
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// Step 4: Access the model and
call the method to change state
// `listen: false` because we
are only modifying state, not rebuilding this widget.
Provider.of<ImageVisibilityModel>(context, listen:
false).toggleVisibility();
},
tooltip: 'Toggle Image',
child: const Icon(Icons.visibility),
),
);
}
}
OUTPUT: WEB
MOBILE
6. Custom
Widgets and Styling
a) Create
custom widgets for specific UI elements.
b) Apply
styling using themes and custom styles.
Concept:
Custom Widgets: Break down complex UIs into smaller, reusable
components. This follows the "Don't Repeat Yourself" (DRY) principle
and makes your code much cleaner.
Themes: ThemeData allows you to define a consistent color scheme,
font styles, and widget styles for your entire app. This ensures a consistent
look and feel.
a) Creating a Custom Widget
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(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme:
ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const
MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const
MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage>
createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext
context) {
return Scaffold(
appBar: AppBar(
backgroundColor:
Theme.of(context).colorScheme.inversePrimary,
title:
Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
children:
<Widget>[
const Text(
'You have
pushed the button this many times:',
),
Text(
'$_counter',
style:
Theme.of(context).textTheme.headlineMedium,
),
const
SizedBox(height: 30), // Add some space
CustomAppButton(
text: 'Click
Me!',
onPressed: () {
print('Custom
button clicked!');
// You can
also increment the counter with this button
_incrementCounter();
},
),
],
),
),
floatingActionButton:
FloatingActionButton(
onPressed:
_incrementCounter,
tooltip: 'Increment',
child: const
Icon(Icons.add),
),
);
}
}
class CustomAppButton extends StatelessWidget {
final String text;
final VoidCallback
onPressed;
const CustomAppButton({
Key? key,
required this.text,
required this.onPressed,
}) : super(key: key);
@override
Widget build(BuildContext
context) {
return ElevatedButton(
onPressed: onPressed,
style:
ElevatedButton.styleFrom(
// Use the app's
primary color from the theme
backgroundColor:
Theme.of(context).colorScheme.primary, // For Material 3, use backgroundColor
foregroundColor:
Theme.of(context).colorScheme.onPrimary, // For Material 3, use foregroundColor
padding: const
EdgeInsets.symmetric(horizontal: 32, vertical: 16),
textStyle: const
TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
child: Text(text),
);
}
}
OUTPUT: WEB
MOBILE
b) Applying
Themes
Define a theme in your MaterialApp.
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(
title: 'Theme Demo',
// Define the theme for
the entire app
theme: ThemeData(
brightness:
Brightness.light,
// primarySwatch is
deprecated in favor of colorScheme in Material 3
// For a similar
effect, you would define a ColorScheme
colorScheme:
ColorScheme.fromSeed(seedColor: Colors.indigo),
fontFamily:
'Georgia', // Make sure you've added this font to pubspec.yaml
useMaterial3: true,
// Enable Material 3
// Define default
text styles using the new Material 3 naming conventions
textTheme: const
TextTheme(
displayLarge:
TextStyle(fontSize: 72.0, fontWeight: FontWeight.bold), // Replaces headline1
headlineLarge:
TextStyle(fontSize: 36.0, fontStyle: FontStyle.italic), // Replaces headline6
bodyMedium:
TextStyle(fontSize: 14.0, fontFamily: 'Hind'), // Replaces bodyText2
),
),
home: const
HomeScreen(),
);
}
}
class HomeScreen extends StatelessWidget {
const
HomeScreen({super.key});
@override
Widget build(BuildContext
context) {
return Scaffold(
appBar: AppBar(
title: const
Text('Theme Demo Home'),
backgroundColor:
Theme.of(context).colorScheme.primary, // Accesses the primary color from the
colorScheme
),
body: Center(
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
children:
<Widget>[
Text(
'This uses the
theme headlineLarge style',
style:
Theme.of(context).textTheme.headlineLarge, // Accesses the custom headlineLarge
style
),
const
SizedBox(height: 20),
Text(
'This uses the
theme bodyMedium style',
style:
Theme.of(context).textTheme.bodyMedium, // Accesses the custom bodyMedium style
),
const
SizedBox(height: 20),
Container(
width: 150,
height: 150,
color:
Theme.of(context).colorScheme.primary, // Accesses the primary color from the
colorScheme
child: Center(
child: Text(
'Primary
Color',
style:
TextStyle(
color:
Theme.of(context).colorScheme.onPrimary, // Text color that contrasts with
primary
fontWeight: FontWeight.bold,
),
),
),
),
],
),
),
);
}
}
OUTPUT: WEB
MOBILE
7. Forms,
Validation, and Error Handling
a) Design a
form with various input fields.
b)
Implement form validation and error handling.
Concept:
Forms are used to collect user input. Flutter's Form widget,
combined with TextFormField, makes it easy to manage input and validation. A
GlobalKey<FormState> is used to identify and control the Form.
Implementation:
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(
title: 'Advanced Form Demo',
theme: ThemeData(
colorScheme:
ColorScheme.fromSeed(seedColor: Colors.teal),
useMaterial3: true,
),
home: Scaffold(
appBar: AppBar(title: const
Text('Registration Form')),
body: const SingleChildScrollView(
// Added SingleChildScrollView
padding: EdgeInsets.all(16.0),
child: AdvancedForm(),
),
),
);
}
}
class AdvancedForm extends StatefulWidget {
const AdvancedForm({Key? key}) : super(key: key);
@override
State<AdvancedForm> createState() =>
_AdvancedFormState();
}
class _AdvancedFormState extends State<AdvancedForm> {
final _formKey = GlobalKey<FormState>();
// Text Controllers for input fields
final TextEditingController _nameController =
TextEditingController();
final TextEditingController _emailController =
TextEditingController();
final TextEditingController _passwordController =
TextEditingController();
final TextEditingController _confirmPasswordController =
TextEditingController();
final TextEditingController _phoneController =
TextEditingController();
// State variables for other input types
String? _selectedGender;
bool _agreedToTerms = false;
DateTime? _selectedDate;
@override
void dispose() {
_nameController.dispose();
_emailController.dispose();
_passwordController.dispose();
_confirmPasswordController.dispose();
_phoneController.dispose();
super.dispose();
}
// Function to show date picker
Future<void> _selectDate(BuildContext context) async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: _selectedDate ?? DateTime.now(),
firstDate: DateTime(1900),
lastDate: DateTime.now(),
);
if (picked != null && picked !=
_selectedDate) {
setState(() {
_selectedDate = picked;
});
}
}
// Function to handle form submission
void _submitForm() {
if (_formKey.currentState!.validate()) {
if (!_agreedToTerms) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Please
agree to the terms and conditions'),
),
);
return;
}
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content:
Text('Processing Registration...')),
);
// Print all collected data
print('Name: ${_nameController.text}');
print('Email: ${_emailController.text}');
print(
'Password:
${_passwordController.text}',
); // In real app, never print password!
print('Phone: ${_phoneController.text}');
print('Gender: $_selectedGender');
print(
'Date of Birth:
${_selectedDate?.toLocal().toString().split(' ')[0]}',
);
print('Agreed to Terms: $_agreedToTerms');
// In a real application, you would send this
data to an API or database.
// For demonstration, we'll just show a
success message after a delay.
Future.delayed(const Duration(seconds: 2), ()
{
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content:
Text('Registration Successful!')),
);
_formKey.currentState?.reset(); //
Clear the form
_nameController.clear();
_emailController.clear();
_passwordController.clear();
_confirmPasswordController.clear();
_phoneController.clear();
setState(() {
_selectedGender = null;
_agreedToTerms = false;
_selectedDate = null;
});
});
}
}
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
crossAxisAlignment:
CrossAxisAlignment.stretch,
children: <Widget>[
// Full Name Field
TextFormField(
controller:
_nameController,
keyboardType:
TextInputType.name,
decoration: const
InputDecoration(
labelText: 'Full
Name',
hintText: 'Enter
your full name',
prefixIcon: Icon(Icons.person),
border:
OutlineInputBorder(),
),
validator: (value) {
if (value == null
|| value.isEmpty) {
return
'Please enter your full name';
}
return null;
},
),
const SizedBox(height: 16.0),
// Email Field
TextFormField(
controller:
_emailController,
keyboardType:
TextInputType.emailAddress,
decoration: const
InputDecoration(
labelText: 'Email
Address',
hintText: 'Enter
your email',
prefixIcon:
Icon(Icons.email),
border:
OutlineInputBorder(),
),
validator: (value) {
if (value == null
|| value.isEmpty) {
return
'Please enter your email address';
}
if
(!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value)) {
return
'Please enter a valid email address';
}
return null;
},
),
const SizedBox(height: 16.0),
// Password Field
TextFormField(
controller:
_passwordController,
obscureText: true,
keyboardType:
TextInputType.visiblePassword,
decoration: const
InputDecoration(
labelText:
'Password',
hintText: 'Create
a password',
prefixIcon:
Icon(Icons.lock),
border:
OutlineInputBorder(),
),
validator: (value) {
if (value == null
|| value.isEmpty) {
return
'Please create a password';
}
if (value.length
< 8) {
return
'Password must be at least 8 characters long';
}
return null;
},
),
const SizedBox(height: 16.0),
// Confirm Password Field
TextFormField(
controller: _confirmPasswordController,
obscureText: true,
keyboardType:
TextInputType.visiblePassword,
decoration: const
InputDecoration(
labelText:
'Confirm Password',
hintText:
'Re-enter your password',
prefixIcon:
Icon(Icons.lock_reset),
border:
OutlineInputBorder(),
),
validator: (value) {
if (value == null
|| value.isEmpty) {
return
'Please confirm your password';
}
if (value !=
_passwordController.text) {
return
'Passwords do not match';
}
return null;
},
),
const SizedBox(height: 16.0),
// Phone Number Field
TextFormField(
controller:
_phoneController,
keyboardType:
TextInputType.phone,
decoration: const
InputDecoration(
labelText: 'Phone
Number',
hintText: 'e.g.,
+1 123-456-7890',
prefixIcon:
Icon(Icons.phone),
border:
OutlineInputBorder(),
),
validator: (value) {
if (value == null
|| value.isEmpty) {
return
'Please enter your phone number';
}
if
(!RegExp(r'^\+?[0-9]{10,15}$').hasMatch(value)) {
// Basic
international phone regex
return
'Please enter a valid phone number';
}
return null;
},
),
const SizedBox(height: 16.0),
// Gender Dropdown
DropdownButtonFormField<String>(
value: _selectedGender,
decoration: const
InputDecoration(
labelText:
'Gender',
prefixIcon:
Icon(Icons.people),
border:
OutlineInputBorder(),
),
hint: const Text('Select
your gender'),
items:
<String>['Male', 'Female', 'Non-binary', 'Prefer not to say']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
})
.toList(),
onChanged: (String?
newValue) {
setState(() {
_selectedGender = newValue;
});
},
validator: (value) {
if (value == null)
{
return
'Please select your gender';
}
return null;
},
),
const SizedBox(height: 16.0),
// Date of Birth Field (with
DatePicker)
InkWell(
onTap: () =>
_selectDate(context),
child: InputDecorator(
decoration: const
InputDecoration(
labelText:
'Date of Birth',
prefixIcon:
Icon(Icons.calendar_today),
border:
OutlineInputBorder(),
),
child: Text(
_selectedDate == null
? 'Select your date of birth'
:
'${_selectedDate!.toLocal().day}/${_selectedDate!.toLocal().month}/${_selectedDate!.toLocal().year}',
style:
Theme.of(context).textTheme.titleMedium,
),
),
),
const SizedBox(height: 16.0),
// Checkbox for Terms and
Conditions
Row(
children: [
Checkbox(
value:
_agreedToTerms,
onChanged:
(bool? newValue) {
setState(() {
_agreedToTerms = newValue ?? false;
});
},
),
const Expanded(
child:
Text('I agree to the terms and conditions'),
),
],
),
const SizedBox(height: 24.0),
// Submit Button
ElevatedButton(
onPressed: _submitForm,
style:
ElevatedButton.styleFrom(
padding: const
EdgeInsets.symmetric(vertical: 16.0),
textStyle: const
TextStyle(fontSize: 20.0),
),
child: const
Text('Register'),
),
],
),
);
}
}
OUTPUT: WEB
MOBILE
8.
Animations
a) Add
animations to UI elements using Flutter's animation framework.
b)
Experiment with different types of animations (fade, slide, etc.).
Concept:
Animations make your app feel more fluid and engaging. Flutter has
two main types of animations:
Implicit Animations: The easy way. Use widgets like
AnimatedContainer. You just change a property (like width or color), and the
widget automatically animates to the new value over a given duration.
Explicit Animations: More control. You use an AnimationController
to define the timing and an Animation object (like a Tween) to define the value
range. This is for more complex, custom animations.
a) Implicit Animation Example (AnimatedContainer)
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(
title: 'Animation
Demos',
theme: ThemeData(
colorScheme:
ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const
AnimationSelectionScreen(),
);
}
}
class AnimationSelectionScreen extends StatelessWidget {
const
AnimationSelectionScreen({super.key});
@override
Widget build(BuildContext
context) {
return Scaffold(
appBar: AppBar(
title: const
Text('Flutter Animation Demos'),
backgroundColor:
Theme.of(context).colorScheme.inversePrimary,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children:
<Widget>[
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const AnimatedContainerDemo()),
);
},
child: const
Text('Implicit Animation (AnimatedContainer)'),
),
const
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const FadeTransitionDemo()),
);
},
child: const
Text('Explicit Animation (FadeTransition)'),
),
],
),
),
);
}
}
// a) Implicit Animation Example (AnimatedContainer)
class AnimatedContainerDemo extends StatefulWidget {
const AnimatedContainerDemo({Key?
key}) : super(key: key);
@override
State<AnimatedContainerDemo> createState() =>
_AnimatedContainerDemoState();
}
class _AnimatedContainerDemoState extends
State<AnimatedContainerDemo> {
bool _isBig = false;
double _width = 100.0;
double _height = 100.0;
Color _color = Colors.blue;
BorderRadiusGeometry
_borderRadius = BorderRadius.circular(0.0);
void _toggleAnimation() {
setState(() {
_isBig = !_isBig;
_width = _isBig ? 200.0
: 100.0;
_height = _isBig ?
200.0 : 100.0;
_color = _isBig ?
Colors.red : Colors.blue;
_borderRadius =
BorderRadius.circular(_isBig ? 25.0 : 0.0);
});
}
@override
Widget build(BuildContext
context) {
return Scaffold(
appBar: AppBar(
title: const
Text("Implicit Animation (AnimatedContainer)"),
backgroundColor:
Theme.of(context).colorScheme.inversePrimary,
),
body: Center(
child:
GestureDetector(
onTap:
_toggleAnimation,
child:
AnimatedContainer(
width: _width,
height: _height,
decoration:
BoxDecoration(
color: _color,
borderRadius:
_borderRadius,
),
// Define how
long the animation should take.
duration: const
Duration(seconds: 1),
// Define the
animation curve.
curve:
Curves.fastOutSlowIn,
child: const
Center(
child:
Text("Tap me!", style: TextStyle(color: Colors.white))),
),
),
),
);
}
}
// b) Explicit Animation Example (FadeTransition)
class FadeTransitionDemo extends StatefulWidget {
const
FadeTransitionDemo({Key? key}) : super(key: key);
@override
State<FadeTransitionDemo> createState() =>
_FadeTransitionDemoState();
}
// Add 'with TickerProviderStateMixin'
class _FadeTransitionDemoState extends
State<FadeTransitionDemo>
with
TickerProviderStateMixin {
late AnimationController
_controller;
late
Animation<double> _animation;
@override
void initState() {
super.initState();
// 1. Create the
AnimationController
_controller =
AnimationController(
duration: const
Duration(seconds: 2),
vsync: this, // The
TickerProvider
);
// 2. Create the
Animation (a Tween)
_animation =
CurvedAnimation(
parent: _controller,
curve: Curves.easeIn,
);
// Start the animation.
We can also make it repeat or reverse.
_controller.forward();
}
@override
void dispose() {
_controller.dispose(); //
Always dispose of the controller!
super.dispose();
}
@override
Widget build(BuildContext
context) {
return Scaffold(
appBar: AppBar(
title: const
Text("Explicit Animation (FadeTransition)"),
backgroundColor:
Theme.of(context).colorScheme.inversePrimary,
),
body: Center(
// 3. Use a
Transition widget (e.g., FadeTransition, SlideTransition)
child:
FadeTransition(
opacity:
_animation, // Link the animation to the opacity property
child: const
Padding(
padding:
EdgeInsets.all(8.0),
child:
FlutterLogo(size: 200.0),
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// You can reverse,
forward, or repeat the animation
if
(_controller.status == AnimationStatus.completed) {
_controller.reverse();
} else {
_controller.forward();
}
},
child: const
Icon(Icons.play_arrow),
),
);
}
}
OUTPUT: WEB
MOBILE
9. Fetching
Data from a REST API
a) Fetch
data from a REST API.
b) Display
the fetched data in a meaningful way in the UI.
Concept:
Most apps need to get data from the internet. The http package is
the standard for making network requests. Since network requests are
asynchronous (they don't complete instantly), we use Futures and the
async/await syntax. FutureBuilder is a fantastic widget for building UI based
on the state of a Future (loading, has data, has error).
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert'; // For json.decode
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext
context) {
return MaterialApp(
title: 'API Data Fetch
Demo',
theme: ThemeData(
colorScheme:
ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const
UserListScreen(),
);
}
}
// Data Model for a single user
class User {
final int id;
final String name;
final String email;
final String? phone; //
Phone can be optional
final String? website; //
Website can be optional
User({
required this.id,
required this.name,
required this.email,
this.phone,
this.website,
});
factory
User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'],
name: json['name'],
email: json['email'],
phone: json['phone'],
website:
json['website'],
);
}
}
class UserListScreen extends StatefulWidget {
const
UserListScreen({super.key});
@override
State<UserListScreen>
createState() => _UserListScreenState();
}
class _UserListScreenState extends State<UserListScreen> {
late
Future<List<User>> futureUsers;
@override
void initState() {
super.initState();
futureUsers =
fetchUsers();
}
// Function to fetch users
from the API
Future<List<User>> fetchUsers() async {
final response = await
http.get(
Uri.parse('https://jsonplaceholder.typicode.com/users'),
);
if (response.statusCode
== 200) {
// If the server
returns a 200 OK response, parse the JSON.
List<dynamic>
userJson = json.decode(response.body);
return
userJson.map((json) => User.fromJson(json)).toList();
} else {
// If the server did
not return a 200 OK response,
// then throw an
exception.
throw Exception('Failed
to load users');
}
}
@override
Widget build(BuildContext
context) {
return Scaffold(
appBar: AppBar(
title: const
Text('Users from API'),
backgroundColor:
Theme.of(context).colorScheme.inversePrimary,
),
body: Center(
child:
FutureBuilder<List<User>>(
future:
futureUsers,
builder: (context,
snapshot) {
if
(snapshot.connectionState == ConnectionState.waiting) {
// While data
is being fetched, show a loading indicator
return const
CircularProgressIndicator();
} else if
(snapshot.hasError) {
// If an error
occurs during fetching
return
Text('Error: ${snapshot.error}');
} else if
(snapshot.hasData) {
// If data is
successfully fetched, display it in a ListView
return
ListView.builder(
itemCount:
snapshot.data!.length,
itemBuilder:
(context, index) {
User user =
snapshot.data![index];
return
Card(
margin:
const EdgeInsets.symmetric(
vertical: 8.0,
horizontal: 16.0,
),
elevation: 4,
child:
Padding(
padding: const EdgeInsets.all(16.0),
child:
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
user.name,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.deepPurple,
),
),
const SizedBox(height: 8),
Row(
children: [
const Icon(
Icons.email,
size: 18,
color: Colors.grey,
),
const SizedBox(width: 8),
Expanded(
child: Text(
user.email,
style: const TextStyle(fontSize: 16),
overflow: TextOverflow.ellipsis,
),
),
],
),
if
(user.phone != null && user.phone!.isNotEmpty) ...[
const SizedBox(height: 4),
Row(
children: [
const Icon(
Icons.phone,
size: 18,
color: Colors.grey,
),
const SizedBox(width: 8),
Text(
user.phone!,
style: const TextStyle(fontSize: 16),
),
],
),
],
if
(user.website != null &&
user.website!.isNotEmpty) ...[
const SizedBox(height: 4),
Row(
children: [
const Icon(
Icons.language,
size: 18,
color: Colors.grey,
),
const SizedBox(width: 8),
Text(
user.website!,
style: const TextStyle(fontSize: 16),
),
],
),
],
],
),
),
);
},
);
}
// Fallback for
cases where snapshot has no data and no error (e.g., empty list)
return const
Text('No users found.');
},
),
),
floatingActionButton:
FloatingActionButton(
onPressed: () {
setState(() {
futureUsers =
fetchUsers(); // Refresh data
});
ScaffoldMessenger.of(context).showSnackBar(
const
SnackBar(content: Text('Refreshing user list...')),
);
},
tooltip: 'Refresh
Users',
child: const
Icon(Icons.refresh),
),
);
}}
OUTPUT:
10. Unit
Tests and Debugging
a) Write
unit tests for UI components.
b) Use
Flutter's debugging tools to identify and fix issues.
a) Writing
a Widget Test
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
// Import the main application file
import 'package:unittesting/main.dart'; // IMPORTANT: Replace
'your_project_name' with your actual project name
void main() {
// Group tests related to
the MyHomePage widget
group('MyHomePage Widget
Tests', () {
// Test 1: Verify that
the counter starts at 0 and increments correctly
testWidgets('Counter
increments smoke test', (WidgetTester tester) async {
// Build our app and
trigger a frame.
// We wrap MyHomePage
in MaterialApp because it needs ancestors like
// Directionality and
MediaQuery that MaterialApp provides.
await
tester.pumpWidget(const MyApp());
// Verify that our
counter starts at 0.
expect(find.text('0'),
findsOneWidget);
expect(find.text('1'),
findsNothing); // Ensure 1 is not present initially
// Tap the '+' icon and
trigger a frame.
await
tester.tap(find.byIcon(Icons.add));
await tester.pump(); //
Rebuilds the widget tree after the tap
// Verify that our
counter has incremented.
expect(find.text('0'),
findsNothing);
expect(find.text('1'),
findsOneWidget);
// Tap again to
increment to 2
await
tester.tap(find.byIcon(Icons.add));
await tester.pump();
expect(find.text('1'),
findsNothing);
expect(find.text('2'),
findsOneWidget);
});
// Test 2: Verify the
presence of the app bar title
testWidgets('App bar
title is displayed correctly', (WidgetTester tester) async {
await
tester.pumpWidget(const MyApp());
// Verify that the
AppBar displays the correct title
expect(find.text('Flutter Demo Home Page'), findsOneWidget);
});
// Test 3: Verify the
presence of the static text
testWidgets('Static text
is displayed', (WidgetTester tester) async {
await
tester.pumpWidget(const MyApp());
// Verify that the
descriptive text is present
expect(find.text('You
have pushed the button this many times:'), findsOneWidget);
});
// Test 4: Verify the
presence of the FloatingActionButton
testWidgets('FloatingActionButton is present with correct icon and
tooltip', (WidgetTester tester) async {
await
tester.pumpWidget(const MyApp());
// Verify that the FAB
is present
expect(find.byType(FloatingActionButton), findsOneWidget);
// Verify the icon on
the FAB
expect(find.byIcon(Icons.add), findsOneWidget);
// Verify the tooltip
text
expect(find.byTooltip('Increment'), findsOneWidget);
});
// Test 5: Test widget
interaction by type
testWidgets('Can find
widgets by type', (WidgetTester tester) async {
await
tester.pumpWidget(const MyApp());
expect(find.byType(Scaffold), findsOneWidget);
expect(find.byType(AppBar), findsOneWidget);
expect(find.byType(Center), findsOneWidget);
expect(find.byType(Column), findsOneWidget);
expect(find.byType(Text), findsNWidgets(3)); // Two static texts and one
counter text
expect(find.byType(FloatingActionButton), findsOneWidget);
});
});
}
No comments:
Post a Comment