Handling Errors Gracefully in Flutter Applications

Muhammad Kuifatieh
4 min readJun 15, 2023

--

Introduction

In the world of mobile app development, handling errors gracefully is a crucial aspect of delivering a seamless user experience. When interacting with APIs, it is not uncommon to encounter errors due to server malfunctions or incorrect data transmission. As developers, it is our responsibility to implement effective error handling mechanisms to ensure that these issues are addressed promptly and effectively.

In this article, we will explore the best practices and techniques for handling errors in Flutter applications. We will delve into various strategies that can be employed to mitigate errors, improve user experience, and provide meaningful feedback to users when errors occur. By understanding and implementing these approaches, you can enhance the robustness and reliability of your Flutter applications.

Let’s dive in and discover the best way to handle errors in Flutter!

Incorporating the dartz Package

As a first step, we will incorporate the dartz package into our project. This package provides the Either type, which consists of two sections: left and right. We can utilize the Either type to store either an error value or a successful result. Let’s see how we can leverage this in our code.

To begin, we will define four classes that play a significant role in our error handling mechanism:

1. Exception Classes

class ServerException implements Exception {
final String message;
ServerException(this.message);
}

class UnauthenticatedExeption implements Exception {}

The Exception Classes represent the exception that we can display to the user when an error occurs.

2. Failure Classes

abstract class Failure extends Equatable {
final String? message; const Failure({this.message});
@override
List<Object> get props => [];
}
// General failures
class ServerFailure extends Failure {
const ServerFailure(String? message) : super(message: message);
}
class UnauthenticatedFailure extends Failure {
const UnauthenticatedFailure();
}

The Failure classes represent the error condition and will be placed in the left section of Either of them since for each Exception of the first type there is a failure corresponding to it.

3. HandlingExceptionRequest Class

class HandlingExceptionRequest {
Exception getException({required Response response}) {
final String message = json.decode(response.body)['message'];
if (response.statusCode == 401) {
return UnauthenticatedExeption();
}
return ServerException(message);
}
}

The HandlingExceptionRequest Class captures the status code returned by the API and converts it into an exception using the Exception Classes.

4. HandlingExceptionManager Class

class HandlingExceptionManager {
Future<Either<Failure, T>> wrapHandling<T>({
required Future<T> Function() tryCall,
Future<T?> Function()? tryCallLocal,
}) async {
try {
final right = await tryCall();
return Right(right);
} on UnauthenticatedExeption {
log("<< catch >> Unauthenticated Error ");
return const Left(UnauthenticatedFailure());
}catch (e) {
log("<< catch >> error is $e");
if (tryCallLocal != null) {
final result = await tryCallLocal();
if (result != null) {
return Right(result);
} else {
return const Left(ServerFailure("message"));
}
} else {
return const Left(ServerFailure("message"));
}
}
}
}

The HandlingExceptionManager is a class that provides the wrapHandling method. This method takes a callback function and executes it. If the result is correct, it is wrapped in the right section of the Either. However, if an exception is thrown, it is caught and wrapped in the left section of the Either using the ExceptionClass.

By utilizing these classes and Either type from the dartz package, we can effectively handle errors in our Flutter applications. The wrapHandling method provided by the HandlingExceptionManager allows us to encapsulate code execution and capture any exceptions that occur.

Integrated Example

Now that we have defined the necessary classes and set up our error handling mechanism, let’s see how we can use them together in an integrated example.

Consider a scenario where we have an API service that fetches user data from a server. We want to handle any errors that may occur during the API request and provide appropriate feedback to the user.

Here’s an example code snippet showcasing the usage of the defined classes and error handling mechanism:

class UserService {
Future<User> getUserData() async {
final response = await apiService.fetchUserData();
if (response.statusCode == 200) {
final userData = User.fromJson(json.decode(response.body));
return userData;
} else {
throw HandlingExceptionRequest().getException(response: response);
}
}
}
class UserRepository {
Future<Either<Failure, User>> getUserData() async {
return HandlingExceptionManager().wrapHandling(
tryCall: () async {
final result = await UserService().getUserData();
return result;
},
);
}
}da

And in the top level of your code, you will do this:

final result = await UserRepository().getUserData();
result.fold((l) {
// Do something according to the type of Failure
}, (r) {
// Do something with Success result
});

Conclusion

In this article, we have explored the best practices and techniques for handling errors in Flutter applications. We have learned about the dartz package and its Either type, which helps us effectively manage errors by storing either error values or successful results. By incorporating the classes we defined, such as exception classes, failure classes, and the error handling manager, we can implement a robust error handling mechanism in our Flutter apps.

Proper error handling is crucial for delivering a seamless user experience. By following the techniques outlined in this article, developers can ensure that errors are addressed promptly and effectively. Users will receive meaningful feedback when errors occur, improving their overall satisfaction with the app.

To recap, we have seen how to handle API errors, convert them into exceptions, and encapsulate code execution using the HandlingExceptionManager. This approach allows us to catch and handle exceptions while keeping our code concise and maintainable.

By implementing these error handling best practices, Flutter developers can enhance the reliability and robustness of their applications. Users will appreciate the improved error handling experience, leading to increased engagement and user satisfaction.

So, let’s integrate these error handling techniques into our Flutter projects and deliver a seamless user experience by gracefully handling errors!

If you’re interested in exploring the code or contributing to the project, you can find the GitHub repository at GitHub Repository URL .

Feel free to visit the repository, browse the code, and contribute by submitting pull requests. Your contributions are highly appreciated

Happy coding and error handling in Flutter!

--

--