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

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

..



..

Json Object:

{
  "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!"
  }
}
..
user_object_model.dart : Data class / model classes for parsing and representing user object data obtained from an API response:


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

..

Comments