In Flutter development, handling API calls efficiently is crucial for building responsive and user-friendly applications. One powerful tool that simplifies the process is the use of Freezed unions. In this blog post, we’ll explore a practical example of making API calls with Freezed unions in a Flutter application
Setting Up the Project
To get started, let’s take a look at the use case. We have a Post model, an ApiResponse Model, and the main application in the PostPage widget.
The Post model is defined using Freezed annotations, providing a concise and expressive way to represent data.
@freezed
class Post with _$Post {
const factory Post({
@JsonKey(name: "userId") required int userId,
@JsonKey(name: "id") required int id,
@JsonKey(name: "title") required String title,
@JsonKey(name: "body") required String body,
}) = _Post;
factory Post.fromJson(Map<String, dynamic> json) => _$PostFromJson(json);
}
The ApiResponse class represents the different states of an API response, including loading, success, empty, and error states.
@Freezed(genericArgumentFactories: true)
class ApiResponse<T> with _$ApiResponse<T> {
const factory ApiResponse.loading() = ApiResponseLoading;
const factory ApiResponse.data(T data) = ApiResponseData;
const factory ApiResponse.empty() = ApiResponseEmpty;
const factory ApiResponse.error(String message) = ApiResponseError;
factory ApiResponse.fromJson(
Map<String, dynamic> json, T Function(Object?) fromJsonT) =>
_$ApiResponseFromJson(json, fromJsonT);
}
Implementing API Calls in the PostPage Widget
Now, let’s dive into the PostPage widget, where we initiate and handle API calls using Freezed unions.
class PostPage extends StatefulWidget {
const PostPage({Key? key}) : super(key: key);
@override
State<PostPage> createState() => _PostPageState();
}
class _PostPageState extends State<PostPage> {
late ApiResponse<List<Post>> postsResponse;
final postApi = PostService();
@override
void initState() {
super.initState();
getResponse();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Freezed Union'),
),
body: postsResponse.when(
loading: () => const Center(child: CircularProgressIndicator()),
data: (data) => Padding(
padding: const EdgeInsets.all(8.0),
child: ListView.builder(
itemBuilder: (context, index) => Card(
elevation: 5.0,
child: ListTile(
title: Text(data[index].title),
subtitle: Text(data[index].body),
),
),
itemCount: data.length,
),
),
empty: () => const Center(
child: Text('No Data'),
),
error: (message) => Center(
child: Text(message),
),
),
);
}
Future<void> getResponse() async {
try {
postsResponse = const ApiResponse.loading();
setState(() {});
final response = await postApi.requestPosts();
if (response.isEmpty) {
postsResponse = const ApiResponse.empty();
setState(() {});
} else {
postsResponse = ApiResponse.data(response);
setState(() {});
}
} catch (e) {
postsResponse = const ApiResponse.error('API Exception');
setState(() {});
}
}
}
In the PostPage widget, we use the postsResponse variable to represent the different states of the API call. The getResponse function initiates the API request and updates the state accordingly, which is then reflected in the UI. For complete source code visit https://github.com/Mudassar41/freezed-union-api-integration