Learn how to efficiently integrate Dio (HTTP client) with MVVM architecture to perform Get API calls in Flutter.
Explore step-by-step tutorials, best practices, and sample code for seamless API integration with nested JSON objects using Provider for state management.
dependencies:
flutter:
sdk: flutter
dio: ^5.3.0
provider: ^6.0.5
Dio MVVM Get API Integration with Provider in Flutter
Build a user object app demonstrating API integration and data management with Dio and Provider in a clean and organized manner.
main.dart : The main entry point of the Flutter app.
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';
import 'package:flutteryfly/views/user_object.dart';
import 'package:provider/provider.dart';
import './data/services/api_service.dart';
import 'data/models/user_object_repository.dart';
import 'data/repositories/user_object_repository.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 UserObjectRepository userObjectRepository = UserObjectRepository(apiService: apiService);
// Provider
runApp(
MultiProvider(
providers: [
// Provide the UserObjectViewModel with UserObjectViewModel dependency to manage product data and API calls
ChangeNotifierProvider<UserObjectViewModel>(
create: (context) => UserObjectViewModel(userRepository: userObjectRepository),
),
],
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'User Object Dio API', // Meta Title for the App
theme: ThemeData(
primarySwatch: Colors.green,
),
home: const UserObjectPage(),
);
}
}
..
user_object.dart : To displays user data fetched from an API using the Dio library and managed with the Provider package
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../data/models/user_object_repository.dart';
class UserObjectPage extends StatefulWidget {
const UserObjectPage({super.key});
@override
State<UserObjectPage> createState() => _UserObjectPageState();
}
class _UserObjectPageState extends State<UserObjectPage> {
@override
void initState() {
super.initState();
// Fetch product data when the state object is inserted into the tree.
final productViewModel = Provider.of<UserObjectViewModel>(context, listen: false);
productViewModel.fetchUserData();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Dio + Provider with API Object'),
),
body: Consumer<UserObjectViewModel>(
builder: (context, userViewModel, _) {
final user = userViewModel.user?.data;
final support = userViewModel.user?.support;
return Center(
child: userViewModel.loading
? const CircularProgressIndicator()
: userViewModel.errorMessage.isNotEmpty
? Text(userViewModel.errorMessage)
: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircleAvatar(
backgroundImage: NetworkImage(user?.avatar ?? ''),
radius: 50,
),
const SizedBox(height: 10),
Text('ID: ${user?.id ?? 'N/A'}'),
Text('Email: ${user?.email ?? 'N/A'}'),
Text('First Name: ${user?.firstName ?? 'N/A'}'),
Text('Last Name: ${user?.lastName ?? 'N/A'}'),
const SizedBox(height: 20),
Text('Support URL: ${support?.url ?? 'N/A'}'),
Text('Support Text: ${support?.text ?? 'N/A'}'),
],
),
);
},
),
);
}
}
..
user_object_view_model.dart : ViewModel class for managing user data and API calls.
// viewmodels/user_object_view_model.dart
import 'package:flutter/material.dart';
import 'package:flutteryfly/data/models/user_object.dart';
import '../repositories/user_object_repository.dart';
class UserObjectViewModel extends ChangeNotifier {
final UserObjectRepository userRepository;
// Constructor that takes a UserObjectRepository as a dependency
UserObjectViewModel({required this.userRepository});
// Private variables to hold user data and state information
UserObject? _user;
bool _loading = false;
String _errorMessage = '';
// Getters to access the private variables from outside the class
UserObject? get user => _user;
bool get loading => _loading;
String get errorMessage => _errorMessage;
// Function to fetch user data from the repository
Future<void> fetchUserData() async {
// Set loading to true before fetching data
_loading = true;
// Clear any previous error message
_errorMessage = '';
try {
// Fetch user data using the userRepository
_user = await userRepository.getUserData();
} catch (e) {
// If an error occurs during data fetching, set the error message
_errorMessage = 'Failed to fetch user data';
}
// Set loading to false after data fetching, regardless of success or failure
_loading = false;
// Notify listeners to update the UI with new data
notifyListeners();
}
}
..
user_object_repository.dart : Handles fetching user data from the API.
// data/repositories/user_object_repository.dart
import '../models/user.dart';
import '../models/user_object.dart';
import '../services/api_service.dart';
class UserObjectRepository {
final ApiService apiService;
// Constructor that takes an ApiService as a dependency
UserObjectRepository({required this.apiService});
// Function to fetch user data from the API
Future<UserObject> getUserData() async {
try {
// Call the API service to get user data with an ID of 2
final data = await apiService.getUserObject(2);
// Convert the fetched data into a UserObject model using the fromJson method
return UserObject.fromJson(data);
} catch (e) {
// If an error occurs during data fetching, throw an exception with an error message
throw Exception('Failed to fetch user data');
}
}
}
..
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';
import '../../utils/logger_interceptor.dart';
class ApiService {
late Dio _dio; // Dio instance to perform HTTP requests.
// Constructor that takes a Dio instance as a dependency
ApiService({required Dio dio}) {
// Initialize the Dio instance with base options and add interceptors.
_dio = Dio(BaseOptions(
//baseUrl: "https://dummyjson.com/products/",
// connectTimeout: const Duration(seconds:5),
// receiveTimeout: const Duration(seconds: 3),
responseType: ResponseType.json,
))..interceptors.addAll([
// Add any interceptors for the Dio instance here.
// For example, LoggerInterceptor() could be a custom logger interceptor.
// LoggerInterceptor(),
]);
}
// Function to fetch user object data from the API based on the provided ID
Future<Map<String, dynamic>> getUserObject(int id) async {
try {
// Perform a GET request to the API endpoint with the provided ID
final response = await _dio.get('https://reqres.in/api/users/$id');
// Check if the response status code is 200 (success)
if (response.statusCode == 200) {
// If successful, return the response data as a Map of dynamic.
return response.data;
} else {
// If the response status code is not 200, throw an exception with the status code.
throw Exception('API failed with status code: ${response.statusCode}');
}
} catch (e) {
// If an error occurs during the request or response handling, throw an exception with the error message.
throw Exception('An error occurred: $e');
}
}
}
..
..
{
"data": {
"id": 2,
"email": "janet.weaver@reqres.in",
"first_name": "Janet",
"last_name": "Weaver",
"avatar": "https://reqres.in/img/faces/2-image.jpg"
},
"support": {
"url": "https://reqres.in/#support-heading",
"text": "To keep ReqRes free, contributions towards server costs are appreciated!"
}
}
// data/models/user_object_model.dart
class UserObject {
final UserObjectData? data;
final Support? support;
UserObject({this.data, this.support});
factory UserObject.fromJson(Map<String, dynamic> json) {
return UserObject(
data: UserObjectData.fromJson(json['data']),
support: Support.fromJson(json['support']),
);
}
}
class UserObjectData {
final dynamic id;
final String? email;
final String? firstName;
final String? lastName;
final String? avatar;
UserObjectData({this.id, this.email, this.firstName, this.lastName, this.avatar});
factory UserObjectData.fromJson(Map<String, dynamic> json) {
return UserObjectData(
id: json['id'],
email: json['email'],
firstName: json['first_name'],
lastName: json['last_name'],
avatar: json['avatar'],
);
}
}
class Support {
final String? url;
final String? text;
Support({this.url, this.text});
factory Support.fromJson(Map<String, dynamic> json) {
return Support(
url: json['url'],
text: json['text'],
);
}
}