Flutter Dio Part 2: Handling Responses and Errors

Search for a command to run...

No comments yet. Be the first to comment.
While I generally write about Flutter. This problem has been bugging me for a while and there seem to be limited resources to find a solution. So for anyone who is looking for a solution. Here you go: Type edge:flags into the address bar and open it...
In this tutorial, we’ll go through covering interceptor, which is a very crucial feature of Dio. You can find the entire source code for the project here: dio_tasker Understanding Interceptors: What is an interceptor? An interceptor in Dio is a powe...

While building apps in Flutter is extremely fluid and wonderfully intuitive. We obviously know that we can’t build the next TikTok or Twitter by only building offline apps. Which is why we absolutely need internet capabilities in our Flutter apps. Th...

Understanding when to use setState in Flutter is crucial for managing our app’s state effectively. Here’s a detailed guide: When to use setState ? Updating the UI: Use setState when we need to update the UI in response to changes in the internal sta...

In this part, we’ll enhance our Task Manager app by parsing JSON responses and handling different types of errors. We’ll use Dio’s built-in error handling mechanisms.
You can find the source code of the entire app here: dio_tasker
We’ll update our ApiService to parse JSON response into a list of task objects.
First, we’ll create a new file lib/models/task.dart
class Task {
final int id;
final String title;
final bool completed;
Task({required this.id, required this.title, required this.completed});
// Factory method to create a Task from JSON
factory Task.fromJSON(Map<String, dynamic> json) => Task(
id: json['id'],
title: json['title'],
completed: json['completed'],
);
}
ApiService:Now, let’s modify the lib/services/api_service.dart to use the Task model:
import 'package:dio/dio.dart';
import '../models/tasks.dart';
class ApiService {
// Create an instance of Dio
final _dio = Dio();
// Method to fetch tasks from the API
Future<List<Task>> fetchTasks() async {
try {
// Make a GET request to the API endpoint
final response =
await _dio.get('https://jsonplaceholder.typicode.com/todos');
// Parse the response data into a list of Task objects
return (response.data as List)
.map((task) => Task.fromJSON(task))
.toList();
} catch (e) {
// Handle errors
throw Exception('Failed to load tasks');
}
}
}
Now we’ll handle network errors, server errors and other types of errors.
ApiService to Handle Errors:Let’s modify lib/services/api_service.dart to handle different types of errors:
import 'package:dio/dio.dart';
import '../models/tasks.dart';
class ApiService {
// Create an instance of Dio
final _dio = Dio();
// Method to fetch tasks from the API
Future<List<Task>> fetchTasks() async {
try {
// Make a GET request to the API endpoint
final response =
await _dio.get('https://jsonplaceholder.typicode.com/todos');
// Parse the response data into a list of Task objects
return (response.data as List)
.map((task) => Task.fromJSON(task))
.toList();
} on DioException catch (dioException) {
// Handle Dio Exceptions
if (dioException.type == DioExceptionType.connectionTimeout) {
throw Exception('Connection Timeout');
} else if (dioException.type == DioExceptionType.receiveTimeout) {
throw Exception('Receive Timeout');
} else if (dioException.type == DioExceptionType.badResponse) {
throw Exception(
'Received invalid status code: ${dioException.response?.statusCode}');
} else {
throw Exception('Something went wrong');
}
} catch (e) {
// Handle other errors
throw Exception('Failed to load tasks');
}
}
}
We’ll update the UI to display error messages when fetching tasks fails.
Let’s update lib/main.dart to display error messages:
import 'package:dio_tasker/services/api_service.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: TaskListScreen(),
);
}
}
class TaskListScreen extends StatefulWidget {
const TaskListScreen({super.key});
@override
State<TaskListScreen> createState() => _TaskListScreenState();
}
class _TaskListScreenState extends State<TaskListScreen> {
// Create an instance of ApiService
final ApiService _apiService = ApiService();
// List to hold the fetched tasks
List<dynamic> _tasks = [];
// Variable to hold error messages
String? _errorMessage;
@override
void initState() {
super.initState();
// Fetch tasks when the widget is initialized
_fetchTasks();
}
// Method to fetch tasks and update the state
void _fetchTasks() async {
try {
// Fetch tasks from the API
final tasks = await _apiService.fetchTasks();
// Update the state with the fetched tasks
setState(() {
_tasks = tasks;
});
} catch (e) {
// Update the state with the error message
setState(() {
_errorMessage = e.toString();
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Task Manager'),
),
body: _errorMessage != null
? Center(
child: Text(_errorMessage!),
)
: ListView.builder(
// Set the number of items in the list
itemCount: _tasks.length,
// Build each item in the list
itemBuilder: (context, index) {
return ListTile(
// Display the task title
title: Text(_tasks[index].title),
);
},
),
);
}
}
Allright so let’s run the app and see if we get any errors.

Now we’ll change the URL to an invalid URL and see if the app can handle that. So instead of using https://jsonplaceholder.typicode.com/todos we’ll use https://jsonplaceholder.typicode.com/to. Which is an invalid URL. And here’s the output:

Perfect. As we can see, our app is able to handle errors.
You can find the source code for the above example here: dio_tasker/02-handling-response-errors.