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

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

example/lib/main.dart

import 'package:autopilot_api/core/autopilot_core.dart';
import 'package:flutter/material.dart';

// ─── Models ───────────────────────────────────────────────────────────────────

class UserModel {
  final int id;
  final String name;
  final String email;
  final String username;

  const UserModel({
    required this.id,
    required this.name,
    required this.email,
    required this.username,
  });

  factory UserModel.fromJson(dynamic json) => UserModel(
    id: json['id'] as int,
    name: json['name'].toString(),
    email: json['email'].toString(),
    username: json['username'].toString(),
  );

  @override
  String toString() =>
      'User #$id\nName     : $name\nEmail    : $email\nUsername : $username';
}

class PostModel {
  final int id;
  final String title;
  final String body;

  const PostModel({required this.id, required this.title, required this.body});

  factory PostModel.fromJson(dynamic json) => PostModel(
    id: json['id'] as int,
    title: json['title'].toString(),
    body: json['body'].toString(),
  );

  @override
  String toString() => 'Post #$id\nTitle : $title';
}

// ─────────────────────────────────────────────────────────────────────────────
// VANILLA FLUTTER STATE MANAGER (ChangeNotifier)
// Works exactly the same with GetX, Riverpod, Bloc, Provider, MobX
// ─────────────────────────────────────────────────────────────────────────────

class ApiDemoController extends ChangeNotifier {
  final _api = AutoPilotApi.instance;

  String output = 'Press a button to test 🚀';
  bool isLoading = false;

  void _setLoading(bool v) {
    isLoading = v;
    notifyListeners();
  }

  void _setOutput(String s) {
    output = s;
    notifyListeners();
  }

  // GET list
  Future<void> getUsers() async {
    _setLoading(true);
    final res = await _api.get<List<UserModel>>(
      endpoint: '/users',
      parser: (json) => (json as List).map(UserModel.fromJson).toList(),
      useCache: true,
    );
    _setLoading(false);
    _setOutput(
      res.isSuccess
          ? '✅ ${res.data?.length} users loaded\n\n'
                '${res.data?.take(3).map((u) => u.toString()).join('\n\n')}\n\n'
                '⏱ ${res.responseTime?.inMilliseconds}ms  [${res.requestId}]'
          : '❌ ${res.message}',
    );
  }

  // GET single
  Future<void> getUser() async {
    _setLoading(true);
    final res = await _api.get<UserModel>(
      endpoint: '/users/1',
      parser: UserModel.fromJson,
    );
    _setLoading(false);
    _setOutput(
      res.isSuccess
          ? '✅ User fetched!\n\n${res.data}\n\n'
                '⏱ ${res.responseTime?.inMilliseconds}ms'
          : '❌ ${res.message}',
    );
  }

  // POST
  Future<void> createPost() async {
    _setLoading(true);
    final res = await _api.post<PostModel>(
      endpoint: '/posts',
      body: {
        'title': 'AutoPilot Zero — Zero Dependencies!',
        'body': 'Pure Dart. No http package. No shared_preferences.',
        'userId': 1,
      },
      parser: PostModel.fromJson,
    );
    _setLoading(false);
    _setOutput(
      res.isSuccess
          ? '✅ Post created!\n\n${res.data}\n\nStatus: ${res.statusCode}\n'
                '⏱ ${res.responseTime?.inMilliseconds}ms'
          : '❌ ${res.message}',
    );
  }

  // PUT
  Future<void> updatePost() async {
    _setLoading(true);
    final res = await _api.put<PostModel>(
      endpoint: '/posts/1',
      body: {
        'title': 'Updated via AutoPilot Zero',
        'body': 'Pure Dart HttpClient',
        'userId': 1,
      },
      parser: PostModel.fromJson,
    );
    _setLoading(false);
    _setOutput(
      res.isSuccess ? '✅ Post updated!\n\n${res.data}' : '❌ ${res.message}',
    );
  }

  // PATCH
  Future<void> patchPost() async {
    _setLoading(true);
    final res = await _api.patch(
      endpoint: '/posts/1',
      body: {'title': 'Patched title only'},
    );
    _setLoading(false);
    _setOutput(
      res.isSuccess ? '✅ Patched!\nRaw: ${res.raw}' : '❌ ${res.message}',
    );
  }

  // DELETE
  Future<void> deletePost() async {
    _setLoading(true);
    final res = await _api.delete(endpoint: '/posts/1');
    _setLoading(false);
    _setOutput(
      res.isSuccess
          ? '✅ Deleted!\nStatus: ${res.statusCode}'
          : '❌ ${res.message}',
    );
  }

  // Query params
  Future<void> queryParams() async {
    _setLoading(true);
    final res = await _api.get<List<PostModel>>(
      endpoint: '/posts',
      queryParams: {'userId': 1, '_limit': 3},
      parser: (json) => (json as List).map(PostModel.fromJson).toList(),
    );
    _setLoading(false);
    _setOutput(
      res.isSuccess
          ? '✅ Posts (userId=1, limit=3)\n\n'
                '${res.data?.map((p) => '• ${p.title}').join('\n')}'
          : '❌ ${res.message}',
    );
  }

  // .handle() extension
  Future<void> handleExtension() async {
    _setLoading(true);
    await _api
        .get<UserModel>(endpoint: '/users/3', parser: UserModel.fromJson)
        .handle(
          onSuccess: (data, msg) =>
              _setOutput('✅ .handle() extension!\n\n$data'),
          onFailure: (msg, code) => _setOutput('❌ Failed [$code]: $msg'),
        );
    _setLoading(false);
  }

  // .onSuccess() + .onFailure() chaining
  Future<void> chainExtensions() async {
    _setLoading(true);
    final res = await _api
        .get<UserModel>(endpoint: '/users/4', parser: UserModel.fromJson)
        .onSuccess((data) => debugPrint('Side effect: got ${data?.name}'))
        .onFailure((msg, code) => debugPrint('Side effect error: $msg'));
    _setLoading(false);
    _setOutput(
      res.isSuccess ? '✅ Chain extensions!\n\n${res.data}' : '❌ ${res.message}',
    );
  }

  // Cache + Deduplication demo
  Future<void> cacheDedup() async {
    _setLoading(true);
    _setOutput(
      '🔄 Firing 3 identical GETs simultaneously...\n'
      'Should deduplicate → 1 real network call',
    );

    final sw = Stopwatch()..start();
    final results = await Future.wait([
      _api.get<UserModel>(
        endpoint: '/users/2',
        parser: UserModel.fromJson,
        useCache: true,
      ),
      _api.get<UserModel>(
        endpoint: '/users/2',
        parser: UserModel.fromJson,
        useCache: true,
      ),
      _api.get<UserModel>(
        endpoint: '/users/2',
        parser: UserModel.fromJson,
        useCache: true,
      ),
    ]);
    sw.stop();

    _setLoading(false);
    final names = results.map((r) => r.data?.name ?? '?').toSet();
    _setOutput(
      '✅ 3 calls → 1 network request!\n\n'
      'All same user: $names\n\n'
      'Times: ${results.map((r) => '${r.responseTime?.inMilliseconds}ms').join(', ')}\n'
      'Wall time: ${sw.elapsedMilliseconds}ms',
    );
  }

  // mapData extension
  Future<void> mapDataExtension() async {
    _setLoading(true);
    // Get user then map name to uppercase
    final res = await _api
        .get<UserModel>(endpoint: '/users/5', parser: UserModel.fromJson)
        .mapData((user) => user.name.toUpperCase());

    _setLoading(false);
    _setOutput(
      res.isSuccess
          ? '✅ .mapData() — name uppercased:\n\n${res.data}'
          : '❌ ${res.message}',
    );
  }
}

// ─────────────────────────────────────────────────────────────────────────────
// MAIN
// ─────────────────────────────────────────────────────────────────────────────

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await AutoPilotApi.init(
    baseUrl: 'https://jsonplaceholder.typicode.com',
    enableLogs: true,
    printPayload: true,
    prettyPrint: true,
    enableCache: true,
    cacheDuration: const Duration(minutes: 3),
    maxRetries: 2,
    tokenType: 'Bearer',

    enableGlobalLoader: true,
    onLoadingChanged: (v) => debugPrint('⏳ Loader: $v'),
    onError: (msg, code) => debugPrint('🔴 Error [$code]: $msg'),
    onRequestSent: (url, m) => debugPrint('📤 $m → $url'),
    onResponseReceived: (url, c, t) =>
        debugPrint('📥 $c ← $url (${t.inMilliseconds}ms)'),

    // match your backend's envelope
    successKey: 'status',
    successValue: true,
    messageKey: 'message',
    dataKey: 'data',
  );

  runApp(const MyApp());
}

// ─────────────────────────────────────────────────────────────────────────────
// APP
// ─────────────────────────────────────────────────────────────────────────────

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

  @override
  Widget build(BuildContext context) => MaterialApp(
    title: 'AutoPilot Zero',
    debugShowCheckedModeBanner: false,
    theme: ThemeData(
      colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
      useMaterial3: true,
    ),
    home: ChangeNotifierProvider(
      create: (_) => ApiDemoController(),
      child: const DemoPage(),
    ),
  );
}

// simple ChangeNotifierProvider inline
class ChangeNotifierProvider<T extends ChangeNotifier> extends StatefulWidget {
  final T Function(BuildContext) create;
  final Widget child;
  const ChangeNotifierProvider({
    super.key,
    required this.create,
    required this.child,
  });

  static T of<T extends ChangeNotifier>(BuildContext context) {
    final scope = context.dependOnInheritedWidgetOfExactType<_Scope<T>>();
    return scope!.notifier;
  }

  @override
  State<ChangeNotifierProvider<T>> createState() =>
      _ChangeNotifierProviderState<T>();
}

class _ChangeNotifierProviderState<T extends ChangeNotifier>
    extends State<ChangeNotifierProvider<T>> {
  late T _notifier;

  @override
  void initState() {
    super.initState();
    _notifier = widget.create(context);
    _notifier.addListener(_rebuild);
  }

  void _rebuild() => setState(() {});

  @override
  void dispose() {
    _notifier.removeListener(_rebuild);
    _notifier.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) =>
      _Scope<T>(notifier: _notifier, child: widget.child);
}

class _Scope<T extends ChangeNotifier> extends InheritedWidget {
  final T notifier;
  const _Scope({required this.notifier, required super.child});

  @override
  bool updateShouldNotify(_Scope<T> old) => true;
}

// ─────────────────────────────────────────────────────────────────────────────
// DEMO PAGE
// ─────────────────────────────────────────────────────────────────────────────

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

  @override
  Widget build(BuildContext context) {
    final ctrl = ChangeNotifierProvider.of<ApiDemoController>(context);

    return Scaffold(
      backgroundColor: const Color(0xFF090D1A),
      appBar: AppBar(
        backgroundColor: const Color(0xFF141928),
        title: Row(
          children: [
            Container(
              padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
              decoration: BoxDecoration(
                color: Colors.indigo.withValues(alpha: 0.3),
                borderRadius: BorderRadius.circular(6),
                border: Border.all(color: Colors.indigo.withValues(alpha: 0.5)),
              ),
              child: const Text(
                'ZERO DEPS',
                style: TextStyle(
                  color: Colors.indigoAccent,
                  fontSize: 10,
                  fontWeight: FontWeight.bold,
                  fontFamily: 'monospace',
                ),
              ),
            ),
            const SizedBox(width: 8),
            const Text(
              'AutoPilot Zero',
              style: TextStyle(
                color: Colors.white,
                fontWeight: FontWeight.bold,
                fontSize: 17,
              ),
            ),
          ],
        ),
        bottom: PreferredSize(
          preferredSize: const Size.fromHeight(18),
          child: Padding(
            padding: const EdgeInsets.only(bottom: 5),
            child: Text(
              'Pure Dart + Flutter SDK only  •  dart:io HttpClient',
              style: TextStyle(color: Colors.white38, fontSize: 10),
            ),
          ),
        ),
      ),
      body: Padding(
        padding: const EdgeInsets.all(12),
        child: Column(
          children: [
            // ── buttons ───────────────────────────────────────────────────
            Wrap(
              spacing: 7,
              runSpacing: 7,
              children: [
                _btn(
                  'GET List',
                  Icons.people,
                  ctrl.getUsers,
                  Colors.green,
                  ctrl.isLoading,
                ),
                _btn(
                  'GET Single',
                  Icons.person,
                  ctrl.getUser,
                  Colors.blue,
                  ctrl.isLoading,
                ),
                _btn(
                  'POST',
                  Icons.add_circle,
                  ctrl.createPost,
                  Colors.purple,
                  ctrl.isLoading,
                ),
                _btn(
                  'PUT',
                  Icons.edit,
                  ctrl.updatePost,
                  Colors.orange,
                  ctrl.isLoading,
                ),
                _btn(
                  'PATCH',
                  Icons.edit_note,
                  ctrl.patchPost,
                  Colors.teal,
                  ctrl.isLoading,
                ),
                _btn(
                  'DELETE',
                  Icons.delete,
                  ctrl.deletePost,
                  Colors.red,
                  ctrl.isLoading,
                ),
                _btn(
                  '?query',
                  Icons.filter_list,
                  ctrl.queryParams,
                  Colors.cyan,
                  ctrl.isLoading,
                ),
                _btn(
                  '.handle()',
                  Icons.extension,
                  ctrl.handleExtension,
                  Colors.pink,
                  ctrl.isLoading,
                ),
                _btn(
                  '.chain()',
                  Icons.link,
                  ctrl.chainExtensions,
                  Colors.lime,
                  ctrl.isLoading,
                ),
                _btn(
                  '.mapData()',
                  Icons.transform,
                  ctrl.mapDataExtension,
                  Colors.amber,
                  ctrl.isLoading,
                ),
                _btn(
                  'Cache/Dedup',
                  Icons.cached,
                  ctrl.cacheDedup,
                  Colors.indigo,
                  ctrl.isLoading,
                ),
              ],
            ),

            const SizedBox(height: 10),

            // loader
            AnimatedContainer(
              duration: const Duration(milliseconds: 150),
              height: ctrl.isLoading ? 2 : 0,
              child: LinearProgressIndicator(
                backgroundColor: Colors.transparent,
                valueColor: const AlwaysStoppedAnimation<Color>(
                  Colors.indigoAccent,
                ),
              ),
            ),

            const SizedBox(height: 10),

            // ── terminal output ────────────────────────────────────────────
            Expanded(
              child: Container(
                width: double.infinity,
                padding: const EdgeInsets.all(14),
                decoration: BoxDecoration(
                  color: const Color(0xFF0D1117),
                  borderRadius: BorderRadius.circular(10),
                  border: Border.all(color: Colors.white10),
                ),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    // mac-style dots
                    Row(
                      children: [
                        _dot(Colors.red),
                        const SizedBox(width: 5),
                        _dot(Colors.orange),
                        const SizedBox(width: 5),
                        _dot(Colors.green),
                        const SizedBox(width: 10),
                        Text(
                          'autopilot_zero  •  pure dart:io',
                          style: TextStyle(
                            color: Colors.white24,
                            fontSize: 10,
                            fontFamily: 'monospace',
                          ),
                        ),
                      ],
                    ),
                    const SizedBox(height: 10),
                    Expanded(
                      child: SingleChildScrollView(
                        child: Text(
                          ctrl.output,
                          style: const TextStyle(
                            color: Colors.greenAccent,
                            fontFamily: 'monospace',
                            fontSize: 12,
                            height: 1.6,
                          ),
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ),

            const SizedBox(height: 6),
            Text(
              '👆 Check debug console for 🎨 colored request/response logs',
              style: TextStyle(color: Colors.white24, fontSize: 10),
              textAlign: TextAlign.center,
            ),
          ],
        ),
      ),
    );
  }

  Widget _btn(
    String label,
    IconData icon,
    VoidCallback? fn,
    Color c,
    bool loading,
  ) => ElevatedButton.icon(
    onPressed: loading ? null : fn,
    icon: Icon(icon, size: 12),
    label: Text(label, style: const TextStyle(fontSize: 10)),
    style: ElevatedButton.styleFrom(
      backgroundColor: c.withValues(alpha: 0.12),
      foregroundColor: c,
      side: BorderSide(color: c.withValues(alpha: 0.35)),
      padding: const EdgeInsets.symmetric(horizontal: 9, vertical: 6),
      minimumSize: const Size(0, 0),
    ),
  );

  Widget _dot(Color c) => Container(
    width: 10,
    height: 10,
    decoration: BoxDecoration(color: c, shape: BoxShape.circle),
  );
}
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