Dio MVVM Get API Integration with Provider in Flutter (With Nested JSON Object)

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');
    }
  }
}

..

..

Json Nested JSON Object:

{
  "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
}
..

Comments