easy_api_call 1.0.0
easy_api_call: ^1.0.0 copied to clipboard
A reusable HTTP API service with token management.
example/lib/main.dart
import 'package:easy_api_call/easy_api_call.dart';
import 'package:flutter/material.dart';
void main() {
// One-time configuration
ApiService().configure(
ApiConfig(
baseUrl: 'https://jsonplaceholder.typicode.com',
enableLogging: true,
),
);
runApp(const CRUDApp());
}
class CRUDApp extends StatelessWidget {
const CRUDApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'CRUD Demo',
theme: ThemeData(primarySwatch: Colors.blue),
home: const CRUDScreen(),
);
}
}
class CRUDScreen extends StatefulWidget {
const CRUDScreen({super.key});
@override
State<CRUDScreen> createState() => _CRUDScreenState();
}
class _CRUDScreenState extends State<CRUDScreen> {
final List<Map<String, dynamic>> _operations = [
{'method': 'GET', 'name': 'Fetch Posts'},
{'method': 'POST', 'name': 'Create Post'},
{'method': 'PUT', 'name': 'Update Post'},
{'method': 'PATCH', 'name': 'Modify Post'},
{'method': 'DELETE', 'name': 'Delete Post'},
];
List<dynamic> _posts = [];
String _result = '';
bool _isLoading = false;
String? _error;
Future<void> _fetchPosts() async {
final response = await ApiService().get<List<dynamic>>('/posts');
setState(() {
_posts = response.data ?? [];
_result = 'Fetched ${_posts.length} posts';
});
}
Future<void> _createPost() async {
final response = await ApiService().post<dynamic>(
'/posts',
data: {
'title': 'New Post',
'body': 'This is a newly created post',
'userId': 1,
},
);
setState(() => _result = 'Created post with ID: ${response.data['id']}');
_fetchPosts(); // Refresh the list
}
Future<void> _updatePost() async {
if (_posts.isEmpty) {
setState(() => _error = 'No posts available to update');
return;
}
final postId = _posts.first['id'];
final response = await ApiService().put<dynamic>(
'/posts/$postId',
data: {
'id': postId,
'title': 'Updated Post',
'body': 'This post has been completely updated',
'userId': 1,
},
);
setState(() => _result = 'Updated post ID: ${response.data['id']}');
_fetchPosts(); // Refresh the list
}
Future<void> _patchPost() async {
if (_posts.isEmpty) {
setState(() => _error = 'No posts available to modify');
return;
}
final postId = _posts.first['id'];
final response = await ApiService().patch<dynamic>(
'/posts/$postId',
data: {
'title': 'Partially Updated Post',
},
);
setState(() => _result = 'Patched post ID: ${response.data['id']}');
_fetchPosts(); // Refresh the list
}
Future<void> _deletePost() async {
if (_posts.isEmpty) {
setState(() => _error = 'No posts available to delete');
return;
}
final postId = _posts.first['id'];
await ApiService().delete('/posts/$postId');
setState(() => _result = 'Deleted post ID: $postId');
_fetchPosts(); // Refresh the list
}
Future<void> _executeOperation(int index) async {
setState(() {
_isLoading = true;
_error = null;
_result = '';
});
try {
switch (_operations[index]['method']) {
case 'GET':
await _fetchPosts();
break;
case 'POST':
await _createPost();
break;
case 'PUT':
await _updatePost();
break;
case 'PATCH':
await _patchPost();
break;
case 'DELETE':
await _deletePost();
break;
}
} catch (e) {
setState(() => _error = 'Unexpected error: $e');
} finally {
setState(() => _isLoading = false);
}
}
@override
void initState() {
super.initState();
_fetchPosts();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('CRUD Operations')),
body: Column(
children: [
if (_isLoading) const LinearProgressIndicator(),
if (_error != null)
Container(
padding: const EdgeInsets.all(16),
color: Colors.red[100],
child: Row(
children: [
const Icon(Icons.error, color: Colors.red),
const SizedBox(width: 8),
Expanded(child: Text(_error!)),
IconButton(
icon: const Icon(Icons.close),
onPressed: () => setState(() => _error = null),
),
],
),
),
Expanded(
child: ListView.builder(
itemCount: _posts.length,
itemBuilder: (context, index) => ListTile(
title: Text(_posts[index]['title'] ?? 'No title'),
subtitle: Text('ID: ${_posts[index]['id']}'),
),
),
),
Container(
padding: const EdgeInsets.all(16),
child: Text(_result, style: Theme.of(context).textTheme.titleMedium),
),
],
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
for (int i = 0; i < _operations.length; i++)
Padding(
padding: const EdgeInsets.only(bottom: 8),
child: FloatingActionButton.extended(
heroTag: 'btn$i',
onPressed: () => _executeOperation(i),
label: Text(_operations[i]['name']),
icon: Icon(_getMethodIcon(_operations[i]['method'])),
),
),
],
),
);
}
IconData _getMethodIcon(String method) {
switch (method) {
case 'GET': return Icons.download;
case 'POST': return Icons.add;
case 'PUT': return Icons.edit;
case 'PATCH': return Icons.edit_attributes;
case 'DELETE': return Icons.delete;
default: return Icons.question_mark;
}
}
}