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