easy_api_provider

pub package pub points MIT License Flutter

A lightweight Flutter HTTP client built on Dio that handles REST API requests with built-in UI state management. Stop writing repetitive loading, success, error, and empty widget logic — easy_api_provider does it for you.

Why easy_api_provider?

Without easy_api_provider With easy_api_provider
Manual try/catch on every request Automatic error handling, no exceptions thrown
Build your own loading/success/error UI Built-in ApiProviderUi widget with animated transitions
Duplicate Dio setup across projects Singleton ApiProvider with one-line config
Manage API state with boilerplate ApiProviderController handles idle, loading, success, error, empty
No request logging out of the box Colored request/response/error logs via TalkerDioLogger

Features

  • All HTTP methods: GET, POST, PUT, PATCH, DELETE, file download
  • Reactive UI states: idle, loading, success, error, empty with animated transitions
  • Automatic error handling: catches DioException, TimeoutException, and unexpected errors
  • No exceptions thrown: every request returns a typed ApiResponse object
  • Request logging: colored console output for debugging with TalkerDioLogger
  • Interceptor support: custom onRequest, onResponse, and onError callbacks
  • Authorization management: set/update/remove auth headers at runtime
  • Query parameters, headers, timeouts: fully configurable per-request or globally
  • Cancel token support: cancel in-flight requests
  • Upload/download progress: track send and receive progress
  • Clean architecture friendly: works with any state management solution
  • Dart 3 ready: requires Dart >=3.0.0

Quick Start

Installation

Add to your pubspec.yaml:

dependencies:
  easy_api_provider: ^2.0.0

Or run:

flutter pub add easy_api_provider

Initialize

import 'package:easy_api_provider/easy_api_provider.dart';

void main() {
  ApiProvider.instance.init(ApiProviderConfig(
    'https://dummyjson.com',
    contentType: 'application/json',
    connectTimeout: const Duration(seconds: 30),
    receiveTimeout: const Duration(seconds: 30),
    headers: {'Accept': 'application/json'},
    requestLogger: true,
  ));

  runApp(const MyApp());
}

Usage

GET Request

final ApiResponse response = await ApiProvider.instance.get('/posts');

if (response.success) {
  print(response.data); // List of posts
} else {
  print(response.message); // Error message
}

POST Request

final ApiResponse response = await ApiProvider.instance.post(
  '/posts/add',
  data: {'title': 'Hello', 'body': 'World', 'userId': 1},
);

PUT Request

final ApiResponse response = await ApiProvider.instance.put(
  '/posts/1',
  data: {'title': 'Updated Title'},
);

PATCH Request

final ApiResponse response = await ApiProvider.instance.patch(
  '/posts/1',
  data: {'title': 'Patched Title'},
);

DELETE Request

final ApiResponse response = await ApiProvider.instance.delete('/posts/1');

File Download

final ApiResponse response = await ApiProvider.instance.download(
  '/file.pdf',
  '/save/path/file.pdf',
  onReceiveProgress: (received, total) {
    print('$received / $total');
  },
);

Query Parameters

final ApiResponse response = await ApiProvider.instance.get(
  '/posts',
  params: {'limit': 5, 'skip': 0},
);

Cancel a Request

final cancelToken = CancelToken();

// Start request
ApiProvider.instance.get('/posts', cancelToken: cancelToken);

// Cancel it
cancelToken.cancel('User cancelled');

Set Authorization Header

// Set token
ApiProvider.instance.setAuthorisation('Bearer your_token_here');

// Remove token (e.g. on logout)
ApiProvider.instance.setAuthorisation(null);

// Change base URL at runtime
ApiProvider.instance.setBaseUrl('https://api.v2.example.com');

Interceptors

ApiProvider.instance.init(ApiProviderConfig(
  'https://api.example.com',
  onRequest: (options) {
    print('Request: ${options.method} ${options.path}');
  },
  onResponse: (response) {
    print('Response: ${response.statusCode}');
  },
  onError: (error) {
    print('Error: ${error.message}');
  },
));

UI State Management

ApiProviderUi is a widget that automatically switches its child based on the current API state.

Controller

final controller = ApiProviderController();

Widget

ApiProviderUi(
  controller: controller,
  idleWidget: (_) => const Text('Pull to refresh'),
  loadingWidget: (_) => const CircularProgressIndicator(),
  successWidget: (_, response) => Text('${response?.data}'),
  errorWidget: (_, response) => Text('Error: ${response?.message}'),
  emptyWidget: (_) => const Text('No data available'),
);

Auto State Handling

Pass a controller to any API method — the state updates automatically:

// This sets controller to loading -> then success or error
final response = await ApiProvider.instance.get(
  '/users',
  controller: controller,
);

Listen to State Changes

controller.listen((status) {
  switch (status) {
    case ApiProviderStatus.loading:
      showSnackBar('Loading...');
    case ApiProviderStatus.success:
      showSnackBar('Done!');
    case ApiProviderStatus.error:
      showSnackBar('Something went wrong');
    default:
      break;
  }
});

API Reference

ApiResponse

Property Type Description
success bool Whether the request succeeded
statusCode int? HTTP status code (e.g. 200, 404)
data dynamic Response body
url String? Full request URL
message String? Success or error message

ApiProviderConfig

Parameter Type Default Description
baseUrl String required API base URL
connectTimeout Duration 30s Connection timeout
receiveTimeout Duration 30s Receive timeout
contentType String application/json Request content type
responseType ResponseType json Expected response type
headers Map? null Default headers
authorization dynamic null Auth header value
requestLogger bool true Enable colored logging
maxRedirects int 1 Max HTTP redirects
onRequest Function? null Request interceptor
onResponse Function? null Response interceptor
onError Function? null Error interceptor
listFormat ListFormat? null Query param list format
extra Map? null Extra Dio options

ApiProviderStatus

Value Description
idle No request in progress
loading Request is being sent
success Response received successfully
error Request failed
empty Successful but no data

Example

See the complete example app in example/.

import 'package:easy_api_provider/easy_api_provider.dart';
import 'package:flutter/material.dart';

void main() {
  ApiProvider.instance.init(ApiProviderConfig(
    'https://dummyjson.com',
    requestLogger: true,
  ));
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: PostsPage(),
    );
  }
}

class PostsPage extends StatefulWidget {
  const PostsPage({super.key});

  @override
  State<PostsPage> createState() => _PostsPageState();
}

class _PostsPageState extends State<PostsPage> {
  final controller = ApiProviderController();

  @override
  void initState() {
    super.initState();
    ApiProvider.instance.get('/posts', params: {'limit': '10'}, controller: controller);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Posts')),
      body: ApiProviderUi(
        controller: controller,
        loadingWidget: (_) => const Center(child: CircularProgressIndicator()),
        successWidget: (_, response) {
          final posts = (response?.data as Map?)?['posts'] as List? ?? [];
          return ListView.builder(
          itemCount: posts.length,
          itemBuilder: (_, i) {
            final post = posts[i];
            return ListTile(title: Text(post['title']));
          },
        ),
        errorWidget: (_, response) => Center(
          child: Text('Error: ${response?.message}'),
        ),
        emptyWidget: (_) => const Center(child: Text('No posts')),
      ),
    );
  }
}

Changelog

See CHANGELOG.md for release history.

Contributing

Pull requests are welcome! Please open an issue first to discuss what you would like to change.

License

This project is licensed under the MIT License — see the LICENSE file for details.

Libraries

easy_api_provider