Effective handover of api responses to your code in dart
Tags: dart, dartlang, flutter, mobile
Published: 2021-12-14 19:39:21

**Caveat** - If you're not familiar with generics some of the code in this article might be a little confusing to you. I suggest reading up more on dart generics at [Dart Academy](https://dart.academy/generics-in-dart-and-flutter/). No need to worry, I'll wait here till you are more familiar with generics and then keep on explaining when you are ready! :grin:

## So you're consuming an API

One of the most common requirements in mobile applications is to consume an API. From the basic to some of the more complex applications out there use this to communicate either with a 3rd party service, talk to an application server or interact with server-less infrastructure like Firebase or the like.

When building code that consumes APIs there will always be some good stuff coming back but it could also send some bad stuff your way, in this post I'll take you through how we can effectively pass different types of responses back to our code and always expect the same result.

## Enough Talking, let's get into it

Generally when an API call is made there would be a few steps you would need to take before you can use the data returned from the host.
- Call the endpoint
- Wait for the response
- Parse the data or handle any errors
- Use the data in your code or show an error to the user

For most of this article I'll be using a single endpoint, extracted from one of our apps, that uses our own implementation of the dart http client for example purposes.

This endpoint will try and fetch a list of products from our API.
```dart
  Future<ProductResponse> getProducts(String country) async {
    return ProductResponse.fromJson(await _httpClient.get(
      endpoint: UrlPaths.PRODUCTS,
      endpointHeaders: {'Authorization': token},
      queryParameters: {
        r'pay_in_country': country,
      },
    ));
  }
```

There can be two types of responses for this endpoint

- A successful response with a list of products
- A error response with a code that defines what went wrong

To fetch the data we could simply call the `getProducts` method in our rest client and handle the response. That could look something like this.

```dart
try {
 final productResponse = await restClient.getProducts(country);
  for (var product in productResponse.items) {
    print(product.name);
  }
} on http.ClientException catch (e) {
  showErrorMessage(getErrorFromException(e));
} on TimeoutException catch (e) {
  showErrorMessage(getErrorFromException(e));
} on NetworkException catch (e) {
  // This is a specific type of exception 
  // that my client throws when there is an error code
  showErrorMessage(getErrorFromException(e));
}
```

This is fine, we're handling errors and using the data when we receive it successfully but you're probably asking yourself, is there a better way of doing this without all of the `try` `catch` boilerplate? :thinking:

## There's a better way
At [Mukuru](https://www.mukuru.com/) we use something called the [Repository Pattern](https://deviq.com/design-patterns/repository-pattern) that provides our business logic with easy to use methods to fetch data from our server.

A simple diagram for illustration purposes

![Diagram illustrating business logic - repository - rest client - API](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tujnm4w6yy0a6q7twjkd.png)


Although this diagram is cool it, and using the repository pattern in architectural design, is not the purpose of this article. This pattern just facilitates something that we call the `NetworkResponse`.

### What is the `NetworkResponse`

First we have to find out how it works. We can start by calling the `getProducts` method again but this time the result will come from our repository in the form of `NetworkResponse`.

**Let's get our products**
```dart
NetworkResponse productsResponse = await myRepository.getProducts(myCountryCode);
if (productsResponse.isSuccessful) {
  final products = productsRepsonse.result.items;
  for (var product in products) {
    print(product.name);
  }
} else {
  showErrorMessage(productsResponse.failure);
}
```
You can see that the result of our `getProducts` call is quite different coming from the repository than it is coming from the rest client in our earlier implementation.

Let's look at what the repository does when we call `getProducts` to return the `NetworkResponse`.
```dart
  Future<NetworkResponse<ProductResponse, NetworkException>> getProducts(
      String country) async {
    return makeRequest(() => restClient.getProducts(country));
  }
```

### Looking at the above code you might notice two things.

**1. The `makeRequest`** wrapper
The method `makeRequest` wrapping our `getProducts` call is a method defined in the base of all our repositories that handles the result from the server. 

Here is a simplified implementation of what `makeRequest` looks like.
```dart
  Future<NetworkResponse<T, NetworkException>> makeRequest<T>(Function request) async {
    if (await hasInternetConnection()) {
      try {
        final T result = await request();
        return NetworkResponse.success(result);
      } on SocketException {
        return NetworkResponse.failure(NetworkException(Strings.couldNotReachServer));
      } on TimeoutException {
        return NetworkResponse.failure(NetworkException(Strings.couldNotReachServer));
      } on NetworkException catch (networkException) {
        return NetworkResponse.failure(getErrorFromException(networkException));
      }
    } else {
      return NetworkResponse.failure(NetworkException(Strings.noNetworkException));
    }
  }
```

All network calls will be executed by `makeRequest` by passing the unexecuted future function as an argument and executing it in the the method. The result would then either pass or fail in this function, handled, and would be returned as a `NetworkResponse` to the repository allowing us to always return the same object as a result.

_The `makeRequest` method could also sit in the rest client itself but that's an architectural decision and out of the scope of this post._

**2. The two generic arguments that `NetworkResponse` consists of**
```dart
class NetworkResponse<S, F> {
  S? _s;
  F? _f;

  factory NetworkResponse.success(S _s) => NetworkResponse._(_s, null);
  factory NetworkResponse.failure(F _f) => NetworkResponse._(null, _f);

  NetworkResponse._(this._s, this._f);

  bool isSuccessful() => _f == null;

  S? result() => _s;
  F? failure() => _f;
}
```
Above is our `NetworkResponse` object, here you can see that it consists of two generic types. The first `S` is used as a success type and the second `F` as the failure type. We then have a few helper methods making it easier for the code using this object to see if the response was successful and to get the result based on that. 

## Wrap Up

With this new knowledge on `NetworkResult` and repositories let's look at this code again with some more detail.
```dart
NetworkResponse<ProductResponse, NetworkException> productsResponse = await myRepository.getProducts(myCountryCode);
if (productsResponse.isSuccessful) {
  final products = productsRepsonse.result.items;
  for (var product in products) {
    print(product.name);
  }
} else {
  showErrorMessage(productsResponse.failure);
}
```

- We see that calling the `getProducts` method in our repository triggers a call to the rest client via the `makeRequest` method.
- The rest client will call the API, parse the response and give back the data OR throw an exception on an error code or if something else goes wrong.
- The `makeRequest` method will receive the data or the exception based on what happened in the rest client and hand back the `NetworkResponse` result back to the `getProducts` method
- The `NetworkResponse` is then given back as a result to our code above.

The beauty of this is that you can always expect the same result from any endpoint and you will almost never need to use a `try` `catch` in your code calling APIs again.

I hope this was not a waste of your time! Be kind :point_down:

<ahref='https://www.freepik.com/photos/background'>Header supplied by - www.freepik.com</a>