# Better HTTP with Dio

The scenario in our projects where we must call an external API via HTTP is never missing.

To achieve this in Dart, we have the official [`package:http`](https://pub.dev/packages/http). This provides us with functions and classes that fulfill what is necessary to make these HTTP calls in a simple way and without much torture.

`http` is characterized by being based on the `Future` data type for requests and can also be used on all stable platforms that Flutter supports.

With `http` we can make calls in a few lines as follows...

```dart
import 'package:http/http.dart' as http;

Future<void> main() async {
  final url = 'https://jsonplaceholder.typicode.com/todos/1';

  final response = await http.get(url);

  print('Response statusCode: ${response.statusCode}');
  print('Response body: ${response.body}');
}
```

And many examples in the community are made using `http` because in many cases it solves more than enough.

But not all is so easy. Sometimes we have use cases where our APIs expose us to endpoints that contain more complex functionality like uploading/downloading a file, streaming data, etc.

There are many cases where this functionality can be implemented in `http` but the detail is that it is not very easy to do it, [here](https://stackoverflow.com/questions/63195155/dart-flutter-post-request-and-stream-response) you can see an example.

# Enter Dio

As always, I like to show you alternatives to the usual packages we use and show you the advantages, disadvantages and why these options are worth trying in your future developments.

[`package:dio`](https://pub.dev/packages/dio) is an alternative to `package:http` and helps us incorporate everything I was telling you about so we don't have to spend time building on top of `http` to develop functionality.

Its usage is very similar to `http` and we just need to instantiate a `Dio` object which would already be our HTTP client.

```dart
import 'package:dio/dio.dart';

Future<void> main() async {
  final url = 'https://jsonplaceholder.typicode.com/todos/1';

  final response = await Dio().get(url);

  print('Response statusCode: ${response.statusCode}');
  print('Response body: ${response.data}');
}
``` 

You can see that the only change I made was to change the `body` property of the `Response` in `http` to the `data` property of the `Response` in `package:dio`. The only difference between `body` and `data` is that the latter is decoded by `Dio` if it is a `Map`.

But hey, that's pretty basic, right?

Let's go with the advantages offered by this package.

# BaseOptions

`dio` allows us to create an object with all the base configurations to interact with our API. We can inject this object as dependencies of our classes in charge of the calls to the API (or APIs).

By the way, we can define different `Dio` objects, as I mentioned, each one with a different configuration for specific endpoints or APIs. But let's see how this configuration is done.

```dart
import 'package:dio/dio.dart';

Future<void> main() async {
  final options = BaseOptions(
    baseUrl: 'https://jsonplaceholder.typicode.com/',
    connectTimeout: 5000,
    receiveTimeout: 3000,
    responseType: ResponseType.json,
  );

  final dio = Dio(options);

  final response = await dio.get('todos/1');

  print('Response statusCode: ${response.statusCode}');
  print('Response body: ${response.data}');
}
``` 

If you run this code, you'll notice that we can still use that `Dio` object to make the same request with a set of our `baseUrl`. So when using the `get` method we only need to specify the endpoint we want to access to retrieve that information.

As I said, we can create several objects that contain different `BaseOptions` in case they implement a microservices architecture where they can have a client for each API they use.

```dart
import 'package:dio/dio.dart';

Future<void> main() async {
  // ... previous request

  final optionsRickAndMorty = BaseOptions(
    baseUrl: 'https://rickandmortyapi.com/api',
    connectTimeout: 5000,
    receiveTimeout: 3000,
    responseType: ResponseType.json,
  );

  final dioRickAndMorty = Dio(optionsRickAndMorty);
  final ricksponse = await dioRickAndMorty.get('/episode/1');

  print('Ricksponse statusCode: ${ricksponse.statusCode}');
  print('Ricksponse episode name: ${ricksponse.data['name']}');
}
``` 

Here I make a call to another very famous API that my friends from [**Flutter Spain**](https://twitter.com/flutter_es) use, [**RickAndMortyAPI**](https://rickandmortyapi.com/documentation). In this case, I create another client and another configuration to call that other endpoint and I still show the information of both.

Microservices enthusiasts will highly appreciate this functionality. You can check more of the options by [here](https://pub.dev/packages/dio#request-options).

By the way, you can see that `data` works identically to `body` when we want to access a specific property of our `Response`.

# Concurrency

This is not a feature of `dio` but I would like to mention it since many do not know that it exists and it is very useful.

As you saw in the previous example, I am making 2 HTTP requests sequentially and at first glance it is fine, perhaps it would be necessary to separate these functions each one on its own so as not to have such a large main function. But another way we can organize our code is with a `Future.wait`.

```dart
final concurrentResponse = await Future.wait([
	dioPlaceholder.get('todos/1'),
	dioRickAndMorty.get('/episode/1'),
]);

concurrentResponse.forEach((res) => print('\n${res.data}'));
``` 

With `Future.wait` we can send the list of `Futures` or requests that we are going to make and this method returns a list of responses where we can access each one and in this case simply print it to the console.

# Download files

If you have projects that require the internal use of files, they will likely require downloading some files to save on the devices that your app operates.

This can be done with `http` but it takes us a long time to implement from scratch. Yes, you guessed it, `dio` makes it easy for us like this:

```dart
import 'package:dio/dio.dart';

Future<void> main() async {
  final client = Dio();
  final url = 'https://images.unsplash.com/photo-1567026392301-672e510f3369';

  final directory = await getTemporaryDirectory();
  final fileName = '${directory.path}/downloads';

  await client.download(
    url,
    fileName,
    options: Options(headers: {HttpHeaders.acceptEncodingHeader: '*'}),
    onReceiveProgress: (received, total) {
      if (total != -1) {
        final pos = received / total * 100;
        print(pos);
      }
    },
  );
}
``` 

Analyzing how to do these downloads, we must have --as usual-- our `Dio` client, the `url` of the file we want to download, a temporary directory that we get using [`package:path_provider`](https ://pub.dev/packages/path_provider), and also a directory on the device where you save those files you download.

Then with the asynchronous `download` method, we have all the communication with this file hosted on a remote server. We can optionally assign additional configuration via `Options`, for example, an `acceptEncoding` that allows us to tell the server which compression algorithms for the content our client supports.

The most important thing here is that `download` gives us access to a callback called `onReceiveProgress` which gives us two integer values: `received` and `total`. The first gives us the number of bytes of the file that have been received on the device and the second is the total number of bytes of the file.

Now they can safely download files in their apps.

> ⚠️ If there is an error somewhere in the download, `dio` stops and deletes everything that was downloaded.

# Upload files

Uploading files is a normal POST request with a few tweaks involved.

```dart
import 'package:dio/dio.dart';

Future<void> main() async {
  final client = Dio();
  final url = 'https://some-api.dev/';

  final directory = await getTemporaryDirectory();
  final fileName = '${directory.path}/rant_about_the_world.txt';

  final payload = FormData.fromMap({
    'user': 'marcossevilla',
    'essay': await MultipartFile.fromFile(fileName),
  });

  await client.post(
    '$url/user/new',
    data: payload,
    onSendProgress: (sent, total) {
      final progress = sent / total * 100;
      print(progress);
    },
  );
}
``` 

What we do in this part is use a file from the device, whose location we contain --additionally its name-- in `fileName`. And next, we form a `FormData` object, similar to the one used in HTML, which is capable of saving the information with a `fromMap` constructor, and if we want to send a file, which will usually take a moment to upload, we use the class from `MultipartFile` which contains an asynchronous `fromFile` method to upload the file.

When uploading the file, we must wait for `MultipartFile` to finish executing its method, so we use an `await`.

Likewise, the POST provides us with the two callbacks `onReceiveProgress` and `onSendProgress` that allow us to calculate percentages to display download/upload information in our graphical interface.

# Bonus tips

I can't leave out my favorite topics: architecture and code structure. I'm not going to make a long article explaining how to use `dio` for your architectures, but how to structure a data layer based on `dio`.

Orienting it to a microservices architecture where we have different APIs for separate modules (or even very specific actions), I recommend separating them by API that you use. This way you can have a client for each API with whatever `BaseOptions` you need.

```sh
data/
	|- rick_and_morty_api/
	|- joke_api/
	|- another_api/
``` 

Now that we have our API folder, we can separate it into 2–3 main groups:

1. Models: These are the classes that represent the data that we are going to send or receive from the API.

2. Parsers: These are classes that abstract the conversion logic of the `String` encoded in JSON to a Dart object. This same logic could be implemented either inside the model or in a `DataSource` or `DataProvider`. They are not so necessary but many like to use them.

3. Datasources / DataProviders: They are in charge of executing the calls to the APIs through a specific client.

Our folder structure would look like this:

```sh
data/
	|- rick_and_morty_api/
		|- models/
		|- parsers/
		|- data_providers/
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1643682617202/A_SQTo3xT.gif)
