10/8/25

Flutter Lab by Dr Madhava rao Maganti

 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

  1. Go to the official Flutter installation page: https://flutter.dev/get-started/install
  2. Select your operating system: WindowsmacOS, or Linux.
  3. Follow the instructions to download the Flutter SDK zip file.
  4. 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.

  1. Find the bin directory inside your extracted Flutter folder (e.g., C:\flutter\bin).
  2. 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):
    1. Install Visual Studio Code.
    2. Open VS Code, go to the Extensions view (click the icon on the left sidebar).
    3. Search for and install the Flutter extension (by Dart Code). This will also automatically install the required Dart extension.
  • For Android Studio:
    1. Install Android Studio.
    2. Open Android Studio, go to Settings/Preferences > Plugins.
    3. 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.

  1. Open a new terminal or command prompt window (to ensure it has the updated PATH).
  2. Run the command:

Generated sh

flutter doctor

  1. 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

  1. Create a new folder on your computer for this experiment (e.g., dart_basics).
  2. Open this folder in VS Code (or your chosen editor).
  3. 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

  1. Open a terminal within VS Code (Terminal > New Terminal).
  2. Make sure you are in the correct directory (dart_basics).
  3. 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:

Blog Archive