Learn how to integrate Dio (HTTP client) with MVVM architecture to perform Get API calls efficiently (With Nested JSON Object).
Explore sample code, step-by-step tutorial, and best practices for seamless API integration.
dependencies:
flutter:
sdk: flutter
dio: ^5.3.0
provider: ^6.0.5
Dio MVVM Product List App
Build a Flutter product 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.
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';
import 'package:flutteryfly/viewmodels/product_view_model.dart';
import 'package:flutteryfly/views/product_list.dart';
import 'package:provider/provider.dart';
import './data/repositories/user_repository.dart';
import './data/services/api_service.dart';
import 'data/repositories/product_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 UserRepository userRepository = UserRepository(apiService: apiService);
final ProductRepository productRepository =
ProductRepository(apiService: apiService);
// Provider
runApp(
MultiProvider(
providers: [
// Provide the UserViewModel with UserRepository dependency to manage user data and API calls
/* ChangeNotifierProvider<UserViewModel>(
create: (context) => UserViewModel(userRepository: userRepository),
),*/
// Provide the ProductViewModel with ProductRepository dependency to manage product data and API calls
ChangeNotifierProvider<ProductViewModel>(
create: (context) =>
ProductViewModel(productRepository: productRepository),
),
],
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'User List', // Meta Title for the App
theme: ThemeData(
primarySwatch: Colors.green,
),
home: const ProductList(),
);
}
}
..
product_list.dart : View file for displaying the list of users.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../viewmodels/product_view_model.dart';
class ProductList extends StatefulWidget {
const ProductList({Key? key}) : super(key: key);
@override
State<ProductList> createState() => _ProductListState();
}
class _ProductListState extends State<ProductList> {
@override
void initState() {
super.initState();
// Fetch product data when the state object is inserted into the tree.
final productViewModel =
Provider.of<ProductViewModel>(context, listen: false);
productViewModel.fetchProducts();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Products'),
),
body: Consumer<ProductViewModel>(
builder: (context, productViewModel, _) {
if (productViewModel.loading) {
return const Center(
child: CircularProgressIndicator(),
);
} else if (productViewModel.errorMessage.isNotEmpty) {
return Center(
child: Text(productViewModel.errorMessage),
);
} else {
return ListView.builder(
itemCount: productViewModel.products.length,
itemBuilder: (context, index) {
final product = productViewModel.products[index];
// Display a list of products with their name, category, and price.
return ListTile(
title: Text(product.title),
subtitle: Text(product.category),
trailing: Text('\$${product.price.toStringAsFixed(2)}'),
);
},
);
}
},
),
);
}
}
..
product_view_model.dart : ViewModel class for managing user data and API calls.
import 'package:flutter/material.dart';
import '../data/models/product.dart';
import '../data/repositories/product_repository.dart';
class ProductViewModel extends ChangeNotifier {
final ProductRepository productRepository;
ProductViewModel({required this.productRepository});
List<Product> _products =
[]; // List to store product 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<Product> get products =>
_products; // Getter method to access the list of products.
bool get loading => _loading; // Getter method to access the loading flag.
String get errorMessage =>
_errorMessage; // Getter method to access the error message.
Future<void> fetchProducts() async {
_loading = true; // Set loading flag to true before making the API call.
_errorMessage = ''; // Clear any previous error message.
try {
// Call the getProducts() method from the ProductRepository to fetch product data from the API.
_products = await productRepository.getProducts();
} catch (e) {
// If an exception occurs during the API call, set the error message to display the error.
_errorMessage = 'Failed to fetch products';
} finally {
// After API call is completed, set loading flag to false and notify listeners of data change.
_loading = false;
notifyListeners();
}
}
}
..
product_repository.dart : Handles fetching user data from the API.
// data/repositories/product_repository.dart
import '../models/product.dart';
import '../services/api_service.dart';
class ProductRepository {
final ApiService
apiService; // Instance of the ApiService class to perform API requests.
ProductRepository({required this.apiService});
Future<List<Product>> getProducts() async {
try {
// Call the getProducts() method from the ApiService to fetch product data from the API.
final data = await apiService.getProducts();
// Extract the 'products' list from the API response data.
final productsData = data['products'];
// Map the 'products' list to a List of Product objects using the Product.fromJson() constructor.
return productsData
.map<Product>((json) => Product.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 products');
}
}
}
..
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');
}
}*/
Future<Map<String, dynamic>> getProducts() async {
try {
// Make a GET request to the API endpoint to fetch product data.
final response = await _dio.get('https://dummyjson.com/products/search?q=Laptop');
// Check if the response status code is 200 (OK).
if (response.statusCode == 200) {
return response.data; // If successful, return the response data (Map 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');
}
}
}
..
..
{
"products": [
{
"id": 7,
"title": "Samsung Galaxy Book",
"description": "Samsung Galaxy Book S (2020) Laptop With Intel Lakefield Chip, 8GB of RAM Launched",
"price": 1499,
"discountPercentage": 4.15,
"rating": 4.25,
"stock": 50,
"brand": "Samsung",
"category": "laptops",
"thumbnail": "https://i.dummyjson.com/data/products/7/thumbnail.jpg",
"images": [
"https://i.dummyjson.com/data/products/7/1.jpg",
"https://i.dummyjson.com/data/products/7/2.jpg",
"https://i.dummyjson.com/data/products/7/3.jpg",
"https://i.dummyjson.com/data/products/7/thumbnail.jpg"
]
},
{
"id": 8,
"title": "Microsoft Surface Laptop 4",
"description": "Style and speed. Stand out on HD video calls backed by Studio Mics. Capture ideas on the vibrant touchscreen.",
"price": 1499,
"discountPercentage": 10.23,
"rating": 4.43,
"stock": 68,
"brand": "Microsoft Surface",
"category": "laptops",
"thumbnail": "https://i.dummyjson.com/data/products/8/thumbnail.jpg",
"images": [
"https://i.dummyjson.com/data/products/8/1.jpg",
"https://i.dummyjson.com/data/products/8/2.jpg",
"https://i.dummyjson.com/data/products/8/3.jpg",
"https://i.dummyjson.com/data/products/8/4.jpg",
"https://i.dummyjson.com/data/products/8/thumbnail.jpg"
]
},
],
"total": 2,
"skip": 0,
"limit": 2
}