Part 6: Lists, Grids, and Dynamic Content

This entry is part 7 of 8 in the series Flutter Development Tutorial (Beginner to Intermediate)

13. API Integration in Flutter (HTTP Requests)

Most mobile apps require data from remote servers or APIs. Flutter supports making HTTP requests to fetch data from RESTful services.
13.1 Adding HTTP Dependency

To make HTTP requests, add the http package in pubspec.yaml:

dependencies:
  http: ^0.13.0

13.2 Making a GET Request

Using http.get() allows you to fetch data from an API. Wrap it in an async function and use FutureBuilder to display the data.

Example: Simple GET Request

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

Future<List<String>> fetchItems() async {
  final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/posts'));

  if (response.statusCode == 200) {
    final List<dynamic> data = jsonDecode(response.body);
    return data.map((item) => item['title'].toString()).toList();
  } else {
    throw Exception('Failed to load items');
  }
}

class ApiExample extends StatefulWidget {
  @override
  _ApiExampleState createState() => _ApiExampleState();
}

class _ApiExampleState extends State<ApiExample> {
  late Future<List<String>> items;

  @override
  void initState() {
    super.initState();
    items = fetchItems();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('API Example')),
      body: FutureBuilder<List<String>>(
        future: items,
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return Center(child: CircularProgressIndicator());
          } else if (snapshot.hasError) {
            return Center(child: Text('Error: ${snapshot.error}'));
          } else {
            return ListView(
              children: snapshot.data!.map((item) => ListTile(title: Text(item))).toList(),
            );
          }
        },
      ),
    );
  }
}

Handling Errors and Timeout

final response = await http.get(
  Uri.parse('https://jsonplaceholder.typicode.com/posts'),
  headers: {'Authorization': 'Bearer your_api_key'},
).timeout(Duration(seconds: 5), onTimeout: () {
  throw Exception('Request timed out');
});

Best Practices for API Integration:

  • Use try-catch blocks to handle potential errors.
  • Optimize API calls by using caching mechanisms, pagination, and debouncing (for search features).
  • Always handle slow connections using timeout() and retry logic.

14. Error Handling in Flutter

Proper error handling is crucial for building stable apps. Flutter provides tools to catch errors and display user-friendly messages when things go wrong.


14.1 Using try-catch for Errors

Wrap asynchronous code in try-catch blocks to handle exceptions gracefully.
Example: Basic Error Handling

void fetchData() async {
  try {
    final response = await http.get(Uri.parse('https://example.com/data'));
    if (response.statusCode == 200) {
      // Parse the data
    } else {
      throw Exception('Failed to load data');
    }
  } catch (error) {
    print('Error: $error');
  }
}

14.2 Global Error Handling with FlutterError

You can handle uncaught exceptions globally using FlutterError.onError:

void main() {
  FlutterError.onError = (FlutterErrorDetails details) {
    FlutterError.presentError(details);
    // Optionally report error to external service
  };
  runApp(MyApp());
}

14.3 Error Handling in UI with FutureBuilder

When using FutureBuilder, always check for errors and handle them appropriately.

Example: Handling Errors in UI
return FutureBuilder<List<String>>(
  future: items,
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return CircularProgressIndicator();
    } else if (snapshot.hasError) {
      return Text('Error: ${snapshot.error}');
    } else {
      return ListView(...);
    }
  },
)
Best Practices for Error Handling:
  • Handle both synchronous and asynchronous errors: Use try-catch for handling runtime exceptions in asynchronous code and other strategies like assertions for synchronous code.
  • Display user-friendly messages: Always catch errors gracefully and provide informative feedback to the user, such as “Something went wrong. Please try again.”
  • Log errors: Use error tracking tools (such as Firebase Crashlytics, Sentry, etc.) to log errors for future debugging.
  • Avoid error silencing: Do not suppress exceptions without proper handling—this could mask bugs and lead to hard-to-trace issues.

Related posts<< Part 5: Working with Forms and Input HandlingPart 7: Advanced State Management >>