api_flow 2.0.0
api_flow: ^2.0.0 copied to clipboard
A comprehensive Flutter package for smart API management with Either pattern, local caching, and robust error handling
import 'package:flutter/material.dart';
import 'package:api_flow/api_flow.dart';
// Example User model
class User {
final int id;
final String name;
final String email;
User({required this.id, required this.name, required this.email});
factory User.fromJson(Map<String, dynamic> json) =>
User(id: json['id'], name: json['name'], email: json['email']);
Map<String, dynamic> toJson() => {'id': id, 'name': name, 'email': email};
}
void main() {
runApp(ApiExample());
}
// Example API service
class ApiExample extends StatefulWidget {
const ApiExample({super.key});
@override
State<ApiExample> createState() => _ApiExampleState();
}
class _ApiExampleState extends State<ApiExample> {
List<User> users = [];
bool isLoading = false;
String statusMessage = '';
@override
void initState() {
super.initState();
_initializeApi();
_loadUsers();
}
// Initialize the API service
Future<void> _initializeApi() async {
await EitherApiService.init(
baseUrl: 'https://jsonplaceholder.typicode.com',
enableCaching: true,
boxNameCaching: 'api_cache',
enableLogging: true,
);
}
// Example: GET request with error handling
Future<void> _loadUsers() async {
setState(() {
isLoading = true;
statusMessage = 'Loading users...';
});
final result = await EitherApiService.get<List<User>>(
endpoint: '/users',
fromJson: (json) => (json as List).map((e) => User.fromJson(e)).toList(),
enableCachingRequest: true,
);
// Handle the Either result
result.fold(
// Left side: Error occurred
(error) {
setState(() {
isLoading = false;
statusMessage = 'Error: ${error.message}';
});
// Handle different error types
switch (error.type) {
case ErrorType.noInternet:
_showSnackbar('No internet connection');
break;
case ErrorType.timeout:
_showSnackbar('Request timeout - please try again');
break;
case ErrorType.serverError:
_showSnackbar('Server error - please try later');
break;
default:
_showSnackbar('Something went wrong');
}
},
// Right side: Success
(success) {
setState(() {
users = success.dataFromApi ?? [];
isLoading = false;
statusMessage = 'Loaded ${users.length} users';
});
// Access additional response details
debugPrint('Response time: ${success.dioDetails.calculatedDuration}ms');
debugPrint('Status code: ${success.dioDetails.statusCode}');
},
);
}
// Example: POST request - Create new user
Future<void> _createUser(String name, String email) async {
setState(() {
statusMessage = 'Creating user...';
});
final newUser = User(id: 0, name: name, email: email);
final result = await EitherApiService.post<User>(
endpoint: '/users',
data: newUser.toJson(),
fromJson: (json) => User.fromJson(json),
invalidateListCache: true, // Clear users list cache
);
result.fold(
(error) {
setState(() {
statusMessage = 'Failed to create user: ${error.message}';
});
_showSnackbar('Failed to create user');
},
(success) {
final createdUser = success.dataFromApi!;
setState(() {
users.insert(0, createdUser);
statusMessage = 'User ${createdUser.name} created successfully';
});
_showSnackbar('User created successfully');
},
);
}
// Example: PUT request - Update user
Future<void> _updateUser(User user, String newName) async {
final updatedUser = User(id: user.id, name: newName, email: user.email);
final result = await EitherApiService.put<User>(
endpoint: '/users/${user.id}',
data: updatedUser.toJson(),
fromJson: (json) => User.fromJson(json),
updateCache: true, // Update cache with new data
);
result.fold((error) => _showSnackbar('Failed to update user'), (success) {
setState(() {
final index = users.indexWhere((u) => u.id == user.id);
if (index != -1) {
users[index] = success.dataFromApi!;
}
statusMessage = 'User updated successfully';
});
_showSnackbar('User updated');
});
}
// Example: DELETE request - Remove user
Future<void> _deleteUser(User user) async {
final result = await EitherApiService.delete<void>(
endpoint: '/users/${user.id}',
invalidateCache: true, // Remove from cache
);
result.fold((error) => _showSnackbar('Failed to delete user'), (success) {
setState(() {
users.removeWhere((u) => u.id == user.id);
statusMessage = 'User deleted successfully';
});
_showSnackbar('User deleted');
});
}
// Helper method to show snackbar
void _showSnackbar(String message) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(message)));
}
// UI to create new user
void _showCreateUserDialog() {
final nameController = TextEditingController();
final emailController = TextEditingController();
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Create New User'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: nameController,
decoration: InputDecoration(labelText: 'Name'),
),
TextField(
controller: emailController,
decoration: InputDecoration(labelText: 'Email'),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('Cancel'),
),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
_createUser(nameController.text, emailController.text);
},
child: Text('Create'),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('EitherApiService Example'),
actions: [IconButton(icon: Icon(Icons.refresh), onPressed: _loadUsers)],
),
body: Column(
children: [
// Status message
Container(
width: double.infinity,
padding: EdgeInsets.all(16),
color: Colors.grey[100],
child: Text(
statusMessage,
style: TextStyle(
fontWeight: FontWeight.w500,
color: statusMessage.contains('Error')
? Colors.red
: Colors.black,
),
),
),
// Loading indicator
if (isLoading) LinearProgressIndicator(),
// Users list
Expanded(
child: users.isEmpty
? Center(
child: isLoading
? CircularProgressIndicator()
: Text('No users found'),
)
: ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) {
final user = users[index];
return ListTile(
leading: CircleAvatar(child: Text(user.name[0])),
title: Text(user.name),
subtitle: Text(user.email),
trailing: PopupMenuButton(
itemBuilder: (context) => [
PopupMenuItem(value: 'edit', child: Text('Edit')),
PopupMenuItem(
value: 'delete',
child: Text('Delete'),
),
],
onSelected: (value) {
if (value == 'edit') {
_showEditUserDialog(user);
} else if (value == 'delete') {
_showDeleteConfirmation(user);
}
},
),
);
},
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: _showCreateUserDialog,
tooltip: 'Add User',
child: Icon(Icons.add),
),
);
}
void _showEditUserDialog(User user) {
final nameController = TextEditingController(text: user.name);
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Edit User'),
content: TextField(
controller: nameController,
decoration: InputDecoration(labelText: 'Name'),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('Cancel'),
),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
_updateUser(user, nameController.text);
},
child: Text('Update'),
),
],
),
);
}
void _showDeleteConfirmation(User user) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Delete User'),
content: Text('Are you sure you want to delete ${user.name}?'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('Cancel'),
),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
_deleteUser(user);
},
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
child: Text('Delete'),
),
],
),
);
}
}