one_request 3.1.0
one_request: ^3.1.0 copied to clipboard
Flutter HTTP, optional WebSocket, and optional connectivity notices. One import; every feature can be skipped or turned off.
one_request #
HTTP, optional WebSockets, and optional connectivity notices for Flutter. One import. Every feature can be skipped, replaced, or turned off from the consuming app.
Works on Android, iOS, web, Windows, macOS, and Linux.
dependencies:
one_request: ^3.1.0
import 'package:one_request/one_request.dart';
Do not add dio, dart_either, flutter_easyloading, web_socket_channel, or connectivity_plus. Those packages are already inside one_request. ResponseType is this package’s enum (json / bytes / stream / plain).
| You can use | You can skip / turn off |
|---|---|
request<T>() (throws) |
Use send<T>() (Either) instead |
send<T>() + fold |
Use request<T>() instead |
EasyLoading via wrap() |
Own MaterialApp.builder; enableLoader: false |
JWT setAuth |
Own headers / interceptors; clearAuth() |
WebSocket socket() |
Don’t call it; or enableWebSocket: false |
| Connectivity snackbar / popover / banner | Don’t call setConnectivity; or clearConnectivity() / ui: none / custom builder |
| Default REST error parser | setErrorHandler / clearErrorHandler() |
| Logging | Off by default |
| Cache, retries, batch | Off / zero by default |
Quick start #
Everything except the request itself is optional.
void main() {
OneRequest.configure(
baseUrl: 'https://api.example.com',
enableErrorLogger: true,
enableResponseLogger: true,
enableLoader: true,
enableErrorOverlay: false,
enableSuccessOverlay: false,
);
runApp(MaterialApp(
builder: OneRequest.wrap(),
home: const HomePage(),
));
}
request — data or RequestException #
final api = OneRequest();
try {
final data = await api.request<Map<String, dynamic>>(
url: '/users/me',
method: RequestType.GET,
unwrap: true, // pull nested { "data": ... }
);
} on RequestException catch (e) {
debugPrint(e.message);
debugPrint(e.code);
}
send — Either<String, T> #
Left is the error string. Right is the data.
final result = await api.send<Map<String, dynamic>>(
url: '/users/me',
method: RequestType.GET,
);
result.fold(
ifRight: (data) => debugPrint('ok $data'),
ifLeft: (error) => debugPrint('error $error'),
);
configure — every field optional #
Omit a field to leave the current value unchanged. resetConfig() restores package defaults.
| Argument | Default | What it does |
|---|---|---|
baseUrl |
null |
Default prefix for relative HTTP and ws paths. Absolute http(s):// / ws(s):// URLs are never prefixed. Per-service clients use OneRequest(baseUrl: …) instead. |
headers |
null |
Global headers (per-call header wins) |
interceptors |
null |
Replaces globally attached interceptors |
enableLoader |
true |
Global loading overlay |
enableErrorOverlay |
true |
Global error overlay |
enableSuccessOverlay |
true |
Global success overlay |
enableLogger |
— | Legacy: both error + response logs |
enableErrorLogger |
false |
4xx / 5xx / exceptions |
enableResponseLogger |
false |
2xx / 3xx |
sanitizeErrorMessages |
true |
Strip Dio noise from messages |
maxErrorMessageLength |
220 |
Truncate overlay / Left text |
showStatusCodeInError |
false |
Prefix [status] on messages |
defaultTimeoutSeconds |
60 |
Per-request timeout overrides |
defaultMaxRetries |
0 |
Per-request maxRetries overrides |
defaultRetryDelay |
1s |
Per-request retryDelay overrides |
defaultMaxRedirects |
1 |
Per-request maxRedirects overrides |
defaultUseCache |
false |
GET memory cache; per-request useCache |
enableWebSocket |
true |
Kill switch; nothing connects until socket() |
wsAutoReconnect |
false |
Default for new sockets |
wsEncodeJson |
true |
Encode Map/List as JSON strings |
wsDecodeJson |
true |
Decode incoming {...} / [...] |
wsMaxReconnectAttempts |
5 |
0 = unlimited when reconnect is on |
wsReconnectDelay |
2s |
Delay between reconnects |
enableConnectivity |
false |
Opt into connectivity_plus |
connectivityUi |
snackbar |
snackbar / banner / popover / none |
showOfflineNotice |
true |
Show when going/staying offline |
showOnlineNotice |
true |
Brief “back online” (snackbar) |
offlineMessage |
No internet connection |
Copy |
onlineMessage |
Back online |
Copy |
connectivityNoticeDuration |
4s |
Online snack duration |
Helpers with the same knobs: setOverlaySettings, setLoggerEnabled, setErrorLoggerEnabled, setResponseLoggerEnabled, getOverlaySettings, isLoggerEnabled, isErrorLoggerEnabled, isResponseLoggerEnabled.
Per-request HTTP #
send and request accept:
| Argument | Notes |
|---|---|
url, method |
Required. GET / POST / PUT / PATCH / DELETE |
body, queryParameters |
JSON map, or formData: true |
header |
Merged over global headers |
contentType |
json / stream / bytes / text |
responsetype |
json / bytes / stream / plain |
timeout, maxRedirects |
Seconds / count |
maxRetries, retryDelay |
Transient timeouts |
useCache |
GET only, in-memory |
loader, resultOverlay |
AND with global overlay flags |
innerData / innderData |
Nested { "data": ... } |
unwrap |
request() only; same envelope |
cancelToken |
Re-exported Dio CancelToken |
interceptors |
Extra interceptors for this call |
formData |
FormData.fromMap |
Shared Dio: OneRequest.client (prefer send / request / setAuth). OneRequest.clearCache() dumps GET cache.
Multiple services (multiple instances) #
configure(baseUrl:) is only the default prefix. One app can talk to several hosts.
- HTTP:
OneRequest(baseUrl: 'https://shop.example.com')vsOneRequest(baseUrl: 'https://pay.example.com'). Each instance prefixes its own relative paths. OmitbaseUrlon the constructor to keep usingconfigure. - Sockets: every
socket()/openSocket()call is a newOneSocket. Hold as many as you need. Passwss://other.host/path(orhttps://…) to ignore HTTPbaseUrl, or passbaseUrl:on that call / useclient.openSocket. - Absolute URLs on
send/request/socketalways win — they are not concatenated onto any base.
final shop = OneRequest(baseUrl: 'https://shop.example.com');
final pay = OneRequest(baseUrl: 'https://pay.example.com');
await shop.request<Map<String, dynamic>>(
url: '/orders',
method: RequestType.GET,
);
await pay.send<Map<String, dynamic>>(
url: '/charge',
method: RequestType.POST,
body: {'amount': 10},
);
// Same HTTP default, different live host:
final chat = OneRequest.socket(url: '/ws/chat'); // → configure/instance base
final live = OneRequest.socket(url: 'wss://live.example.com/feed');
final alerts = pay.openSocket(url: '/ws/alerts'); // pay's base → wss://pay…/ws/alerts
await Future.wait([chat.ready, live.ready, alerts.ready]);
OneRequest.socket(url: '/ws', baseUrl: 'https://realtime.example.com') overrides without creating an HTTP client.
await api.request<List<int>>(
url: '/invoice.pdf',
method: RequestType.GET,
responsetype: ResponseType.bytes,
loader: false,
resultOverlay: false,
timeout: 30,
maxRetries: 1,
);
JWT (optional) #
Skip this if you already attach headers. clearAuth() removes it.
OneRequest.setAuth(
getAccessToken: () => storage.read('access_token'),
getRefreshToken: () => storage.read('refresh_token'),
saveTokens: (access, refresh) async { /* persist */ },
refreshPath: '/auth/token/refresh/',
skipPathContains: const ['/auth/token'],
headerName: 'Authorization',
headerPrefix: 'Bearer ',
onRefreshFailed: () async { /* logout */ },
);
Loading UI (optional) #
MaterialApp(builder: OneRequest.wrap()); // EasyLoading + connectivity host
OneRequest.initLoading; // same as wrap()
OneRequest.wrap((context, child) { // compose your overlay
return Stack(children: [child!, const MyBanner()]);
});
OneRequest.loadingconfig( // colors / indicator / mask
progressColor: Colors.white,
backgroundColor: Colors.black,
);
LoadingStuff.setCustomBuilders( // replace loader / error widgets
loadingBuilder: (context, status) => const Spinner(),
errorBuilder: (context, message) => Text(message),
localization: (msg) => msg,
);
OneRequest.loadingWidget(status: 'Saving');
await OneRequest.dismissLoading;
Skip wrap() if you do not want EasyLoading. Connectivity default UI then needs connectivityOverlay (below) or your own onChanged / builder.
Uploads #
Bytes work on every platform (including web):
final api = OneRequest();
await api.request<Map<String, dynamic>>(
url: '/upload',
method: RequestType.POST,
formData: true,
body: {
'file': api.fileFromByte(filebyte: bytes),
},
);
file() and fileFromPath() need dart:io (mobile/desktop). Guard with hasDartIo if you share code with web. fileFormString is also available.
Errors and overlays #
Django-style bodies (error, detail, details, code, data) are parsed by default.
OneRequest.setErrorHandler(handler: myParser); // replace
OneRequest.setErrorHandler(logger: myLog); // keep parser
OneRequest.clearErrorHandler(); // off
OneRequest.resetErrorHandler(); // package default
await api.send(
url: '/silent',
method: RequestType.GET,
loader: false,
resultOverlay: false,
);
RequestException fields: message, code, data, statusCode, url. Helpers: RestErrorParser.handler, RestErrorParser.messageFromBody, unwrapPayload.
Batch, cache, retries #
final results = await OneRequest.batch<Map<String, dynamic>>([
{'url': '/a', 'method': RequestType.GET, 'useCache': true},
{'url': '/b', 'method': RequestType.POST, 'body': {'x': 1}},
], maxRetries: 2, exponentialBackoff: true);
OneRequest.clearCache();
WebSocket (optional) #
Nothing connects until you call socket(). Each call is a separate connection (different URL allowed). Hard-disable with configure(enableWebSocket: false) — then socket() throws. Defaults: JSON on, reconnect off.
final socket = OneRequest.socket(
url: '/ws/chat', // or wss://other.host/path — not tied to HTTP baseUrl
baseUrl: 'https://realtime.example.com', // optional; else instance / configure
headers: {'X-Client': 'app'},
protocols: const ['json'],
pingInterval: const Duration(seconds: 30), // native only
connectTimeout: const Duration(seconds: 10),
autoReconnect: false,
maxReconnectAttempts: 5,
reconnectDelay: const Duration(seconds: 2),
encodeJson: true,
decodeJson: true,
onMessage: (event) {},
onState: (state) {},
onError: (error, stack) {},
);
await socket.ready;
socket.send({'type': 'hello'}); // Map/List → JSON if encodeJson
socket.send('plain');
socket.messages.listen((event) {});
socket.states.listen((state) {});
debugPrint('${socket.state} ${socket.uri}');
await socket.close(1000, 'bye');
Also: client.openSocket(...) (uses that instance’s baseUrl / headers), OneSocket.connect(...), OneRequest.isWebSocketEnabled(), resolveSocketUri, resolveRequestUrl. Extra headers are sent on Android / iOS / desktop; browsers cannot set WebSocket headers.
SocketState: disconnected / connecting / connected / reconnecting / closing.
Connectivity (optional, off by default) #
Uses connectivity_plus only after you opt in. Interface up/down (not a full internet ping). Default UI is a snackbar; pick popover, banner, none, or a builder.
OneRequest.setConnectivity(); // snackbar + “Back online”
OneRequest.setConnectivity(ui: ConnectivityUi.popover);
OneRequest.setConnectivity(ui: ConnectivityUi.banner);
OneRequest.setConnectivity(
ui: ConnectivityUi.none,
onChanged: (status) => debugPrint('online=${status.online}'),
);
OneRequest.setConnectivity(
showOffline: true,
showOnline: false,
offlineMessage: 'No internet',
onlineMessage: 'Back online',
noticeDuration: const Duration(seconds: 3),
builder: (context, status, child) {
if (status.online) return child;
return Stack(children: [child, const Text('offline')]);
},
);
OneRequest.setConnectivity(enabled: false);
OneRequest.clearConnectivity();
Read without UI: isConnectivityEnabled(), connectivityUi, connectivityStatus, connectivity (stream), checkConnectivity().
Host widgets:
MaterialApp(builder: OneRequest.wrap()); // EasyLoading + notices
MaterialApp(
builder: (context, child) =>
OneRequest.connectivityOverlay(child: child!),
);
ConnectivityStatus.online and .connections (NetworkKind: wifi, mobile, ethernet, vpn, bluetooth, satellite, other, none).
Logging #
Off by default.
OneRequest.configure(
enableErrorLogger: true,
enableResponseLogger: true,
);
OneRequest.logger; // logging package Logger
Re-exported types #
From package:one_request/one_request.dart you already have CancelToken, Interceptor, FormData, MultipartFile, DioException, Either / Left / Right, EasyLoading / EasyLoadingMaskType, plus this package’s RequestType, ResponseType, ContentType, RequestException, OneSocket, ConnectivityUi, resolveSocketUri, resolveRequestUrl.
Platforms #
Declared in pubspec.yaml: Android, iOS, web, Windows, macOS, Linux. HTTP and WebSocket run on all of them. Path uploads (file / fileFromPath) are native-only. Compatibility gates: tool/check_compatibility.ps1 (analyze + VM tests + Chrome).
Migrating from 2.x #
2.x (either_dart) |
3.x (dart_either) |
|---|---|
Either<T, String> |
Either<String, T> |
Left = success |
Right = success |
fold((data) {}, (error) {}) |
fold(ifRight: ..., ifLeft: ...) |
.left / .right |
getOrNull() / leftOrNull() |
Or stop folding and use request<T>().
Example #
See example/lib/main.dart — HTTP, WebSocket, and connectivity UI toggles.
License #
GPL-3.0