falkon 0.1.0
falkon: ^0.1.0 copied to clipboard
A lean, interceptor-driven HTTP client for Dart — built on dart:io with zero third-party dependencies.
// ignore_for_file: unused_local_variable
import 'dart:io';
import 'package:falkon/falkon.dart';
// ---------------------------------------------------------------------------
// 1. Model
// ---------------------------------------------------------------------------
class Post {
final int id;
final String title;
final String body;
const Post({required this.id, required this.title, required this.body});
factory Post.fromJson(Map<String, dynamic> json) => Post(
id: json['id'] as int,
title: json['title'] as String,
body: json['body'] as String,
);
}
// ---------------------------------------------------------------------------
// 2. Repository
// ---------------------------------------------------------------------------
class PostRepository extends NetworkRepository {
final NetworkClient _client;
const PostRepository(this._client);
Future<Result<Post>> fetchPost(int id) => _client.get(
'/posts/$id',
parser: Post.fromJson,
);
Future<Result<Post>> createPost({
required String title,
required String body,
}) =>
_client.post(
'/posts',
body: {'title': title, 'body': body, 'userId': 1},
parser: Post.fromJson,
);
}
// ---------------------------------------------------------------------------
// 3. Domain service
// ---------------------------------------------------------------------------
class PostService extends NetworkService {
PostService(super.client);
Future<Result<Post>> getAndValidate(int id) async {
final result = await client.get('/posts/$id', parser: Post.fromJson);
return result.map((post) {
if (post.title.isEmpty) throw const FormatException('Empty title');
return post;
});
}
}
// ---------------------------------------------------------------------------
// 4. Wire-up
// ---------------------------------------------------------------------------
Future<void> main() async {
// Build config once — treat it like a singleton.
final config = FalkonClientConfig.builder('https://jsonplaceholder.typicode.com')
.timeout(const Duration(seconds: 10))
.maxRetries(2)
.addInterceptor(LoggingInterceptor())
.addInterceptor(BearerTokenInterceptor(() => 'my-secret-token'))
.build();
final client = FalkonClient(config);
// --- GET ---
final postResult = await client.get('/posts/1', parser: Post.fromJson);
postResult.fold(
onSuccess: (post) => print('Fetched: ${post.title}'),
onFailure: (err) => print('Error: $err'),
);
// --- POST ---
final created = await client.post(
'/posts',
body: {'title': 'Hello', 'body': 'World', 'userId': 1},
parser: Post.fromJson,
);
print(created.isSuccess ? 'Created id=${created.data.id}' : 'Failed: ${created.error}');
// --- Upload ---
final uploadResult = await client.upload<String>(
'/upload',
files: [UploadFile.fromFile(fieldName: 'avatar', file: File('avatar.png'))],
fields: {'userId': '42'},
onProgress: (sent, total) => print('$sent / $total bytes'),
);
// --- Download ---
final dlResult = await client.download(
'https://example.com/report.pdf',
savePath: '/tmp/report.pdf',
onProgress: (recv, total) => print('Downloaded $recv / $total'),
);
// --- Per-request options (e.g. different base URL, extra headers) ---
final special = await client.get<String>(
'/health',
options: RequestOptions(
baseUrl: 'https://internal.example.com',
headers: {'X-Internal-Token': 'abc'},
timeout: const Duration(seconds: 5),
),
);
// --- Repository usage ---
final repo = PostRepository(client);
final result = await repo.fetchPost(2);
result.onSuccess((p) => print('Repo got: ${p.title}')).onFailure((e) => print('Repo err: $e'));
client.close();
}