Networking in Flutter using Dio
Handling network requests is a critical aspect of application development, and it is essential to handle unexpected results gracefully to ensure a good user experience.
In this article, we will explore how to use the Dio package to handle REST API requests in Flutter.
Dio is a powerful HTTP client for Dart that provides an intuitive API for performing advanced network tasks with ease.
It offers support for interceptors, global configuration, FormData, request cancellation, file downloading, and timeout, among other features.
While Flutter's built-in http package is suitable for performing basic network tasks, it can be challenging to use when handling more advanced features.
Therefore, Dio is an excellent choice for developers who want to perform advanced network tasks with ease. With its extensive documentation and active community support, developers can handle network requests in Flutter without difficulty.
In summary, Dio is a valuable package that simplifies the process of handling network requests in Flutter. By using its intuitive API, developers can easily handle complex network tasks and ensure that their applications provide an excellent user experience.
Here's a step-by-step tutorial on how to use Dio, MVVM architecture, Provider with Consumer, and ListView to fetch and display data from the JSONPlaceholder API in Flutter:
Add dependencies
First, you need to add the Dio and Provider packages to your Flutter project by adding the following lines to your pubspec.yaml file:
dependencies:
flutter:
sdk: flutter
dio: ^5.3.0
provider: ^6.0.5
Then, run flutter pub get in your terminal to install these packages.
MVVM folder structure - demo
lib/
├── data/
│ ├── models/
│ │ └── user.dart // Contains the User model class for representing user data.
│ ├── repositories/
│ │ └── user_repository.dart // Handles fetching user data from the API.
│ └── services/
│ └── api_service.dart // Provides methods to interact with the API using Dio or other HTTP clients.
│
├── utils/
│ ├── constants.dart // Contains constant values used throughout the app.
│ └── logger.dart // Utility for logging app events or errors.
│
├── view/
│ └── user_list.dart // View file for displaying the list of users.
│
├── view_models/
│ └── user_view_model.dart // ViewModel class for managing user data and API calls.
│
├── widgets/
│ ├── user_tile.dart // Reusable widget for displaying user details in a list.
│ └── loading_spinner.dart // Reusable loading spinner widget.
│
├── main.dart // The main entry point of the Flutter app.
The separate code layers of Model View ViewModel are:
Model: It represents the business logic and the data of an Application. It also consists of the business logic - local and remote data source, model classes, repository.
View: It contains the UI Code, Also sends the user action to the ViewModel but does not receive the response back directly.
ViewModel: It acts as a connection between the View and the business logic. Furthermore, it doesn't have any idea about which View it has to use as it does not possess a direct reference with the View.
..
Dio MVVM User List App
Build a Flutter user list app using Dio for API calls and MVVM architecture. Learn how to manage user data and API integration efficiently with sample code and best practices.
main.dart : The main entry point of the Flutter app.
// main.dart
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';
import 'package:flutteryfly/viewmodels/user_view_model.dart';
import 'package:provider/provider.dart';
import './data/repositories/user_repository.dart';
import './data/services/api_service.dart';
import './views/user_list_consumer.dart';
void main() {
// Create Dio instance for HTTP requests
final Dio dio = Dio();
// Create ApiService instance with the Dio instance
final ApiService apiService = ApiService(dio: dio);
// Create UserRepository instance with the ApiService instance
final UserRepository userRepository = UserRepository(apiService: apiService);
runApp(
MultiProvider(
providers: [
// Provide the UserViewModel with UserRepository dependency to manage user data and API calls
ChangeNotifierProvider<UserViewModel>(
create: (context) => UserViewModel(userRepository: userRepository),
),
],
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'User List', // Meta Title for the App
theme: ThemeData(
primarySwatch: Colors.green,
),
home: const UserListConsumer(),
);
}
}
..
user_list_consumer.dart : View file for displaying the list of users.
Create the UserListConsumer Widget
For larger applications with more complex state management, using Consumer could be a better choice for better control over widget rebuilds.
// views/user_list_consumer.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../viewmodels/user_view_model.dart';
class UserListConsumer extends StatefulWidget {
const UserListConsumer({super.key}); // A stateful widget to display the users
@override
State<UserListConsumer> createState() => _UserListConsumerState(); // Returns the state of the widget
}
class _UserListConsumerState extends State<UserListConsumer> {
@override
void initState() { // Called when the state object is inserted into the tree.
super.initState();
final userViewModel = Provider.of<UserViewModel>(context, listen: false); // Fetches userViewModel object
userViewModel.fetchUsers(); // Calls the method to fetch the users
}
@override
Widget build(BuildContext context) { // Build method which returns the UI
return Scaffold(
appBar: AppBar(
title: const Text('Users'),
),
body: Consumer<UserViewModel>( // Consumer widget to listen for changes in UserViewModel
builder: (context, userViewModel, child) {
if (userViewModel.loading) { // If data is still loading, show a progress indicator
return const Center(
child: CircularProgressIndicator(),
);
} else if (userViewModel.errorMessage.isNotEmpty) { // If there is an error, show the error message
return Center(
child: Text(userViewModel.errorMessage),
);
} else { // Otherwise, show the list of users
return ListView.builder(
itemCount: userViewModel.users.length,
itemBuilder: (context, index) {
final user = userViewModel.users[index];
return ListTile(
title: Text(user.name),
subtitle: Text(user.email),
leading: CircleAvatar(
child: Text(user.id.toString()),
),
);
},
);
}
},
),
);
}
}
..
user_view_model.dart : ViewModel class for managing user data and API calls.
// view_models/user_view_model.dart
import 'package:flutter/material.dart';
import '../data/models/user.dart';
import '../data/repositories/user_repository.dart';
class UserViewModel extends ChangeNotifier {
final UserRepository userRepository;
UserViewModel({required this.userRepository});
List<User> _users = []; // List to store user data fetched from the API.
bool _loading = false; // Boolean flag to track if data is currently being fetched.
String _errorMessage = ''; // String to store any error message that occurs during data fetching.
List<User> get users => _users; // Getter method to access the list of users.
bool get loading => _loading; // Getter method to access the loading flag.
String get errorMessage => _errorMessage; // Getter method to access the error message.
Future<void> fetchUsers() async {
_loading = true; // Set loading flag to true before making the API call.
_errorMessage = ''; // Clear any previous error message.
try {
// Call the getUsers() method from the UserRepository to fetch user data from the API.
_users = await userRepository.getUsers();
} catch (e) {
// If an exception occurs during the API call, set the error message to display the error.
_errorMessage = 'Failed to fetch users';
} finally {
// After API call is completed, set loading flag to false and notify listeners of data change.
_loading = false;
notifyListeners();
}
}
}
..
user_repository.dart : Handles fetching user data from the API.
// data/repositories/user_repository.dart
import '../models/user.dart';
import '../services/api_service.dart';
class UserRepository {
final ApiService apiService; // Instance of the ApiService class to perform API requests.
UserRepository({required this.apiService});
Future<List<User>> getUsers() async {
try {
// Call the getUsers() method from the ApiService to fetch user data from the API.
final data = await apiService.getUsers();
// Map the API response data to a List of User objects using the User.fromJson() constructor.
return data.map((json) => User.fromJson(json)).toList();
} catch (e) {
// If an exception occurs during the API call, throw an exception with an error message.
throw Exception('Failed to fetch users');
}
}
}
..
api_service.dart : Provides methods to interact with the API using Dio or other HTTP clients.
// data/services/api_service.dart
import 'package:dio/dio.dart';
class ApiService {
final Dio _dio; // Dio instance to perform HTTP requests.
ApiService({required Dio dio}) : _dio = dio;
Future<List<dynamic>> getUsers() async {
try {
// Make a GET request to the API endpoint to fetch user data.
final response = await _dio.get('https://jsonplaceholder.typicode.com/users');
// Check if the response status code is 200 (OK).
if (response.statusCode == 200) {
return response.data; // If successful, return the response data (List of dynamic).
} else {
// If the response status code is not 200, throw an exception with an error message.
throw Exception('API failed with status code: ${response.statusCode}');
}
} catch (e) {
// If any exception occurs during the API call, throw an exception with the error message.
throw Exception('An error occurred: $e');
}
}
}
..
..
https://jsonplaceholder.typicode.com/users
[
{
"id": 1,
"name": "Leanne Graham",
"username": "Bret",
"email": "Sincere@april.biz",
"address": {
"street": "Kulas Light",
"suite": "Apt. 556",
"city": "Gwenborough",
"zipcode": "92998-3874",
"geo": {
"lat": "-37.3159",
"lng": "81.1496"
}
},
"phone": "1-770-736-8031 x56442",
"website": "hildegard.org",
"company": {
"name": "Romaguera-Crona",
"catchPhrase": "Multi-layered client-server neural-net",
"bs": "harness real-time e-markets"
}
},
{
"id": 2,
"name": "Ervin Howell",
"username": "Antonette",
"email": "Shanna@melissa.tv",
"address": {
"street": "Victor Plains",
"suite": "Suite 879",
"city": "Wisokyburgh",
"zipcode": "90566-7771",
"geo": {
"lat": "-43.9509",
"lng": "-34.4618"
}
},
"phone": "010-692-6593 x09125",
"website": "anastasia.net",
"company": {
"name": "Deckow-Crist",
"catchPhrase": "Proactive didactic contingency",
"bs": "synergize scalable supply-chains"
}
},
]
..
Dear Author you done a great job a after read this article my all doubts are clear expect this Provides methods to interact with the API using Dio or other HTTP clients.can you provide me details about this sentence
ReplyDeletekind and regards