Popular Searches
Popular Course Categories
Popular Courses

Displaying API Data in Flutter

Displaying API Data in Flutter

Flutter APIs & Networking

Displaying API Data in Flutter

Displaying API data is an important part of Flutter application development. Most modern applications retrieve information from remote servers through REST APIs and display that information using Flutter widgets such as Text, ListView, GridView, Card, Image.network, and FutureBuilder.

The basic process is to make an HTTP request, receive the response, check the response status, decode the JSON data, convert it into Dart objects, and finally display those objects in the Flutter UI. Flutter's networking examples commonly use the http package and FutureBuilder for handling asynchronous API data.

Flutter Fetch Data Documentation

1. What Does Displaying API Data Mean?

Displaying API data means taking information received from a backend server and presenting it to the user through Flutter widgets.

For example, an API may return:

{
  "id": 1,
  "name": "John",
  "email": "[email protected]"
}

Flutter can process this response and display:

Name: John
Email: [email protected]

2. Typical API Data Flow in Flutter

Flutter Application
        ↓
HTTP Request
        ↓
API Server
        ↓
JSON Response
        ↓
jsonDecode()
        ↓
Dart Model Object
        ↓
Future / Async Operation
        ↓
FutureBuilder
        ↓
Flutter Widgets
        ↓
Data Displayed on Screen

3. Why API Data Is Used in Flutter

  • Display user information.
  • Load products from an online store.
  • Display news articles.
  • Show weather information.
  • Load social media posts.
  • Display orders and transactions.
  • Fetch images from remote servers.
  • Display dashboard statistics.
  • Load search results dynamically.
  • Synchronize application data with a backend.

4. Required Package for API Requests

The http package provides a simple way to make HTTP requests from Flutter applications.

flutter pub add http

Then import it:

import 'package:http/http.dart' as http;

Flutter's networking documentation demonstrates the use of the http package for common HTTP networking tasks.

Flutter HTTP Fetch Data

5. Importing JSON Support

If the API returns JSON, import dart:convert.

import 'dart:convert';
import 'package:http/http.dart' as http;

The jsonDecode() function converts JSON text into Dart data structures such as maps and lists.

6. Example API Response

Suppose an API returns the following JSON:

{
  "id": 1,
  "name": "John Doe",
  "email": "[email protected]",
  "phone": "9876543210"
}

This data can be decoded and displayed inside Flutter.

7. Creating a Dart Model

A model class represents API data in a structured Dart format. Using models makes API data easier to understand, validate, and display throughout an application.

class User {
  final int id;
  final String name;
  final String email;
  final String phone;

  User({
    required this.id,
    required this.name,
    required this.email,
    required this.phone,
  });

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'] as int,
      name: json['name'] as String,
      email: json['email'] as String,
      phone: json['phone'] as String,
    );
  }
}

8. Fetching Data from an API

Create a function that sends an HTTP GET request and converts the response into a User object.

Future<User> fetchUser() async {
  final response = await http.get(
    Uri.parse('https://example.com/api/user/1'),
  );

  if (response.statusCode == 200) {
    final data = jsonDecode(response.body) as Map<String, dynamic>;
    return User.fromJson(data);
  }

  throw Exception('Failed to load user');
}

9. Understanding Future in API Requests

Network requests are asynchronous because the application has to wait for a remote server to respond. Dart uses Future to represent a value that will be available later.

Future<User> fetchUser() async {
  // API request
}

The application should not freeze while waiting for the server response. Flutter can continue rendering the interface while the asynchronous request is running.

10. Using FutureBuilder

FutureBuilder is a Flutter widget designed for building UI based on the current state of a Future. It can be used to display loading, success, and error states.

Flutter Async Widgets

FutureBuilder<User>(
  future: fetchUser(),
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return const CircularProgressIndicator();
    }

    if (snapshot.hasError) {
      return Text('Error: ${snapshot.error}');
    }

    if (snapshot.hasData) {
      final user = snapshot.data!;
      return Text(user.name);
    }

    return const Text('No data available');
  },
)

11. FutureBuilder States

State Purpose Typical UI
Waiting API request is still running CircularProgressIndicator
Success API data has been received Text, ListView, Card
Error Request or processing failed Error message
Empty No data was returned No data message

12. Displaying a Single API Object

For an API that returns one object, the data can be displayed using basic Flutter widgets.

FutureBuilder<User>(
  future: fetchUser(),
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return const Center(
        child: CircularProgressIndicator(),
      );
    }

    if (snapshot.hasError) {
      return Center(
        child: Text('Error: ${snapshot.error}'),
      );
    }

    if (!snapshot.hasData) {
      return const Center(
        child: Text('No user found'),
      );
    }

    final user = snapshot.data!;

    return Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text('ID: ${user.id}'),
          Text('Name: ${user.name}'),
          Text('Email: ${user.email}'),
          Text('Phone: ${user.phone}'),
        ],
      ),
    );
  },
)

13. Displaying API Data Using Card

Cards can be used to create a cleaner UI for API data.

Card(
  margin: const EdgeInsets.all(12),
  child: Padding(
    padding: const EdgeInsets.all(16),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          user.name,
          style: const TextStyle(
            fontSize: 20,
            fontWeight: FontWeight.bold,
          ),
        ),
        const SizedBox(height: 8),
        Text(user.email),
        const SizedBox(height: 4),
        Text(user.phone),
      ],
    ),
  ),
)

14. Displaying a List of API Data

Many APIs return multiple records as a JSON array.

[
  {
    "id": 1,
    "name": "John",
    "email": "[email protected]"
  },
  {
    "id": 2,
    "name": "Jane",
    "email": "[email protected]"
  },
  {
    "id": 3,
    "name": "Mike",
    "email": "[email protected]"
  }
]

15. Creating a Function to Fetch a List

Future<List<User>> fetchUsers() async {
  final response = await http.get(
    Uri.parse('https://example.com/api/users'),
  );

  if (response.statusCode == 200) {
    final List<dynamic> data = jsonDecode(response.body) as List<dynamic>;

    return data
        .map(
          (item) => User.fromJson(
            item as Map<String, dynamic>,
          ),
        )
        .toList();
  }

  throw Exception('Failed to load users');
}

16. Displaying API List with ListView.builder

ListView.builder is useful for displaying a dynamic list of API records.

FutureBuilder<List<User>>(
  future: fetchUsers(),
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return const Center(
        child: CircularProgressIndicator(),
      );
    }

    if (snapshot.hasError) {
      return Center(
        child: Text('Error: ${snapshot.error}'),
      );
    }

    final users = snapshot.data ?? [];

    if (users.isEmpty) {
      return const Center(
        child: Text('No users found'),
      );
    }

    return ListView.builder(
      itemCount: users.length,
      itemBuilder: (context, index) {
        final user = users[index];

        return ListTile(
          leading: CircleAvatar(
            child: Text(user.name[0]),
          ),
          title: Text(user.name),
          subtitle: Text(user.email),
        );
      },
    );
  },
)

17. Displaying API Data with ListTile

ListTile is useful for simple rows containing a title, subtitle, icon, or leading widget.

ListTile(
  leading: const Icon(Icons.person),
  title: Text(user.name),
  subtitle: Text(user.email),
  trailing: Text('${user.id}'),
)

18. Displaying API Data with GridView

If API records need to be displayed as cards in a grid, use GridView.builder.

GridView.builder(
  padding: const EdgeInsets.all(12),
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
    crossAxisSpacing: 10,
    mainAxisSpacing: 10,
    childAspectRatio: 1.2,
  ),
  itemCount: users.length,
  itemBuilder: (context, index) {
    final user = users[index];

    return Card(
      child: Padding(
        padding: const EdgeInsets.all(12),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(Icons.person, size: 40),
            const SizedBox(height: 8),
            Text(user.name),
            Text(user.email),
          ],
        ),
      ),
    );
  },
)

19. Displaying API Images

If an API provides an image URL, Flutter can display the remote image using Image.network.

Image.network(
  user.profileImage,
  width: 100,
  height: 100,
  fit: BoxFit.cover,
)

A loading builder can be used while the image is downloading:

Image.network(
  user.profileImage,
  width: 100,
  height: 100,
  fit: BoxFit.cover,
  loadingBuilder: (
    context,
    child,
    loadingProgress,
  ) {
    if (loadingProgress == null) {
      return child;
    }

    return const Center(
      child: CircularProgressIndicator(),
    );
  },
)

20. Handling Image Loading Errors

Image.network(
  user.profileImage,
  errorBuilder: (
    context,
    error,
    stackTrace,
  ) {
    return const Icon(
      Icons.broken_image,
      size: 60,
    );
  },
)

21. Complete User Model with Profile Image

class User {
  final int id;
  final String name;
  final String email;
  final String profileImage;

  User({
    required this.id,
    required this.name,
    required this.email,
    required this.profileImage,
  });

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'] as int,
      name: json['name'] as String,
      email: json['email'] as String,
      profileImage: json['profile_image'] as String,
    );
  }
}

22. Displaying Products from an API

A common Flutter application example is an e-commerce product list.

{
  "id": 101,
  "title": "Laptop",
  "price": 75000,
  "image": "https://example.com/laptop.jpg"
}

Product Model

class Product {
  final int id;
  final String title;
  final double price;
  final String image;

  Product({
    required this.id,
    required this.title,
    required this.price,
    required this.image,
  });

  factory Product.fromJson(Map<String, dynamic> json) {
    return Product(
      id: json['id'] as int,
      title: json['title'] as String,
      price: (json['price'] as num).toDouble(),
      image: json['image'] as String,
    );
  }
}

23. Product Card UI

Card(
  child: Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      Expanded(
        child: Image.network(
          product.image,
          width: double.infinity,
          fit: BoxFit.cover,
        ),
      ),
      Padding(
        padding: const EdgeInsets.all(12),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              product.title,
              style: const TextStyle(
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 6),
            Text(
              '₹${product.price.toStringAsFixed(2)}',
            ),
          ],
        ),
      ),
    ],
  ),
)

24. Handling Loading State

The user should receive visual feedback while API data is being loaded.

if (snapshot.connectionState == ConnectionState.waiting) {
  return const Center(
    child: CircularProgressIndicator(),
  );
}

Other possible loading indicators include:

  • CircularProgressIndicator
  • LinearProgressIndicator
  • Skeleton loading UI
  • Placeholder cards
  • Progress indicators inside buttons

25. Handling Error State

An API request can fail because of network problems, server errors, invalid responses, authentication issues, or other conditions.

if (snapshot.hasError) {
  return Center(
    child: Text(
      'Something went wrong: ${snapshot.error}',
    ),
  );
}

26. Handling Empty Data

A successful API request does not always mean that records are available. The server may return an empty list.

final users = snapshot.data ?? [];

if (users.isEmpty) {
  return const Center(
    child: Text('No users available'),
  );
}

27. Displaying Data with RefreshIndicator

RefreshIndicator can be used to allow the user to pull down and refresh API data.

RefreshIndicator(
  onRefresh: () async {
    setState(() {
      futureUsers = fetchUsers();
    });

    await futureUsers;
  },
  child: ListView.builder(
    itemCount: users.length,
    itemBuilder: (context, index) {
      final user = users[index];

      return ListTile(
        title: Text(user.name),
        subtitle: Text(user.email),
      );
    },
  ),
)

28. Why API Calls Should Not Be Placed Directly in build()

It is generally not recommended to create a new API request directly inside the build() method. Flutter may call build() many times, which can cause repeated requests.

Instead, store the Future in state and initialize it in initState() when appropriate. Flutter's official fetch-data recipe demonstrates this pattern.

Flutter Fetch Data Recipe

Recommended Pattern

late Future<List<User>> futureUsers;

@override
void initState() {
  super.initState();
  futureUsers = fetchUsers();
}

Then use:

FutureBuilder<List<User>>(
  future: futureUsers,
  builder: (context, snapshot) {
    // Build UI
  },
)

29. Complete Example: API Data List Screen

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

class User {
  final int id;
  final String name;
  final String email;

  User({
    required this.id,
    required this.name,
    required this.email,
  });

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'] as int,
      name: json['name'] as String,
      email: json['email'] as String,
    );
  }
}

Future<List<User>> fetchUsers() async {
  final response = await http.get(
    Uri.parse(
      'https://jsonplaceholder.typicode.com/users',
    ),
  );

  if (response.statusCode == 200) {
    final List<dynamic> data =
        jsonDecode(response.body) as List<dynamic>;

    return data
        .map(
          (item) => User.fromJson(
            item as Map<String, dynamic>,
          ),
        )
        .toList();
  }

  throw Exception('Failed to load users');
}

class UserScreen extends StatefulWidget {
  const UserScreen({super.key});

  @override
  State<UserScreen> createState() => _UserScreenState();
}

class _UserScreenState extends State<UserScreen> {
  late Future<List<User>> futureUsers;

  @override
  void initState() {
    super.initState();
    futureUsers = fetchUsers();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Users'),
      ),
      body: FutureBuilder<List<User>>(
        future: futureUsers,
        builder: (context, snapshot) {
          if (snapshot.connectionState ==
              ConnectionState.waiting) {
            return const Center(
              child: CircularProgressIndicator(),
            );
          }

          if (snapshot.hasError) {
            return Center(
              child: Text(
                'Error: ${snapshot.error}',
              ),
            );
          }

          final users = snapshot.data ?? [];

          if (users.isEmpty) {
            return const Center(
              child: Text('No users found'),
            );
          }

          return ListView.builder(
            itemCount: users.length,
            itemBuilder: (context, index) {
              final user = users[index];

              return Card(
                margin: const EdgeInsets.symmetric(
                  horizontal: 12,
                  vertical: 6,
                ),
                child: ListTile(
                  leading: CircleAvatar(
                    child: Text(user.name[0]),
                  ),
                  title: Text(user.name),
                  subtitle: Text(user.email),
                  trailing: Text(
                    '#${user.id}',
                  ),
                ),
              );
            },
          );
        },
      ),
    );
  }
}

30. Displaying Nested API Data

API responses may contain nested objects. For example:

{
  "id": 1,
  "name": "John",
  "address": {
    "city": "Mumbai",
    "country": "India"
  }
}

Create an Address model:

class Address {
  final String city;
  final String country;

  Address({
    required this.city,
    required this.country,
  });

  factory Address.fromJson(Map<String, dynamic> json) {
    return Address(
      city: json['city'] as String,
      country: json['country'] as String,
    );
  }
}

Then include it in the user model:

class User {
  final int id;
  final String name;
  final Address address;

  User({
    required this.id,
    required this.name,
    required this.address,
  });

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'] as int,
      name: json['name'] as String,
      address: Address.fromJson(
        json['address'] as Map<String, dynamic>,
      ),
    );
  }
}

31. Displaying Nested Data

Column(
  crossAxisAlignment: CrossAxisAlignment.start,
  children: [
    Text('Name: ${user.name}'),
    Text('City: ${user.address.city}'),
    Text('Country: ${user.address.country}'),
  ],
)

32. Handling Nullable API Data

API fields can sometimes be null. Dart's nullable types should be used when a value is optional.

class User {
  final int id;
  final String name;
  final String? phone;

  User({
    required this.id,
    required this.name,
    this.phone,
  });

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'] as int,
      name: json['name'] as String,
      phone: json['phone'] as String?,
    );
  }
}

Display it safely:

Text(user.phone ?? 'Phone number not available')

33. Displaying API Data with Search

API data can be filtered locally after it has been loaded.

final filteredUsers = users.where((user) {
  return user.name
      .toLowerCase()
      .contains(searchText.toLowerCase());
}).toList();

The filtered collection can then be passed to ListView.builder.

34. Displaying API Data with Navigation

A common pattern is to display a list of API records and navigate to a detail screen when the user selects an item.

ListTile(
  title: Text(user.name),
  subtitle: Text(user.email),
  onTap: () {
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => UserDetailScreen(
          user: user,
        ),
      ),
    );
  },
)

User Detail Screen

class UserDetailScreen extends StatelessWidget {
  final User user;

  const UserDetailScreen({
    super.key,
    required this.user,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(user.name),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              user.name,
              style: const TextStyle(
                fontSize: 24,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 12),
            Text(user.email),
          ],
        ),
      ),
    );
  }
}

35. Displaying API Data in a Dashboard

API data can also be used to create dashboard statistics.

Row(
  children: [
    Expanded(
      child: Card(
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Column(
            children: [
              const Text('Users'),
              Text(
                '${users.length}',
                style: const TextStyle(
                  fontSize: 28,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ],
          ),
        ),
      ),
    ),
    Expanded(
      child: Card(
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Column(
            children: [
              const Text('Active'),
              Text(
                '${activeUsers.length}',
                style: const TextStyle(
                  fontSize: 28,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ],
          ),
        ),
      ),
    ),
  ],
)

36. Separating API Logic from UI

For larger applications, API communication should be separated from UI widgets. A service class can handle HTTP requests while the UI focuses on presentation.

class UserService {
  Future<List<User>> getUsers() async {
    final response = await http.get(
      Uri.parse('https://example.com/api/users'),
    );

    if (response.statusCode != 200) {
      throw Exception('Failed to load users');
    }

    final List<dynamic> data =
        jsonDecode(response.body) as List<dynamic>;

    return data
        .map(
          (item) => User.fromJson(
            item as Map<String, dynamic>,
          ),
        )
        .toList();
  }
}

Flutter's architecture guidance describes services as classes that interact with external APIs, while repositories can act as a source of truth and coordinate application data.

Flutter Data Layer Documentation

37. Service and UI Flow

Flutter Screen
      ↓
UserService
      ↓
HTTP Request
      ↓
API Server
      ↓
JSON Response
      ↓
User.fromJson()
      ↓
List<User>
      ↓
FutureBuilder
      ↓
ListView
      ↓
User Interface

38. Handling API Errors Gracefully

Instead of displaying technical error details directly to users, applications can provide a friendly message.

if (snapshot.hasError) {
  return Center(
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        const Icon(
          Icons.error_outline,
          size: 50,
        ),
        const SizedBox(height: 12),
        const Text(
          'Unable to load data',
        ),
        const SizedBox(height: 12),
        ElevatedButton(
          onPressed: () {
            setState(() {
              futureUsers = fetchUsers();
            });
          },
          child: const Text('Try Again'),
        ),
      ],
    ),
  );
}

39. Retry API Requests

A retry button can create a new Future and trigger another API request.

ElevatedButton(
  onPressed: () {
    setState(() {
      futureUsers = fetchUsers();
    });
  },
  child: const Text('Retry'),
)

40. API Data with Loading, Success, Empty, and Error States

FutureBuilder<List<User>>(
  future: futureUsers,
  builder: (context, snapshot) {
    if (snapshot.connectionState ==
        ConnectionState.waiting) {
      return const LoadingWidget();
    }

    if (snapshot.hasError) {
      return const ErrorStateWidget();
    }

    final users = snapshot.data ?? [];

    if (users.isEmpty) {
      return const EmptyWidget();
    }

    return UserList(users: users);
  },
)

A well-designed API screen should explicitly consider the main UI states:

  1. Loading state.
  2. Error state.
  3. Empty state.
  4. Success state.
  5. Refresh or retry state when required.

41. Reusable Loading Widget

class LoadingWidget extends StatelessWidget {
  const LoadingWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return const Center(
      child: CircularProgressIndicator(),
    );
  }
}

42. Reusable Empty Widget

class EmptyWidget extends StatelessWidget {
  const EmptyWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return const Center(
      child: Text('No data available'),
    );
  }
}

43. Reusable User List Widget

class UserList extends StatelessWidget {
  final List<User> users;

  const UserList({
    super.key,
    required this.users,
  });

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: users.length,
      itemBuilder: (context, index) {
        final user = users[index];

        return ListTile(
          leading: CircleAvatar(
            child: Text(user.name[0]),
          ),
          title: Text(user.name),
          subtitle: Text(user.email),
        );
      },
    );
  }
}

44. Performance Considerations

  • Do not repeatedly make the same API request during widget rebuilds.
  • Store Future objects appropriately when using FutureBuilder.
  • Use ListView.builder for large dynamic lists.
  • Avoid unnecessary widget rebuilds.
  • Use pagination when APIs return large datasets.
  • Cache data when appropriate.
  • Consider background JSON parsing for sufficiently large responses.

45. Background JSON Parsing

For very large JSON responses, parsing can become computationally expensive. Flutter provides guidance for parsing JSON in the background so that expensive parsing work does not unnecessarily interfere with the UI.

Flutter Background JSON Parsing

46. Pagination for Large API Lists

Instead of loading thousands of records at once, an API may provide data page by page.

GET /api/users?page=1&limit=20

When the user reaches the end of the list, another page can be requested.

Future<List<User>> fetchUsers(int page) async {
  final response = await http.get(
    Uri.parse(
      'https://example.com/api/users?page=$page&limit=20',
    ),
  );

  if (response.statusCode != 200) {
    throw Exception('Failed to load users');
  }

  final List<dynamic> data =
      jsonDecode(response.body) as List<dynamic>;

  return data
      .map(
        (item) => User.fromJson(
          item as Map<String, dynamic>,
        ),
      )
      .toList();
}

47. Displaying API Data in Different Widgets

Widget Common Usage
Text Display individual values
ListTile Display list records
Card Display structured information
ListView.builder Display large dynamic lists
GridView.builder Display grid-based API data
Image.network Display remote images
FutureBuilder Display asynchronous API results
RefreshIndicator Refresh API data

48. Common Mistakes

  • Forgetting to add the http package.
  • Forgetting required platform network configuration where applicable.
  • Not checking the response status code.
  • Trying to access JSON fields before decoding the response.
  • Assuming the API always returns valid data.
  • Not handling null values.
  • Calling API functions repeatedly inside build().
  • Not showing a loading indicator.
  • Not providing an error state.
  • Not handling empty API responses.
  • Loading very large datasets without pagination or appropriate optimization.
  • Putting all networking logic directly inside UI widgets.

49. Best Practices

  1. Use a dedicated service or repository for API communication.
  2. Use model classes for structured API data.
  3. Use fromJson() for JSON-to-model conversion.
  4. Check HTTP status codes.
  5. Handle loading, success, empty, and error states.
  6. Use FutureBuilder for straightforward Future-based UI.
  7. Use ListView.builder for dynamic lists.
  8. Use pagination for large datasets.
  9. Use RefreshIndicator when manual refresh is useful.
  10. Handle nullable API fields safely.
  11. Keep API and presentation logic separated.
  12. Use background parsing when large JSON responses require it.

50. Complete API Data Display Architecture

                    API SERVER
                         ↓
                   JSON RESPONSE
                         ↓
                     HTTP CLIENT
                         ↓
                    API SERVICE
                         ↓
                    jsonDecode()
                         ↓
                  MODEL.fromJson()
                         ↓
                       Future
                         ↓
                  FutureBuilder
                         ↓
          ┌──────────────┼──────────────┐
          ↓              ↓              ↓
       Loading          Error         Success
          ↓              ↓              ↓
     Progress UI      Error UI       Data UI
                                         ↓
                                ListView / GridView
                                         ↓
                                    Flutter UI

51. Quick Revision

Concept Purpose
HTTP GET Retrieve data from an API
http Make HTTP requests
jsonDecode() Convert JSON text into Dart data
fromJson() Convert JSON data into a model
Future Represent asynchronous API results
FutureBuilder Build UI based on Future state
ListView.builder Display dynamic API lists
GridView.builder Display API data in grids
Image.network Display remote images
RefreshIndicator Refresh API data

52. Practice Exercise

Create a Flutter application that fetches a list of users from an API and displays them in a responsive user list.

Requirements

  1. Create a User model.
  2. Create a fromJson() factory constructor.
  3. Create a fetchUsers() function.
  4. Make a GET request using the http package.
  5. Decode the JSON response.
  6. Convert the response into a List.
  7. Use FutureBuilder.
  8. Display a loading indicator while fetching data.
  9. Display an error message if the request fails.
  10. Display an empty-state message if the list is empty.
  11. Display users using ListView.builder.
  12. Add a retry button.
  13. Add pull-to-refresh functionality.
  14. Navigate to a user detail screen when a user is tapped.

53. Interview Questions

  1. How do you display API data in Flutter?
  2. What is the purpose of the http package?
  3. Why is API data usually handled asynchronously?
  4. What is a Future in Dart?
  5. What is FutureBuilder?
  6. What are the common states handled by FutureBuilder?
  7. Why should an API call not normally be created directly inside build()?
  8. How do you display a list of API objects?
  9. What is the difference between ListView and ListView.builder?
  10. How do you display API images in Flutter?
  11. How do you handle API errors?
  12. How do you handle an empty API response?
  13. Why should model classes be used for API data?
  14. How can API data be refreshed?
  15. What is pagination?
  16. When should JSON parsing be performed in the background?
  17. How can networking logic be separated from UI code?

54. Official Flutter Resources

55. Learn Flutter with JustAcademy

For structured Flutter training and practical application development, explore the following resources:

56. Summary

Displaying API data in Flutter involves several connected steps: making an HTTP request, receiving the response, checking the status code, decoding JSON, converting the response into Dart model objects, and displaying those objects using Flutter widgets. FutureBuilder is useful for managing asynchronous UI states such as loading, success, and error. Lists can be displayed with ListView.builder, grids with GridView.builder, images with Image.network, and individual records with widgets such as Card and ListTile. For larger applications, separating networking into service or repository classes improves organization and maintainability.

Previous Next
whatsapp