
Flutter Networking
Wire Flutter apps to REST JSON APIs with a typed ApiService wrapper, timeouts, and injectable auth headers for solo mobile shipping.
Overview
Flutter Networking is an agent skill for the Build phase that scaffolds a typed Flutter ApiService for JSON REST calls with timeouts and injectable headers.
Install
npx skills add https://github.com/madteacher/mad-agents-skills --skill flutter-networkingWhat is this skill?
- ApiService facade over package:http with configurable base URL and 15s default timeout
- Typed getJson, postJson, and putJson entry points with JsonDecoder<T> callbacks
- Optional HeadersProvider hook for async auth or session headers on every request
- Query parameter and per-call header overrides on shared send path
- Client lifecycle: inject Client for tests or let the service own and dispose the default client
- Default request timeout: 15 seconds
- Exposes GET, POST, and PUT JSON helpers on a shared send path
Adoption & trust: 603 installs on skills.sh; 101 GitHub stars; 2/3 security scanners passed (skills.sh audits).
What problem does it solve?
Your Flutter app scatters raw HTTP calls with duplicated base URLs, missing timeouts, and untyped JSON parsing.
Who is it for?
Solo builders creating Flutter apps that consume a REST backend and want a single maintained networking module.
Skip if: GraphQL-only stacks, gRPC/protobuf clients, or teams already standardized on dio with interceptors and code generation.
When should I use this skill?
You are implementing or refactoring Flutter REST/JSON networking and need a centralized ApiService with timeouts and header injection.
What do I get? / Deliverables
You get a reusable ApiService with typed JSON helpers, centralized headers, and test-friendly client injection for your mobile API layer.
- ApiService module with typed JSON request methods
- Configurable headers and timeout defaults for the mobile client
Recommended Skills
Journey fit
Build is where mobile clients connect to backends; this skill implements the HTTP integration layer, not distribution or store launch. Integrations fits client-side API access—GET/POST/PUT helpers, query params, shared headers—rather than pure widget layout.
How it compares
Use as a focused http.Client template skill rather than a full state-management or offline-sync framework.
Common Questions / FAQ
Who is flutter-networking for?
Indie Flutter developers and agent-assisted builders who need a small, readable JSON API layer without adopting a heavier networking stack.
When should I use flutter-networking?
During Build integrations while connecting screens to your backend, adding auth headers, or refactoring duplicated http usage in a Flutter repo.
Is flutter-networking safe to install?
The skill implies network access to your API endpoints—review the Security Audits panel on this Prism page and never embed production secrets in generated code.
SKILL.md
READMESKILL.md - Flutter Networking
import 'dart:async'; import 'dart:convert'; import 'package:http/http.dart' as http; typedef JsonDecoder<T> = T Function(Object? json); typedef HeadersProvider = FutureOr<Map<String, String>> Function(); class ApiService { final http.Client _client; final String _baseUrl; final Duration _timeout; final HeadersProvider? _headersProvider; final bool _ownsClient; ApiService({ required String baseUrl, http.Client? client, Duration timeout = const Duration(seconds: 15), HeadersProvider? headersProvider, }) : _baseUrl = baseUrl, _client = client ?? http.Client(), _timeout = timeout, _headersProvider = headersProvider, _ownsClient = client == null; Future<T?> getJson<T>( String path, { Map<String, String>? queryParameters, Map<String, String>? headers, required JsonDecoder<T> decode, }) { return _sendJson( 'GET', path, queryParameters: queryParameters, headers: headers, decode: decode, ); } Future<T?> postJson<T>( String path, { Object? body, Map<String, String>? queryParameters, Map<String, String>? headers, required JsonDecoder<T> decode, }) { return _sendJson( 'POST', path, queryParameters: queryParameters, headers: headers, body: body, decode: decode, ); } Future<T?> putJson<T>( String path, { Object? body, Map<String, String>? queryParameters, Map<String, String>? headers, required JsonDecoder<T> decode, }) { return _sendJson( 'PUT', path, queryParameters: queryParameters, headers: headers, body: body, decode: decode, ); } Future<T?> deleteJson<T>( String path, { Map<String, String>? queryParameters, Map<String, String>? headers, JsonDecoder<T>? decode, }) { return _sendJson( 'DELETE', path, queryParameters: queryParameters, headers: headers, decode: decode, ); } Future<T?> _sendJson<T>( String method, String path, { Map<String, String>? queryParameters, Map<String, String>? headers, Object? body, JsonDecoder<T>? decode, }) async { final uri = _uri(path, queryParameters); final requestHeaders = await _headers(headers, hasBody: body != null); final encodedBody = body == null ? null : jsonEncode(body); try { final response = await switch (method) { 'GET' => _client.get(uri, headers: requestHeaders), 'POST' => _client.post(uri, headers: requestHeaders, body: encodedBody), 'PUT' => _client.put(uri, headers: requestHeaders, body: encodedBody), 'DELETE' => _client.delete(uri, headers: requestHeaders), _ => throw ArgumentError.value(method, 'method', 'Unsupported method'), }.timeout(_timeout); return _handleResponse(response, decode); } on TimeoutException catch (error) { throw ApiTimeoutException( 'Request timed out after ${_timeout.inSeconds}s', cause: error, ); } on http.ClientException catch (error) { throw ApiNetworkException(error.message, cause: error); } } Uri _uri(String path, Map<String, String>? queryParameters) { final base = _baseUrl.endsWith('/') ? _baseUrl.substring(0, _baseUrl.length - 1) : _baseUrl; final normalizedPath = path.startsWith('/') ? path : '/$path'; return Uri.parse( '$base$normalizedPath', ).replace(queryParameters: queryParameters); } Future<Map<String, String>> _headers( Map<String, String>? extra, { required bool hasBody, }) async { final provided = await _headersProvider?.call(); return { 'Accept': 'application/json', if (hasBody && _extraBodyNeedsJson(extra)) 'Content-Type': 'application/json', ...?provided, ...?extra, }; } bool _extraBodyNeedsJson(Map<String, String>? extra) { return extra == null || !extra.keys.an