autopilot_api 2.0.1 copy "autopilot_api: ^2.0.1" to clipboard
autopilot_api: ^2.0.1 copied to clipboard

Zero dependency pure Dart API engine for Flutter with smart caching, retry, token refresh, multipart upload, download support and automatic parsing.


 █████╗ ██╗   ██╗████████╗ ██████╗ ██████╗ ██╗██╗      ██████╗ ████████╗
██╔══██╗██║   ██║╚══██╔══╝██╔═══██╗██╔══██╗██║██║     ██╔═══██╗╚══██╔══╝
███████║██║   ██║   ██║   ██║   ██║██████╔╝██║██║     ██║   ██║   ██║
██╔══██║██║   ██║   ██║   ██║   ██║██╔═══╝ ██║██║     ██║   ██║   ██║
██║  ██║╚██████╔╝   ██║   ╚██████╔╝██║     ██║███████╗╚██████╔╝   ██║
╚═╝  ╚═╝ ╚═════╝    ╚═╝    ╚═════╝ ╚═╝     ╚═╝╚══════╝ ╚═════╝    ╚═╝

⚡ Zero-Dependency Smart API Engine for Flutter #

Pub Version Flutter Dart License Dependencies

Pure Dart  •  dart:io HttpClient  •  Zero Dependencies  •  Production Ready

Features · Install · Quick Start · API Ref · State Managers


🎯 The Problem #

Every Flutter developer has written this at least once:

// ❌ The painful reality — 50+ lines for a single API call
Future<UserModel?> getUser(int id) async {
  try {
    final prefs = await SharedPreferences.getInstance();
    final token = prefs.getString('token') ?? '';
    final request = await HttpClient().getUrl(Uri.parse('$baseUrl/users/$id'));
    request.headers.set('Authorization', 'Bearer $token');
    request.headers.set('Content-Type', 'application/json');
    final response = await request.close().timeout(const Duration(seconds: 30));
    final body     = await response.transform(utf8.decoder).join();
    final json     = jsonDecode(body);
    if (response.statusCode == 401) { /* refresh token... */ }
    if (response.statusCode == 422) { /* validation errors... */ }
    if (json['status'] == true) return UserModel.fromJson(json['data']);
    throw Exception(json['message']);
  } on SocketException  { throw Exception('No internet'); }
  on TimeoutException   { throw Exception('Timed out'); }
  catch (e)             { rethrow; }
}

Multiply this by every endpoint in your app. Thousands of lines of identical, bug-prone boilerplate.


✅ The AutoPilot Solution #

// ✅ The only code you need to write
final res = await api.get(
  endpoint : '/users/1',
  parser   : UserModel.fromJson,
);

if (res.isSuccess) print(res.data);
else print(res.message);

AutoPilot handles everything automatically:

Feature What it does
🔑 Token injection Reads token from storage, adds to every request
🔄 Token refresh On 401 → refresh → retry silently
🌐 Internet check Detects offline before hitting network
⏱ Timeouts Configurable, typed exception on expiry
🔁 Retry Exponential backoff, configurable attempts
💾 Caching Memory + disk, auto-expiry, per-request
⚡ Deduplication Same concurrent GETs → 1 network call
📦 Parsing JSON → your model via parser function
🎨 Debug logs Colored logs with full payload, auto-off in release

✨ Features #

┌─────────────────────────────────────────────────────────────────────┐
│                      AUTOPILOT ZERO v2.0.0                          │
├─────────────────────────┬───────────────────────────────────────────┤
│  HTTP Methods           │  GET · POST · PUT · PATCH · DELETE        │
│  File Operations        │  Multipart Upload · File Download         │
│  Auth                   │  Auto Token Inject · Auto Token Refresh   │
│  Caching                │  Memory Layer · Disk Layer · Auto Expiry  │
│  Reliability            │  Retry · Exponential Backoff · Dedup      │
│  Networking             │  Pure dart:io · Zero dependencies         │
│  Storage                │  Pure Dart local storage (no plugins)     │
│  Connectivity           │  Native socket lookup                     │
│  Logging                │  Colored ANSI · Full payload · Auto-off   │
│  State Managers         │  GetX · Bloc · Riverpod · Provider · MobX│
│  Extensions             │  .handle() · .mapData() · .onSuccess()    │
│  Dependencies           │  ZERO ← this is the headline             │
└─────────────────────────┴───────────────────────────────────────────┘

🔥 v2.0.0 — Zero Dependencies #

Removed Package Replaced With Benefit
http dart:io HttpClient No external dep, full control
shared_preferences Pure Dart JSON file storage No platform channels
connectivity_plus InternetAddress.lookup() Works everywhere, zero config
path Internal string utils Smaller binary
mime Internal MIME resolver No conflicts
# Before — 5 external dependencies
dependencies:
  http: ^1.2.2
  shared_preferences: ^2.3.3
  connectivity_plus: ^6.0.0
  path: ^1.9.0
  mime: ^1.0.6

# After — ZERO
dependencies:
  flutter:
    sdk: flutter

Smaller APK. Faster builds. Zero version conflicts. Zero native setup.


📦 Installation #

dependencies:
  autopilot_api: ^2.0.0
flutter pub get

No pod install. No gradle changes. No native setup. Just add and go.


🚀 60-Second Quickstart #

// Step 1 — Initialize once in main()
void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await AutoPilotApi.init(
    baseUrl      : 'https://api.example.com/v1',
    enableLogs   : true,    // 🎨 colored logs in debug
    printPayload : true,    // 📋 full JSON in console
    enableCache  : true,    // 💾 auto cache GET responses
    maxRetries   : 3,       // 🔁 auto retry on failure
  );

  runApp(const MyApp());
}

// Step 2 — Use anywhere, nothing else needed
final api = AutoPilotApi.instance;

final res = await api.get(
  endpoint : '/users/1',
  parser   : UserModel.fromJson,
);

if (res.isSuccess) {
  print(res.data);        // ✅ UserModel
  print(res.message);     // "Success"
  print(res.statusCode);  // 200
  print(res.responseTime?.inMilliseconds); // 245 (ms)
} else {
  print(res.message);     // ❌ error description
  print(res.statusCode);  // 404
}

// Step 3 — Set token after login (auto-injected from now on)
await AutoPilotApi.setToken(loginRes.data!.token);

🌟 All Requests #

// ── GET single ─────────────────────────────────────────────────────────────
final res = await api.get<UserModel>(
  endpoint : '/users/1',
  parser   : UserModel.fromJson,
);

// ── GET list ───────────────────────────────────────────────────────────────
final res = await api.get<List<UserModel>>(
  endpoint : '/users',
  parser   : (json) => (json as List).map(UserModel.fromJson).toList(),
);

// ── GET with query params → /posts?userId=1&page=2 ────────────────────────
final res = await api.get<List<PostModel>>(
  endpoint    : '/posts',
  queryParams : {'userId': 1, 'page': 2, 'limit': 10},
  parser      : (json) => (json as List).map(PostModel.fromJson).toList(),
);

// ── GET with cache ─────────────────────────────────────────────────────────
final res = await api.get<ConfigModel>(
  endpoint      : '/app/config',
  parser        : ConfigModel.fromJson,
  useCache      : true,
  cacheDuration : const Duration(hours: 24),
);

// ── POST ───────────────────────────────────────────────────────────────────
final res = await api.post<LoginModel>(
  endpoint : '/auth/login',
  body     : {'email': email, 'password': password},
  parser   : LoginModel.fromJson,
);

// ── PUT / PATCH / DELETE ───────────────────────────────────────────────────
await api.put(endpoint: '/posts/1',   body: {'title': 'New'});
await api.patch(endpoint: '/users/1', body: {'name': 'Updated'});
await api.delete(endpoint: '/posts/1');

// ── MULTIPART — single file ────────────────────────────────────────────────
await api.multipart(
  endpoint : '/profile/photo',
  fileKey  : 'image',
  filePath : pickedFile.path,
);

// ── MULTIPART — multiple files + fields + progress ────────────────────────
await api.multipart<UploadModel>(
  endpoint : '/documents',
  files    : [
    MultipartFileModel(key: 'doc', path: file.path,
        fileName: 'report.pdf', mimeType: 'application/pdf'),
  ],
  fields     : {'title': 'Q4 Report', 'year': '2025'},
  onProgress : (sent, total) =>
      setState(() => progress = sent / total),
  parser     : UploadModel.fromJson,
);

// ── MULTIPART — from bytes (no file path needed) ──────────────────────────
await api.multipart(
  endpoint : '/upload',
  files    : [
    MultipartFileModel.fromBytes(
      key: 'avatar', bytes: imageBytes, fileName: 'avatar.png'),
  ],
);

// ── DOWNLOAD with progress ─────────────────────────────────────────────────
final res = await api.download(
  endpoint   : '/reports/jan.pdf',
  savePath   : '/storage/Download/jan.pdf',
  onProgress : (recv, total) => print('${(recv/total*100).toInt()}%'),
);
if (res.isSuccess) print('Saved: ${res.data}');

📦 ApiResponse<T> #

res.isSuccess       // bool     — did it succeed?
res.data            // T?       — parsed model
res.message         // String   — server or error message
res.statusCode      // int      — 200, 201, 400, 401, 422, 500...
res.raw             // dynamic  — raw JSON body
res.errors          // Map?     — validation errors map
res.requestId       // String?  — tracing ID e.g. "a3f2b1c9"
res.responseTime    // Duration? — round-trip time
res.timestamp       // DateTime — when received

// Status helpers
res.isClientError      // 400–499
res.isServerError      // 500+
res.isUnauthorized     // 401
res.isForbidden        // 403
res.isNotFound         // 404
res.isValidationError  // 422
res.isTimeout          // 408
res.isNoInternet       // 0

// Data helpers
res.dataOr(fallback)            // safe access with fallback
res.dataOrThrow                 // throws if null
res.validationError('email')    // first error for field
res.allErrors                   // all validation errors as string
res.cast<R>(newData)            // cast to different type

🎣 Extension Methods #

// .handle() — no if/else needed
await api.get(endpoint: '/me', parser: UserModel.fromJson)
  .handle(
    onSuccess : (data, msg) => setState(() => user = data),
    onFailure : (msg, code) => showSnackbar(msg),
  );

// .mapData() — transform data type in chain
final res = await api
    .get<String>(endpoint: '/stats/total')
    .mapData((s) => int.parse(s));   // ApiResponse<int>

// .onSuccess() / .onFailure() — side effects, returns original
await api.post(endpoint: '/order', body: data)
    .onSuccess((_)       => Analytics.log('order_placed'))
    .onFailure((msg, __) => Crashlytics.record(msg))
    .handle(
      onSuccess : (data, _) => navigateTo(SuccessPage(data)),
      onFailure : (msg,  _) => showErrorDialog(msg),
    );

🔐 Token Management #

await AutoPilotApi.setToken(token);                          // set access token
await AutoPilotApi.setTokens(accessToken: a, refreshToken: r); // set both
await AutoPilotApi.clearTokens();                            // logout

// Auto refresh on 401
await AutoPilotApi.init(
  enableTokenRefresh : true,
  onRefreshToken     : () async {
    final r = await TokenManager.getRefreshToken();
    final res = await refreshEndpoint(r);
    return res.newToken;  // returned → saved → request retried
  },
);

💾 Caching #

// Global
await AutoPilotApi.init(enableCache: true, cacheDuration: Duration(minutes: 5));

// Per-request override
await api.get(endpoint: '/config', useCache: true, cacheDuration: Duration(hours: 1));

// Invalidate
await api.invalidateCache('/config');
await api.clearCache();

⚙️ Full Configuration #

await AutoPilotApi.init(
  baseUrl            : 'https://api.example.com/v1',  // required
  tokenType          : 'Bearer',
  timeoutSeconds     : 30,
  maxRetries         : 3,
  retryDelay         : Duration(seconds: 1),
  enableCache        : true,
  cacheDuration      : Duration(minutes: 5),
  enableLogs         : true,      // auto false in release
  printPayload       : true,      // print full JSON
  prettyPrint        : true,
  enableTokenRefresh : true,
  onRefreshToken     : () async => newToken,
  enableGlobalLoader : true,
  onLoadingChanged   : (v) => AppController.setLoading(v),
  onError            : (msg, code) => showSnackbar(msg),
  onRequestSent      : (url, method) => Analytics.log(url, method),
  onResponseReceived : (url, code, time) => Analytics.log(url, code),
  globalHeaders      : {
    'X-App-Version' : '2.0.0',
    'X-Platform'    : Platform.isAndroid ? 'android' : 'ios',
  },
  // match your backend's JSON envelope:
  successKey   : 'status',    // default
  successValue : true,        // default
  messageKey   : 'message',   // default
  dataKey      : 'data',      // default
  enableDeduplication : true,
);

🔌 State Manager Integration #

GetX #

class UserController extends GetxController {
  final user      = Rx<UserModel?>(null);
  final isLoading = false.obs;

  Future<void> fetchUser() async {
    isLoading.value = true;
    await AutoPilotApi.instance
        .get<UserModel>(endpoint: '/users/me', parser: UserModel.fromJson)
        .handle(
          onSuccess : (data, _) => user.value = data,
          onFailure : (msg,  _) => Get.snackbar('Error', msg),
        );
    isLoading.value = false;
  }
}

Riverpod #

class UserNotifier extends AsyncNotifier<UserModel?> {
  @override Future<UserModel?> build() async => null;

  Future<void> fetch() async {
    state = const AsyncLoading();
    final res = await AutoPilotApi.instance
        .get(endpoint: '/users/me', parser: UserModel.fromJson);
    state = res.isSuccess
        ? AsyncData(res.data)
        : AsyncError(res.message, StackTrace.current);
  }
}
final userProvider = AsyncNotifierProvider<UserNotifier, UserModel?>(UserNotifier.new);

Bloc / Cubit #

class UserCubit extends Cubit<UserState> {
  UserCubit() : super(const UserInitial());

  Future<void> fetchUser() async {
    emit(const UserLoading());
    final res = await AutoPilotApi.instance
        .get(endpoint: '/users/me', parser: UserModel.fromJson);
    emit(res.isSuccess ? UserLoaded(res.data!) : UserError(res.message));
  }
}

Provider #

class UserProvider extends ChangeNotifier {
  UserModel? user;
  bool isLoading = false;

  Future<void> fetchUser() async {
    isLoading = true; notifyListeners();
    final res = await AutoPilotApi.instance
        .get(endpoint: '/users/me', parser: UserModel.fromJson);
    user = res.data;
    isLoading = false; notifyListeners();
  }
}

MobX #

abstract class _UserStore with Store {
  @observable UserModel? user;
  @observable bool isLoading = false;

  @action
  Future<void> fetchUser() async {
    isLoading = true;
    final res = await AutoPilotApi.instance
        .get(endpoint: '/users/me', parser: UserModel.fromJson);
    if (res.isSuccess) user = res.data;
    isLoading = false;
  }
}

🌍 Runtime Environment Switch #

api.reconfigure((c) => c.copyWith(baseUrl: 'https://staging.api.com'));
api.reconfigure((c) => c.copyWith(baseUrl: 'https://api.example.com'));
api.reconfigure((c) => c.copyWith(timeoutSeconds: 60)); // slow network

🎨 Debug Logs #

┌────────────────────────────────────────────────────
│ 🚀 REQUEST [a3f2b1c9]  14:32:01.432
│  POST  https://api.example.com/v1/auth/login
│  ⊳ Headers
│    Authorization: ***masked***
│  ⊳ Body
│    {
│      "email": "john@example.com",
│      "password": "••••••••"
│    }
└────────────────────────────────────────────────────

┌────────────────────────────────────────────────────
│ ✅ RESPONSE [a3f2b1c9]  14:32:01.687
│  Status: 200  ⏱ 255ms
│  ⊳ Payload
│    {
│      "status": true,
│      "message": "Login successful",
│      "data": { "token": "eyJ...", "user": { "id": 1 } }
│    }
└────────────────────────────────────────────────────

┌────────────────────────────────────────────────────
│ 💥 ERROR [b7d3e2f1]  14:35:22.901
│  Status: 422
│  Validation failed
│  ⊳ Error Body
│    { "errors": { "email": ["already taken"] } }
└────────────────────────────────────────────────────

Auto-disabled in release builds. Zero configuration needed.


📐 Architecture #

autopilot_zero/
├── lib/
│   ├── autopilot_zero.dart              ← single import
│   ├── core/autopilot_core.dart         ← 🧠 main engine (dart:io)
│   ├── models/                          ← ApiResponse, Config, FileModel
│   ├── storage/ap_storage.dart          ← 💾 pure Dart file storage
│   ├── auth/token_manager.dart          ← memory + disk token
│   ├── cache/cache_service.dart         ← two-layer cache
│   ├── connectivity/                    ← socket lookup
│   ├── exceptions/                      ← typed exception hierarchy
│   ├── extensions/                      ← .handle() .mapData() .onSuccess()
│   ├── logger/                          ← colored ANSI logger
│   ├── parsers/                         ← smart JSON parser
│   ├── queue/                           ← deduplication
│   └── retry/                           ← exponential backoff
├── example/lib/main.dart                ← full demo app
├── test/autopilot_zero_test.dart        ← 30+ unit tests
└── pubspec.yaml                         ← ZERO external deps

📋 API Reference #

Method Description
AutoPilotApi.init(...) Initialize — call once in main()
AutoPilotApi.instance Get singleton
AutoPilotApi.setToken(t) Save access token
AutoPilotApi.setTokens(...) Save access + refresh token
AutoPilotApi.clearTokens() Logout
api.get(...) GET request
api.post(...) POST request
api.put(...) PUT request
api.patch(...) PATCH request
api.delete(...) DELETE request
api.multipart(...) File upload (single/multiple)
api.download(...) File download with progress
api.invalidateCache(e) Clear cache for endpoint
api.clearCache() Clear all cache
api.reconfigure(fn) Update config at runtime
api.dispose() Close HTTP client

⚡ Performance #

┌──────────────────┬─────────────┬────────────────┐
│ Metric           │ Traditional │ AutoPilot Zero │
├──────────────────┼─────────────┼────────────────┤
│ Boilerplate LOC  │ 50+/endpoint│ 4/endpoint     │
│ Dependencies     │ 5–10        │ 0              │
│ Token read time  │ ~10ms (disk)│ ~0ms (memory)  │
│ Duplicate GETs   │ All hit net │ 1 hits net     │
│ APK size impact  │ +200–500KB  │ +0KB           │
└──────────────────┴─────────────┴────────────────┘

🧪 Testing #

flutter test
flutter test --coverage

🗺 Roadmap #

  • ✅ v1.0.0 — Core engine (http + shared_preferences)
  • ✅ v2.0.0 — Zero dependencies (pure dart:io)
  • ❌ v2.1.0 — WebSocket support
  • ❌ v2.2.0 — GraphQL support
  • ❌ v2.3.0 — Offline request queue
  • ❌ v3.0.0 — Code generator (@GET('/users') annotations)

📄 License #

MIT License © 2025 AutoPilot API


Built with ❤️ for the Flutter community

⭐ Star on GitHub · 📦 pub.dev · 🐛 Issues

If AutoPilot saved you hours of boilerplate, consider starring the repo ⭐

3
likes
140
points
200
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Zero dependency pure Dart API engine for Flutter with smart caching, retry, token refresh, multipart upload, download support and automatic parsing.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter

More

Packages that depend on autopilot_api