sendRequest<R> method
- @protected
- @visibleForTesting
- Uri uri, {
- DataRequestMethod method = DataRequestMethod.GET,
- Map<
String, String> ? headers, - Object? body,
- _OnSuccessGeneric<
R> ? onSuccess, - _OnErrorGeneric<
R> ? onError, - bool omitDefaultParams = false,
- bool returnBytes = false,
- DataRequestLabel? label,
- bool closeClientAfterRequest = true,
inherited
The function used to perform an HTTP request and return an R
.
IMPORTANT:
uri
takes the FULLUri
including query parametersheaders
does NOT include ANY defaults such as defaultHeaders (unless you omit the argument, in which case defaults will be included)
Example:
await sendRequest(
baseUrl.asUri + 'token' & await defaultParams & {'a': 1},
headers: await defaultHeaders & {'a': 'b'},
onSuccess: (data) => data['token'] as String,
);
ignore: comment_references
To build the URI you can use String.asUri
, Uri.+
and Uri.&
.
To merge headers and params with their defaults you can use the helper
Map<String, dynamic>.&
.
In addition, onSuccess
is supplied to post-process the
data in JSON format. Deserialization and initialization
typically occur in this function.
onError
can also be supplied to override _RemoteAdapter.onError
.
Implementation
@protected
@visibleForTesting
Future<R?> sendRequest<R>(
final Uri uri, {
DataRequestMethod method = DataRequestMethod.GET,
Map<String, String>? headers,
Object? body,
_OnSuccessGeneric<R>? onSuccess,
_OnErrorGeneric<R>? onError,
bool omitDefaultParams = false,
bool returnBytes = false,
DataRequestLabel? label,
bool closeClientAfterRequest = true,
}) async {
// defaults
headers ??= await defaultHeaders;
final params =
omitDefaultParams ? <String, dynamic>{} : await defaultParams;
label ??= DataRequestLabel('custom', type: internalType);
onSuccess ??= this.onSuccess;
onError ??= this.onError;
http.Response? response;
Object? responseBody;
Object? error;
StackTrace? stackTrace;
final client = _isTesting ? ref.read(httpClientProvider)! : httpClient;
log(label,
'requesting${_logLevel > 1 ? ' [HTTP ${method.toShortString()}] $uri' : ''}');
try {
final request = http.Request(method.toShortString(), uri & params);
request.headers.addAll(headers);
if (body != null) {
if (body is String) {
request.body = body;
} else if (body is List) {
request.bodyBytes = body.cast<int>();
} else if (body is Map) {
request.bodyFields = body.cast<String, String>();
} else {
throw ArgumentError('Invalid request body "$body".');
}
}
final stream = await client.send(request);
response = await http.Response.fromStream(stream);
} catch (err, stack) {
error = err;
stackTrace = stack;
} finally {
if (closeClientAfterRequest) {
client.close();
}
}
// response handling
var contentType = '';
try {
if (response != null) {
contentType = response.headers['content-type'] ?? 'application/json';
if (returnBytes) {
responseBody = response.bodyBytes;
} else if (response.body.isNotEmpty) {
final body = response.body;
if (contentType.contains('json')) {
responseBody = json.decode(body);
} else {
responseBody = body;
}
}
}
} on FormatException catch (e, stack) {
error = e;
stackTrace = stack;
}
final code = response?.statusCode;
if (error == null && code != null && code >= 200 && code < 400) {
final data = DataResponse(
body: responseBody,
statusCode: code,
headers: {...?response?.headers, 'content-type': contentType},
);
return onSuccess(data, label);
} else {
if (isOfflineError(error)) {
// queue a new operation if:
// - this is a network error and we're offline
// - the request was not a find
if (method != DataRequestMethod.GET) {
OfflineOperation<T>(
httpRequest: '${method.toShortString()} $uri',
label: label,
timestamp: DateTime.now().millisecondsSinceEpoch,
body: body?.toString(),
headers: headers,
onSuccess: onSuccess as _OnSuccessGeneric<T>,
onError: onError as _OnErrorGeneric<T>,
adapter: this as RemoteAdapter<T>,
).add();
}
// wrap error in an OfflineException
final offlineException = OfflineException(error: error!);
// call error handler but do not return it
// (this gives the user the chance to present
// a UI element to retry fetching, for example)
onError(offlineException, label);
// instead return a fallback model from local storage
switch (label.kind) {
case 'findAll':
return findAll(remote: false) as Future<R?>;
case 'findOne':
case 'save':
return label.model as R?;
default:
return null;
}
}
// if it was not a network error
// remove all operations with this request
OfflineOperation.remove(label, this as RemoteAdapter<T>);
final e = DataException(error ?? responseBody ?? '',
stackTrace: stackTrace, statusCode: code);
final sb = StringBuffer(e);
if (_logLevel > 1) {
sb.write(' $uri');
if (stackTrace != null) {
sb.write(stackTrace);
}
}
log(label, sb.toString());
return await onError(e, label);
}
}