anynet 0.1.0-dev.1
anynet: ^0.1.0-dev.1 copied to clipboard
AnyNet is a Flutter networking layer (DEV) with get/post/put/delete helpers, retry, timeout, caching, offline support, logging, and unified error handling.
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:anynet/anynet.dart';
void main() {
runApp(const MyApp());
}
/// ─────────────────────────────────────────────────────────
/// AnyNet Mental Model
///
/// Think of AnyNet as:
///
/// UI
/// ↓
/// AnyNet.get/post/put/delete() ← easy, http/dio-like API
/// ↓
/// AnyNetClient.send() ← single execution pipeline
/// ↓
/// Interceptors (logging, auth)
/// ↓
/// Retry / Cache / Timeout
/// ↓
/// Network (IO / Dio adapter)
///
/// Beginners: just use AnyNet.get()
/// Advanced: customize AnyNetClient
/// ─────────────────────────────────────────────────────────
/// ---------------------------------------------------------------------------
/// Example Model
/// ---------------------------------------------------------------------------
class User {
final int? id;
final String name;
final String email;
User({this.id, required this.name, required this.email});
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'] as int?,
name: json['name'] ?? '',
email: json['email'] ?? '',
);
}
Map<String, dynamic> toJson() {
return {'name': name, 'email': email};
}
User copyWith({String? name, String? email}) {
return User(id: id, name: name ?? this.name, email: email ?? this.email);
}
}
/// ---------------------------------------------------------------------------
/// App
/// ---------------------------------------------------------------------------
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: AnyNetCrudExample(),
);
}
}
/// ---------------------------------------------------------------------------
/// Screen
/// ---------------------------------------------------------------------------
class AnyNetCrudExample extends StatefulWidget {
const AnyNetCrudExample({super.key});
@override
State<AnyNetCrudExample> createState() => _AnyNetCrudExampleState();
}
class _AnyNetCrudExampleState extends State<AnyNetCrudExample> {
late final AnyNet _anyNet;
List<User> _users = [];
bool _loading = false;
String? _error;
int _requestCount = 0;
@override
void initState() {
super.initState();
// 👉 Advanced configuration (safe to ignore for beginners)
final client = AnyNetClient(
baseUrl: 'https://jsonplaceholder.typicode.com',
retryPolicy: const RetryPolicy(maxRetries: 2),
cache: MemoryCache(),
interceptors: [LoggingInterceptor()],
);
// 👉 Beginner-friendly API
_anyNet = AnyNet(client);
_fetchUsers();
}
/// -------------------------------------------------------------------------
/// GET – Fetch users list
///
/// http.get(url)
/// dio.get('/users')
/// ↓
/// AnyNet.get('/users')
/// -------------------------------------------------------------------------
Future<void> _fetchUsers() async {
_setLoading();
final response = await _anyNet.get<List<User>>(
'/users',
cacheable: true,
mapper: (json) => (json as List).map((e) => User.fromJson(e)).toList(),
);
_handleResponse(
response,
onSuccess: (data) {
_users = data;
},
);
}
/// -------------------------------------------------------------------------
/// POST – Add new user
/// -------------------------------------------------------------------------
Future<void> _addUser() async {
_setLoading();
final response = await _anyNet.post<User>(
'/users',
data: User(name: 'New User', email: 'new@anynet.dev').toJson(),
mapper: (json) => User.fromJson(json),
);
_handleResponse(
response,
onSuccess: (user) {
_users.insert(0, user);
},
);
}
/// -------------------------------------------------------------------------
/// PUT – Update user
/// -------------------------------------------------------------------------
Future<void> _updateUser(User user) async {
_setLoading();
final updated = user.copyWith(name: '${user.name} ✨');
final response = await _anyNet.put<User>(
'/users/${user.id}',
data: updated.toJson(),
mapper: (_) => updated,
);
_handleResponse(
response,
onSuccess: (_) {
final index = _users.indexWhere((u) => u.id == user.id);
_users[index] = updated;
},
);
}
/// -------------------------------------------------------------------------
/// DELETE – Remove user
/// -------------------------------------------------------------------------
Future<void> _deleteUser(User user) async {
_setLoading();
final response = await _anyNet.delete<void>(
'/users/${user.id}',
mapper: (_) => null,
);
_handleResponse(
response,
onSuccess: (_) {
_users.removeWhere((u) => u.id == user.id);
},
);
}
/// -------------------------------------------------------------------------
/// Unified response handler
/// -------------------------------------------------------------------------
void _handleResponse<T>(
ApiResponse<T> response, {
required void Function(T data) onSuccess,
}) {
setState(() {
_loading = false;
_requestCount++;
});
if (response.isSuccess && response.data != null) {
onSuccess(response.data as T);
_error = null;
} else {
log('[AnyNet] ERROR ${response.error?.message}');
_error = _mapError(response.error);
}
}
String _mapError(ApiError? error) {
switch (error?.type) {
case ApiErrorType.offline:
return 'No internet connection';
case ApiErrorType.timeout:
return 'Request timed out';
case ApiErrorType.network:
return 'Network error';
default:
return 'Something went wrong';
}
}
void _setLoading() {
setState(() {
_loading = true;
_error = null;
});
}
/// -------------------------------------------------------------------------
/// UI
/// -------------------------------------------------------------------------
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('AnyNet Example'),
actions: [
IconButton(
icon: const Icon(Icons.add),
onPressed: _loading ? null : _addUser,
),
],
),
body: Column(
children: [
if (_loading) const LinearProgressIndicator(),
Padding(
padding: const EdgeInsets.all(8),
child: Text(
_loading
? 'Loading...'
: _error != null
? 'Error occurred'
: 'Loaded ${_users.length} users • Requests: $_requestCount',
style: Theme.of(context).textTheme.bodySmall,
),
),
if (_error != null)
Padding(
padding: const EdgeInsets.all(12),
child: Text(_error!, style: const TextStyle(color: Colors.red)),
),
Expanded(
child: ListView.builder(
itemCount: _users.length,
itemBuilder: (_, index) {
final user = _users[index];
return ListTile(
title: Text(user.name),
subtitle: Text(user.email),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.edit),
onPressed: () => _updateUser(user),
),
IconButton(
icon: const Icon(Icons.delete),
onPressed: () => _deleteUser(user),
),
],
),
);
},
),
),
],
),
);
}
}
/// ---------------------------------------------------------------------------
/// Features demonstrated:
/// ✔ Typed API responses
/// ✔ GET / POST / PUT / DELETE
/// ✔ http/dio-like API
/// ✔ Retry with backoff
/// ✔ Request caching
/// ✔ Interceptors (logging)
/// ✔ Unified error handling
/// ✔ Beginner + advanced friendly
/// ---------------------------------------------------------------------------