# Type safe http calls in Flutter with Chopper

# Type safe HTTP calls in Flutter with Chopper

## Problem:

I am not sure about you but for me when I started working with Flutter (or dart) I had great difficulty in handling API calls and JSON responses because of lack of a native JSON support as easy as python or Js (the languages I was previously working with), I mean the data type `Map<String, dynamic>` is almost equivalent to a JSON but naah. Also, I believe when you are going with a strictly typed language like dart you would like your API responses to also be dart objects rather than a random Map.

Well my current project requires a lot of communication with an Express-based API, initially I was using flutter team's own [http package](https://pub.dev/packages/http) which was really good but adding query parameters and headers in them is terribly handled and in short, I don't like it.

##### That's when I came across **Chopper** which is dubbed as ***retrofit for flutter,*** this is what I'll be talking about in this blog



# Solution: [Chopper](https://pub.dev/packages/chopper)

Well chopper is a flutter package that helps you simplify your REST call

## API/HTTP calls will be handled using chopper

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

// the line below is strictly supposed to be <fileName>.chopper.dart
part 'chopper.chopper.dart';

@ChopperApi(baseUrl: 'api/v1/')
abstract class API extends ChopperService {
  @Get()
  Future<Response> getter();

  @Post()
  Future<Response> post(@Body() Map<String, dynamic> body);

  static API create() {
    final client = ChopperClient(
        baseUrl: 'https://d2def3d9f930.ngrok.io/',
        services: [_$API()],
        converter: JsonConverter());
  }
}
```

Well, chopper is one those dependencies which generate code for you ***coooool,*** that's why we need the *part of* directive.

The above snippet is the simplest implementation of the chopper packages without any type safety or interceptors. Showing only 2 calls a get and a post request both to `https://d2def3d9f930.ngrok.io/api/v1`

we can run the following command to build the <filename>.chopper.dart file

```bash
flutter packages pub run build_runner watch
```

Your Service should be an abstract class and should extend the ChopperService class

The decorator @ChopperAPI is needed as it defines the abstract class (or interface) as a ChopperService

Every call is a separate function and the method is defined by the corresponding decorator, which takes in a fixe

- path<String>: the path duh 🤷🏻
- headers<Map<String, dynamic>>: A dictionary/Map of headers apart from default ones

##### Dynamic decorators as parameters

This is followed by a list of parameters to the actual function, these need to specified with suitable decorators like

- @Path: if you have a variable path as in you send a user id or order id in your path itself you can use this directive

    ```dart
    @Get(path: '/hashnode/{user}')
    Future<Response> getUser(@Path String userId); 
    ```

- @Query: To send query parameters with the call

    ```dart
    @Get(path: '/hashnode/iresharma')
    Future<Response> getUser(@Query('search') String searchString);
    ```

- @QueryMap: To send a series of Query parameters you can use this directive to pass a Map which will be converted to multiple queries

    ```dart
    @Get(path: '/hashnode/iresharma')
    Future<Response> getUser(@QueryMap() Map<String, dynamic> queries);
    ```

- @Headers: To send headers along, I am not sure but this is the only way you can send dynamic headers

    ```dart
    @Get(path: '/hashnode/iresharma')
    Future<Response> getUser(@Headers('search') String searchString);
    ```

- @Body: To send a Body in case of POST or PUT or PATCH request

    ```dart
    @Post(path: '/hashnode/iresharma')
    Future<Response> getUser(@Body() Map<String, dynamic> queries);
    ```

### Let's talk about the `Request` and `Response` objects

- **Response Object**

    The response object is pretty simple and has only a few attributes

    - base<BaseResponse>

        which contains the Request, statuscode and some stuff

    - Body<BodyType>

        this contains body of the response. The datatype <Bodytype> is determined by the converter you use

    - error<Object>
- **Request Object**

    Well here's the variable declaration for the Request class

    ```dart
    	final String method;
      final String baseUrl;
      final String url;
      final dynamic body;
      final List<PartValue> parts;
      final Map<String, dynamic> parameters;
      final Map<String, String> headers;
      final bool multipart;
    ```

    it's pretty self-explanatory

## Interceptors

Interceptors are basically a set of commands that are run with every command

All interceptors are client wide

we put interceptors inside the client

 

```dart
static API create() {
    final client = ChopperClient(
        baseUrl: 'https://d2def3d9f930.ngrok.io',
        services: [_$API()],
				interceptors: [],
        converter: JsonConverter());
  }
```

A few built-in interceptors are:

- HeadersInterceptor

    ```dart
    static API create() {
        final client = ChopperClient(
            baseUrl: 'https://d2def3d9f930.ngrok.io',
            services: [_$API()],
    				interceptors: [
    					HeadersInterceptor({'Content-type': 'Application/json'})
    				],
            converter: JsonConverter());
      }
    ```

    The above code will add the `'Content-type': 'Application/JSON'` header to all the requests that it makes.

- HttpLoggingInterceptor

    We use this header to log everything that's happening with the request, but this requires some setup which is as follows:

    we include the logging package which is preloaded by chopper.

    Add make the following changes to main.dart

    ```dart
    void main() {
    		Logger.root.level = Level.ALL;
    		Logger.root.onRecord((rec) {
    			print('${rec.level) ${rec.message});		
    		});
    		runApp(MyAPP());
    }
    ```

    The above code will instantiate the Logger and now we can add the `HttpLoggingInterceptor` interceptor to the Client

    ```dart
    static API create() {
        final client = ChopperClient(
            baseUrl: 'https://d2def3d9f930.ngrok.io',
            services: [_$API()],
    				interceptors: [
    					HeadersInterceptor({'Content-type': 'Application/json'}),
    					HttpLoggingInterceptor()
    				],
            converter: JsonConverter());
      }
    ```

    **I personally use a custom logging interceptor, this prints way more information than needed and makes the debug console very very messy**

- CurlInterceptor

    This is not used as much but It's really cool because it prints out the curl command for the request just made

    ### Custom interceptors

    +— async functions

    +— class-based

    **Async Functions**

    we can create both request and response interceptors and I'd suggest not using the HttpLoggingInterceptor because it logs a lot of not so important information like the whole response which may make the more important information like the route, headers and query parameter difficult to read

    ```dart
    static API create() {
        final client = ChopperClient(
            baseUrl: 'https://d2def3d9f930.ngrok.io',
            services: [_$API()],
    				interceptors: [
    					HeadersInterceptor({'Content-type': 'Application/json'}),
    					// HttpLoggingInterceptor(),
    					(Request req) async {
    							chopperLogger.info('${req.headers}');
    							// VERY VERY IMPORTANT TO RETURN req
    							return req;
    					}
    				],
            converter: JsonConverter());
      }
    ```

    The above code is an example of request interceptor which logs the headers of the request, note it's **very very important to return the req back from the function** because the way it's made it does not give a lint but ends up crashing or compilation error

    below is an example of a response interceptor

    ```dart
    static API create() {
        final client = ChopperClient(
            baseUrl: 'https://d2def3d9f930.ngrok.io',
            services: [_$API()],
    				interceptors: [
    					HeadersInterceptor({'Content-type': 'Application/json'}),
    					// HttpLoggingInterceptor(),
    					(Request req) async {
    							chopperLogger.info('${req.headers}');
    							// VERY VERY IMPORTANT TO RETURN req
    							return req;
    					},
    					(Response res) {
    						chopperLogger('${res.statusCode}');
    					}
    				],
            converter: JsonConverter());
      }
    ```

    **Custom Class-based interceptors**

    Well according to our need we can have various reasons and functionality where we'd want to have interceptors but here we are gonna implement a simple interceptor which does not make the request if the user is on mobile data

    Important to note the below example uses `connectivity` package 

    ```dart
    class MobileDataInterceptor implements RequestInterceptor {
    	@override
    	FutureOr<Request> onRequest(Request req) {
    		final connection = await connectivity().checkConnnectivity();
    		if(connection == ConnenctivityResult.mobile) {
    				// throw an appropriate exception
    		}
    		return req;
    	}
    }
    ```

### Custom convertors

The code below is a generic converter

```dart
import 'dart:convert';

import 'package:chopper/chopper.dart';

class GenericConvertor extends JsonConverter {
  final Map<Type, Function> typeToJsonFactoryMap;

  GenericConvertor(this.typeToJsonFactoryMap);

  @override
  Response<BodyType> convertResponse<BodyType, InnerType>(Response response) {
    return response.copyWith(
      body: fromJsonData<BodyType, InnerType>(
          response.body, typeToJsonFactoryMap[InnerType]),
    );
  }

  T fromJsonData<T, InnerType>(String jsonData, Function jsonParser) {
    var jsonMap = json.decode(jsonData);
    if (jsonMap is List) {
      return jsonMap
          .map((item) => jsonParser(item as Map<String, dynamic>) as InnerType)
          .toList() as T;
    }

    return jsonParser(jsonMap);
  }
}
```

How does this work ???

well the convertor is called like

```dart
GenericConvertor({
  UserModel: (jsonData) => UserModel.fromJson(jsonData),
  AuthCheck: (jsonData) => AuthCheck.fromJson(jsonData)
})
```

Basically, it takes in a `Map<Type, function>` corresponding to the class and the fromJson() function for each type

Then it runs a function which copies the response object and replaces the body with the object we want

it does that running the fromJson function and encoding the object as a bodyType, as required by the response object.

**Even though it handles the list but that only works if the body of your response is directly a list else it breaks, and this true for normal values as well it works only if the body is directly the object you want**

For a case where the data structure is different, you use the code snippet below as reference to write a custom converter

the data i received had body['chapters'] = List<ChapterModel> so i had to use this convertor

```dart
import 'dart:convert';

import 'package:chopper/chopper.dart';
import 'package:learners/models/DataModels/Chapters/chapterModel.dart';

class SubjectConvertor extends JsonConverter {
  @override
  Response<BodyType> convertResponse<BodyType, InnerType>(Response response) {
    return response.copyWith(
      body: fromJsonData<BodyType, InnerType>(response.body),
    );
  }

  T fromJsonData<T, InnerType>(String jsonData) {
    var jsonMap = json.decode(jsonData);
    List<dynamic> chapters = jsonMap['chapters'];
    if (chapters == null) {
      return ChapterModel.fromJson(jsonMap['chapter']) as T;
    }
    return chapters.map((e) => ChapterModel.fromJson(e)).toList() as T;
  }
}
```

##### for more watch the tutorial by reso coder

[ResoCoder Chopper](https://www.youtube.com/watch?v=zFXK5EsrUF0)

