impak_retro 2.1.1 copy "impak_retro: ^2.1.1" to clipboard
impak_retro: ^2.1.1 copied to clipboard

ImpakRetro is a Dart library built on Dio, offering a type-safe, configurable, and user-friendly interface for handling HTTP requests and interacting with APIs.

ImpakRetro #

ImpakRetro is a powerful Dart library for handling HTTP requests. Built on top of the Dio package, it offers a type-safe, configurable, and easy-to-use interface for interacting with APIs.


Features #

  • Type-Safe Requests: Parse API responses into typed objects with ease.
  • Global and Request-Specific Authorization Tokens: Simplify authenticated API calls.
  • Customizable Logging: Leverage built-in logging or provide your custom logging interceptors.
  • Timeouts and Error Handling: Define request timeouts and handle API errors gracefully.
  • Singleton Support: Maintain a single ImpakRetro instance across your application.
  • File Uploads: Built-in support for form-data and file uploads.
  • Flexible Configuration: Customize base URLs, headers, and request parameters.
  • Client lifecycle interceptors: Register Dio-compatible hooks for pre-request work (tokens, refresh) and post-response / post-error work (401 handling, logging) via ImpakRetroClientInterceptor or ImpakRetroClientInterceptorCallbacks.

Installation #

Add impak_retro to your pubspec.yaml:

dependencies:
  impak_retro: ^2.1.1

Then run flutter pub get to install the package.

or run flutter pub add impak_retro to install the package package from terminal

Usage #

1. Initial Setup #

Using Singleton Instance

import 'package:impak_retro/impak.dart';


ImpakRetro.instance.init(
  useLogger: true, 
  baseUrl: "https://api.example.com", //optional
  authToken: "your_auth_token" //optional
);

Or Using Constructor

import 'package:impak_retro/impak.dart';


final impakRetro = ImpakRetro(
  userLogger: true,
  baseUrl: "https://api.example.com", //optional
  authToken: "your_auth_token", //optional
  timeout: 30, //optional
  timeUnit: TimeUnit.SECONDS, //optional
  // clientInterceptors: [ ImpakRetroClientInterceptorCallbacks(...) ], //optional; see Client lifecycle interceptors
);

You can also set an authorization token that you will use repeatedly using the static method setAuthToken example

ImpakRetro.setAuthToken("Bearer $token");

2. Client lifecycle interceptors #

You can attach one or more Dio Interceptor instances when you construct ImpakRetro or call init, using the clientInterceptors parameter. Types such as Interceptor, RequestOptions, DioException, and Dio are re-exported from package:impak_retro/impak.dart, so your app does not need a direct dio dependency for interceptors. They are inserted before the optional HTTP logger: on the way out they run first (good for attaching or refreshing tokens); on the way back Dio runs them last after the logger (good for global response or error policies).

Pass null for clientInterceptors on a later init to leave the current client interceptors unchanged. Pass [] to remove interceptors that were registered through this parameter on a previous init. Pass a non-empty list to replace that set.

The library provides:

  • ImpakRetroClientInterceptor — subclass and override beforeRequest, afterResponse, and/or afterError.
  • ImpakRetroClientInterceptorCallbacks — supply closures for the same three phases when you prefer not to subclass.

Each hook must end by calling exactly one of handler.next, handler.reject, or handler.resolve on the handler Dio gives you (same rules as a normal Dio interceptor). Omitted callbacks behave like the defaults and simply forward with next.

The callback class exposes three optional hooks:

Callback When it runs Typical uses
onBeforeRequest After options are composed, before the request is sent Check or refresh access token, set options.headers['Authorization'], add trace headers
onAfterResponse After a response object is received (any HTTP status) Audit logging, normalize headers, inspect response.statusCode before ImpakRetro maps the body
onAfterError When Dio surfaces an error Global 401/403 → sign out or navigate to login, refresh token + handler.resolve on a retried request

Example with all three callbacks (your app would inject services instead of inline comments):

import 'package:impak_retro/impak.dart';

final impakRetro = ImpakRetro(
  baseUrl: 'https://api.example.com',
  authToken: 'Bearer $accessToken',
  clientInterceptors: [
    ImpakRetroClientInterceptorCallbacks(
      onBeforeRequest: (options, handler) async {
        // Pre-request: e.g. await tokenRepository.ensureValidAccessToken();
        // options.headers['Authorization'] = 'Bearer ${tokenRepository.accessToken}';
        handler.next(options);
      },
      onAfterResponse: (response, handler) async {
        // Post-response: e.g. logging, metrics
        // if (response.statusCode == 204) { ... }
        handler.next(response);
      },
      onAfterError: (err, handler) async {
        final code = err.response?.statusCode;
        if (code == 401 || code == 403) {
          // Post-error: e.g. navigator to login, clear session
          // For refresh-and-retry, obtain a new token, clone err.requestOptions,
          // call dio.fetch, then handler.resolve(retryResponse).
        }
        handler.next(err);
      },
    ),
  ],
);

Using the singleton and init:

ImpakRetro.instance.init(
  baseUrl: 'https://api.example.com',
  authToken: 'Bearer $accessToken',
  clientInterceptors: [
    ImpakRetroClientInterceptorCallbacks(
      onBeforeRequest: (options, handler) async {
        handler.next(options);
      },
    ),
  ],
);

Subclassing ImpakRetroClientInterceptor

For larger apps you can extend the base class and override beforeRequest, afterResponse, or afterError instead of using callbacks.

import 'package:impak_retro/impak.dart';

class AuthLifecycleInterceptor extends ImpakRetroClientInterceptor {
  @override
  Future<void> beforeRequest(
    RequestOptions options,
    RequestInterceptorHandler handler,
  ) async {
    handler.next(options);
  }

  @override
  Future<void> afterError(
    DioException err,
    ErrorInterceptorHandler handler,
  ) async {
    handler.next(err);
  }
}

3. Making Requests #

The ImpakRetro class provides a typeSafeCall method for making type-safe HTTP requests, typeSafeFormDataCall for making type-safe form-data request as well raw requests using the call or formDataCall methods.

  • Type-Safe Requests:
  try{
    final result = await impakRetro.typeSafeCall<Response>(
        path: Constants.SAMPLE_PATH,// e.g "/api/v1/users
        successFromJson: (json) => Response.fromJson(json),
        method: RequestMethod.POST,
        //headers: {} //optional
        useAuthToken: true, //set this when you have set an auth token
        //authorizationToken: "Bearer ${Constants.TOKEN}" //this can be used to provide a different auth Token from the one provided at initialization
        body: {"password": Constants.SAMPLE_PASSWORD1,});

    if(result.isSuccessful){
      ///Response is my custom model and has a field `data`
      _result = result.asBody.data;
    }else {
      error = result.asError.toString(); //asError returns a dynamic data which conforms to what the api returns when there is an error
    }

    //Response can also be obtained using a switch statement as below
    switch(result){
      case ImpakRetroSuccess<Response>():
        _result = result.asBody.data;
        break;

      case ImpakRetroFailure():
        setState(() {
          error = result.error.toString();
        });
        break;

    }
  }catch(e){
    if(e.runtimeType is ImpakRetroException){
      e as ImpakRetroException;
      error = e.message;
      print(e.statusCode);
      switch(e.type){
        case ExceptionType.TIMEOUT_ERROR:
        //Custom implementation
        case ExceptionType.BAD_REQUEST:
        //Custom implementation
        case ExceptionType.SERVER_ERROR:
        //Custom implementation
        case ExceptionType.CANCELLED_ERROR:
        //Custom implementation
        case ExceptionType.UNKNOWN_ERROR:
        //Custom implementation
        case ExceptionType.MAPPING_ERROR:
        //Custom implementation
        case ExceptionType.AUTHORISATION_ERROR:
        //Custom implementation
        case ExceptionType.CONNECTION_ERROR:
        //Custom implementation
      }
    }
  }
  • Type-Safe Form-data Request:
    try{
      final file = File("some path");
      final result = await impakRetro.typeSafeFormDataCall<Response>(
          path: Constants.SAMPLE_PATH1,
          successFromJson: (json) => Response.fromJson(json),
          method: RequestMethod.POST,
          onProgress: (uploaded, total)=>{} //optional
          useAuthToken: true,
          baseUrl: Constants.BASE_URL,
          authorizationToken: "Bearer ${Constants.TOKEN1}",
          formData: ImpakRetroFormData({
            "File": file,
            "name": file.path,
            //and so on
          })
      );

      if(result.isSuccessful){
        ///Response is my custom model and has a field `data`
        _result = result.asBody.data;
      }else {
        error = result.asError.toString(); //asError returns a dynamic data which conforms to what the api returns when there is an error
      }

      //Response can also be obtained using a switch statement as below
      switch(result){
        case ImpakRetroSuccess<Response>():
          _result = result.asBody.data;
          break;

        case ImpakRetroFailure():
          setState(() {
            error = result.error.toString();
          });
          break;

      }
    }catch(e){
      if(e.runtimeType is ImpakRetroException){
        e as ImpakRetroException;
        error = e.message;
        print(e.statusCode);
        switch(e.type){
          case ExceptionType.TIMEOUT_ERROR:
            //Custom implementation
          case ExceptionType.BAD_REQUEST:
            //Custom implementation
          case ExceptionType.SERVER_ERROR:
            //Custom implementation
          case ExceptionType.CANCELLED_ERROR:
            //Custom implementation
          case ExceptionType.UNKNOWN_ERROR:
            //Custom implementation
          case ExceptionType.MAPPING_ERROR:
            //Custom implementation
          case ExceptionType.AUTHORISATION_ERROR:
            //Custom implementation
          case ExceptionType.CONNECTION_ERROR:
            //Custom implementation
        }
      }
    }
  • Raw Form-data Request:
    try{
      final file = File("some path");
      final result = await impakRetro.formDataCall(
          path: Constants.SAMPLE_PATH1,
          method: RequestMethod.POST,
          useAuthToken: true,
          onProgress: (uploaded, total)=>{} //optional
          baseUrl: Constants.BASE_URL,
          authorizationToken: "Bearer ${Constants.TOKEN1}",
          formData: ImpakRetroFormData({
            "File": file,
            "name": file.path,
            //and so on
          })
      );

      if(result.isSuccessful){
        ///Response is my custom model and has a field `data`
        ///`asBody` returns a dynamic result that matches `Response` model
        _result = Response.fromJson(result.asBody).data;
        ///OR
        ///
        _result = result.asBody["data"];
      }else {
        error = result.asError.toString(); //asError returns a dynamic data which conforms to what the api returns when there is an error
      }
      
    }catch(e){
      if(e.runtimeType is ImpakRetroException){
        e as ImpakRetroException;
        error = e.message;
        print(e.statusCode);
        switch(e.type){
          case ExceptionType.TIMEOUT_ERROR:
            //Custom implementation
          case ExceptionType.BAD_REQUEST:
            //Custom implementation
          case ExceptionType.SERVER_ERROR:
            //Custom implementation
          case ExceptionType.CANCELLED_ERROR:
            //Custom implementation
          case ExceptionType.UNKNOWN_ERROR:
            //Custom implementation
          case ExceptionType.MAPPING_ERROR:
            //Custom implementation
          case ExceptionType.AUTHORISATION_ERROR:
            //Custom implementation
          case ExceptionType.CONNECTION_ERROR:
            //Custom implementation
        }
      }
    }
  • Raw Request: A raw request other than form data request
    try{
      final result = await impakRetro.call(
          path: Constants.SAMPLE_PATH1,
          method: RequestMethod.GET,
          queryParameters: {
            "param1": 3,
            'param2': "other params"
          },
          baseUrl: Constants.BASE_URL,
          authorizationToken: "Bearer ${Constants.TOKEN1}",
      );

      if(result.isSuccessful){
        ///Response is my custom model and has a field `data`
        ///`asBody` returns a dynamic result that matches `Response` model
        _result = Response.fromJson(result.asBody).data;
        ///OR
        ///
        _result = result.asBody["data"];
      }else {
        error = result.asError.toString(); //asError returns a dynamic data which conforms to what the api returns when there is an error
      }

    }catch(e){
      if(e.runtimeType is ImpakRetroException){
        e as ImpakRetroException;
        error = e.message;
        print(e.statusCode);
        switch(e.type){
          case ExceptionType.TIMEOUT_ERROR:
            //Custom implementation
          case ExceptionType.BAD_REQUEST:
            //Custom implementation
          case ExceptionType.SERVER_ERROR:
            //Custom implementation
          case ExceptionType.CANCELLED_ERROR:
            //Custom implementation
          case ExceptionType.UNKNOWN_ERROR:
            //Custom implementation
          case ExceptionType.MAPPING_ERROR:
            //Custom implementation
          case ExceptionType.AUTHORISATION_ERROR:
            //Custom implementation
          case ExceptionType.CONNECTION_ERROR:
            //Custom implementation
        }
      }
    }

4. Exception Handling with ImpakRetroException #

The exceptions that are thrown during a request are instances of ImpakRetroException, which contains an ExceptionType enum. Use the switch statement to handle different types of exceptions:

try {
  final response = await ImpakRetro.instance.typeSafeCall<MyModel>(
    path: "/some-api-endpoint",
    method: RequestMethod.GET,
    successFromJson: (json) => MyModel.fromJson(json),
  );
} catch (e) {
  if (e is ImpakRetroException) {
    switch (e.type) {
      case ExceptionType.TIMEOUT_ERROR:
        print("Timeout error: ${e.message}");
        break;
      case ExceptionType.AUTHORISATION_ERROR:
        print("Authorization error: ${e.message}");
        break;
      case ExceptionType.BAD_REQUEST:
        print("Bad request: ${e.message}");
        break;
      case ExceptionType.SERVER_ERROR:
        print("Server error: ${e.message}");
        break;
      case ExceptionType.UNKNOWN_ERROR:
        print("Unknown error: ${e.message}");
        break;
      case ExceptionType.MAPPING_ERROR:
        print("Mapping error: ${e.message}");
        break;
      case ExceptionType.CANCELLED_ERROR:
        print("Request cancelled: ${e.message}");
        break;
      case ExceptionType.CONNECTION_ERROR:
        print("Connection error: ${e.message}");
        break;
    }
  }
}

Available Exception Types #

ImpakRetroException contains the following exception types:

  • TIMEOUT_ERROR: Thrown when the request times out.
  • AUTHORISATION_ERROR: Thrown when the user is unauthorized (e.g., invalid or missing token).
  • BAD_REQUEST: Thrown when the server returns a bad request response.
  • SERVER_ERROR: Thrown when the server encounters an error (status code 5xx).
  • UNKNOWN_ERROR: Thrown for any unknown errors.
  • MAPPING_ERROR: Thrown when the response cannot be mapped to the expected model.
  • CANCELLED_ERROR: Thrown when the request is cancelled.
  • CONNECTION_ERROR: Thrown when a connection error occurs.

Contribution #

If you'd like to contribute to this project, feel free to fork the repository, make your changes, and submit a pull request.

License #

This project is licensed under the BSD 3-Clause License - see the LICENSE file for details.

2
likes
160
points
224
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

ImpakRetro is a Dart library built on Dio, offering a type-safe, configurable, and user-friendly interface for handling HTTP requests and interacting with APIs.

Repository (GitHub)
View/report issues

License

BSD-3-Clause (license)

Dependencies

dio, flutter, http_parser, logger, os_mime_type, pretty_dio_logger

More

Packages that depend on impak_retro