autopilot_api 1.0.1
autopilot_api: ^1.0.1 copied to clipboard
Zero-boilerplate smart API engine for Flutter. Only uses http + shared_preferences.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:autopilot_api/autopilot_api.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:$id name:$name\n email:$email)';
}
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:$id\n title:$title)';
}
// ─── Entry Point ──────────────────────────────────────────────────────────────
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// ✅ ONE-TIME SETUP
await AutoPilotApi.init(
baseUrl : 'https://jsonplaceholder.typicode.com',
enableLogs : true,
printPayload : true, // full JSON in debug console
prettyPrint : true, // pretty formatted
enableCache : true,
cacheDuration: const Duration(minutes: 3),
maxRetries : 2,
tokenType : 'Bearer',
enableGlobalLoader : true,
onLoadingChanged : (loading) =>
debugPrint('⏳ Global loader: $loading'),
onError : (msg, code) =>
debugPrint('🔴 Global error [$code]: $msg'),
onRequestSent : (url, method) =>
debugPrint('📤 $method → $url'),
onResponseReceived : (url, code, time) =>
debugPrint('📥 $code ← $url (${time.inMilliseconds}ms)'),
// Match your API's response 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 Demo',
theme : ThemeData(
colorScheme : ColorScheme.fromSeed(seedColor: Colors.indigo),
useMaterial3 : true,
),
home: const DemoPage(),
);
}
// ─── Demo Page ────────────────────────────────────────────────────────────────
class DemoPage extends StatefulWidget {
const DemoPage({super.key});
@override
State<DemoPage> createState() => _DemoPageState();
}
class _DemoPageState extends State<DemoPage> {
final api = AutoPilotApi.instance;
String _out = 'Press a button to test 🚀';
bool _loading= false;
// ── GET list ──────────────────────────────────────────────────────────────
Future<void> _getUsers() async {
setState(() => _loading = true);
final res = await api.get<List<UserModel>>(
endpoint : '/users',
parser : (json) => (json as List).map(UserModel.fromJson).toList(),
useCache : true,
);
setState(() {
_loading = false;
_out = res.isSuccess
? '✅ ${res.data?.length} users\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 {
setState(() => _loading = true);
final res = await api.get<UserModel>(
endpoint : '/users/1',
parser : UserModel.fromJson,
);
setState(() {
_loading = false;
_out = res.isSuccess
? '✅ User fetched!\n\n${res.data}\n\n'
'⏱ ${res.responseTime?.inMilliseconds}ms'
: '❌ ${res.message}';
});
}
// ── POST ──────────────────────────────────────────────────────────────────
Future<void> _createPost() async {
setState(() => _loading = true);
final res = await api.post<PostModel>(
endpoint : '/posts',
body : {
'title' : 'AutoPilot is awesome!',
'body' : 'Zero boilerplate API calls in Flutter.',
'userId' : 1,
},
parser: PostModel.fromJson,
);
setState(() {
_loading = false;
_out = res.isSuccess
? '✅ Post created!\n\n${res.data}\n\n'
'Status: ${res.statusCode}\n'
'⏱ ${res.responseTime?.inMilliseconds}ms'
: '❌ ${res.message}';
});
}
// ── PUT ───────────────────────────────────────────────────────────────────
Future<void> _updatePost() async {
setState(() => _loading = true);
final res = await api.put<PostModel>(
endpoint : '/posts/1',
body : {
'title' : 'Updated via AutoPilot',
'body' : 'Updated body.',
'userId' : 1,
},
parser: PostModel.fromJson,
);
setState(() {
_loading = false;
_out = res.isSuccess
? '✅ Post updated!\n\n${res.data}'
: '❌ ${res.message}';
});
}
// ── DELETE ────────────────────────────────────────────────────────────────
Future<void> _deletePost() async {
setState(() => _loading = true);
final res = await api.delete(endpoint: '/posts/1');
setState(() {
_loading = false;
_out = res.isSuccess
? '✅ Post deleted!\nStatus: ${res.statusCode}'
: '❌ ${res.message}';
});
}
// ── Query params ──────────────────────────────────────────────────────────
Future<void> _queryParams() async {
setState(() => _loading = true);
final res = await api.get<List<PostModel>>(
endpoint : '/posts',
queryParams : {'userId': 1, '_limit': 3},
parser : (json) =>
(json as List).map(PostModel.fromJson).toList(),
);
setState(() {
_loading = false;
_out = res.isSuccess
? '✅ Posts for userId=1 (limit 3)\n\n'
'${res.data?.map((p) => '• ${p.title}').join('\n')}'
: '❌ ${res.message}';
});
}
// ── .handle() extension ───────────────────────────────────────────────────
Future<void> _handleExt() async {
setState(() => _loading = true);
await api
.get<UserModel>(endpoint: '/users/3', parser: UserModel.fromJson)
.handle(
onSuccess : (data, msg) =>
setState(() => _out = '✅ .handle() extension!\n\n$data'),
onFailure : (msg, code) =>
setState(() => _out = '❌ Failed [$code]: $msg'),
);
setState(() => _loading = false);
}
// ── Cache + Deduplication demo ────────────────────────────────────────────
Future<void> _cacheDedup() async {
setState(() {
_loading = true;
_out = '🔄 Firing 3 identical requests simultaneously...\n'
'(Should deduplicate to 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();
setState(() {
_loading = false;
final names = results.map((r) => r.data?.name ?? '?').toSet();
_out = '✅ 3 calls → 1 network request!\n\n'
'All got same user: $names\n\n'
'Individual times: ${results.map((r) => '${r.responseTime?.inMilliseconds}ms').join(', ')}\n'
'Total wall time: ${sw.elapsedMilliseconds}ms';
});
}
// ─── UI ───────────────────────────────────────────────────────────────────
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor : const Color(0xFF0A0E1A),
appBar: AppBar(
backgroundColor : const Color(0xFF1A1F2E),
title: const Text(
'🚀 AutoPilot API Demo',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
),
bottom: PreferredSize(
preferredSize : const Size.fromHeight(20),
child : const Padding(
padding : EdgeInsets.only(bottom: 6),
child : Text(
'Only http + shared_preferences • No Dio',
style: TextStyle(color: Colors.white38, fontSize: 11),
),
),
),
),
body: Padding(
padding : const EdgeInsets.all(14),
child : Column(
children: [
// buttons
Wrap(
spacing : 8,
runSpacing : 8,
children : [
_btn('GET List', Icons.people, _getUsers, Colors.green),
_btn('GET Single', Icons.person, _getUser, Colors.blue),
_btn('POST', Icons.add_circle, _createPost, Colors.purple),
_btn('PUT', Icons.edit, _updatePost, Colors.orange),
_btn('DELETE', Icons.delete, _deletePost, Colors.red),
_btn('?query', Icons.filter_list, _queryParams, Colors.teal),
_btn('.handle()', Icons.extension, _handleExt, Colors.pink),
_btn('Cache/Dedup', Icons.cached, _cacheDedup, Colors.amber),
],
),
const SizedBox(height: 10),
// loader
if (_loading)
LinearProgressIndicator(
minHeight : 2,
valueColor : AlwaysStoppedAnimation<Color>(Colors.indigoAccent),
)
else
const SizedBox(height: 2),
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: [
Row(children: [
_dot(Colors.red),
const SizedBox(width: 5),
_dot(Colors.orange),
const SizedBox(width: 5),
_dot(Colors.green),
const SizedBox(width: 10),
const Text('autopilot_output',
style: TextStyle(
color: Colors.white24,
fontSize: 11,
fontFamily: 'monospace')),
]),
const SizedBox(height: 10),
Expanded(
child: SingleChildScrollView(
child: Text(
_out,
style: const TextStyle(
color : Colors.greenAccent,
fontFamily : 'monospace',
fontSize : 12,
height : 1.6,
),
),
),
),
],
),
),
),
const SizedBox(height: 8),
const 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) =>
ElevatedButton.icon(
onPressed : _loading ? null : fn,
icon : Icon(icon, size: 13),
label : Text(label, style: const TextStyle(fontSize: 11)),
style : ElevatedButton.styleFrom(
backgroundColor : c.withOpacity(0.12),
foregroundColor : c,
side : BorderSide(color: c.withOpacity(0.35)),
padding : const EdgeInsets.symmetric(
horizontal: 10, vertical: 7),
minimumSize: const Size(0, 0),
),
);
Widget _dot(Color c) => Container(
width : 10,
height : 10,
decoration : BoxDecoration(color: c, shape: BoxShape.circle),
);
}