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:


Step 1: 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:



  dio: ^5.0.3
  provider: ^6.0.5
  logger: ^1.3.0

Then, run flutter pub get in your terminal to install these packages.

..

Step 2: Define the User model

Next, define a User class that will represent the data that you will fetch from the JSONPlaceholder API. You can create a new file called model / user_model.dart and add the following code:

// Step 1: Create a data model for the user object
class User {
  final int id; // User ID
  final String name; // User name
  final String email; // User email address

  User({required this.id, required this.name, required this.email});

  // Factory method to create a User object from a JSON map
  factory User.fromJson(Map json) {
    return User(
      id: json['id'], // Extract the ID from the JSON map
      name: json['name'], // Extract the name from the JSON map
      email: json['email'], // Extract the email address from the JSON map
    );
  }
}


In this code, the User class has three properties: id, name, and email. It also has a factory constructor that converts a JSON object to a User instance.
..
Step 3: Create the API service

Now, create a new file called controller / api_service.dart and add the following code:

import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import '../model/user.dart';
import 'logger_interceptor.dart';

// Step 2: Create a repository that handles fetching the data from the API
class UserRepository {
  // Create a Dio instance with base options and logger interceptor
  final Dio _dio = Dio(BaseOptions(
    baseUrl: 'https://jsonplaceholder.typicode.com',
    connectTimeout: const Duration(seconds:5),
    receiveTimeout: const Duration(seconds: 3),
    responseType: ResponseType.json,
  ))..interceptors.addAll([
    //LoggerInterceptor()
  ]);

  // This method makes a GET request to the '/users' endpoint of the API
  // and returns a list of User objects parsed from the response
  Future> getUsers() async {
    try {
      final response = await _dio.get('/users');
      final data = response.data as List;
      final users = data.map((json) => User.fromJson(json)).toList();
      return users;
    } catch (e) {
      // If the request fails, throw an exception with a message
      throw Exception('Failed to load users');
    }
  }
}



In this code, the ApiService class has a single method called getUsers()

This method uses the Dio package to make an HTTP GET request to the JSONPlaceholder API to fetch a list of users. It returns the response data as a list of dynamic objects.
..
Step 4: Implement the ViewModel

Now, create a new file called view_model / user_view_model.dart and add the following code:

// Step 3: Create a view model that uses the repository to get the data
import 'package:flutter/cupertino.dart';
import '../controller/api_service.dart';
import '../model/user.dart';

class UserViewModel extends ChangeNotifier {

  final UserRepository userRepository; // Declare a final UserRepository object
  UserViewModel({required this.userRepository}); // Constructor to initialize the UserRepository object

  List _users = []; // List of User objects
  bool _loading = false; // Boolean flag to track if data is loading or not
  String _errorMessage = ''; // String to hold error message if any

  List get users => _users; // Getter method to get the list of users
  bool get loading => _loading; // Getter method to get the loading flag
  String get errorMessage => _errorMessage; // Getter method to get the error message

  Future fetchUsers() async { // Async method to fetch users from repository
    _loading = true; // Set loading flag to true
    try {
      _users = await userRepository.getUsers(); // Call getUsers() method from UserRepository and assign the returned value to _users list
    } catch (e) { // Catch any exceptions
      _errorMessage = e.toString(); // Set the error message string to the exception string
    } finally {
      _loading = false; // Set loading flag to false
      notifyListeners(); // Notify listeners that the data has changed
    }
  }
}


In this code, the UserViewModel class extends the ChangeNotifier class, which means it can notify its listeners when its state changes. 

The UserViewModel class has a single method called fetchUsers(), which calls the getUsers() method of the ApiService class to fetch a list of users from the API. It then converts the response data into a list of User instances and sets the _users property to the loaded users. 

Finally, it calls notifyListeners() to notify its listeners that its state has changed.
..
Step 5: Create the UserList Widget

Now, create a new file called view / user_list.dart and add the following code:

// Step 5: Create the view
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../view_model.dart/user_view_model.dart';

class UserList extends StatefulWidget {
  const UserList({super.key}); // A stateful widget to display the users

  @override
  State createState() => _UserListState(); // Returns the state of the widget
}

class _UserListState extends State {
  @override
  void initState() { // Called when the state object is inserted into the tree.
    super.initState();

    final userViewModel = Provider.of(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( // 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()),
                  ),
                );
              },
            );
          }
        },
      ),
    );
  }
}



..
Step 6: Add the UserList to your app

Finally, you need to add the `UserList` widget to your app. You can add the following code to your `main.dart` file:

import 'package:dio_api/view/user_list.dart';
import 'package:dio_api/view_model.dart/user_view_model.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'controller/api_service.dart';

// Main function to run the app
 Future main() async {

  // Create a multi-provider for the app and provide UserViewModel
  runApp(
      MultiProvider(
        providers: [
          // Pass in the UserRepository instance as an argument
          ChangeNotifierProvider(create: (_) => UserViewModel(userRepository: UserRepository())),
        ],
        child: const MyApp(),
      ));
}

// MyApp widget to build the app
class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State createState() => _MyAppState();
}

// State for MyApp widget
class _MyAppState extends State {

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'DIO api',
      theme: ThemeData(
        useMaterial3: true,
        colorSchemeSeed: Colors.red,
      ),

      // Set the home screen to the UserList screen
      home:
      const Scaffold(
        body: UserList(),
      ),

    );
  }
}



..
Testing with API data
We will use{JSON} Placeholder to test our network data because it provides you with a hosted REST API consisting of sample user data and allows you to perform a variety of network operation tests.

Output:




..

..

GET source code on Github:

Comments