sendRequest<R> method

  1. @protected
  2. @visibleForTesting
Future<R?> sendRequest<R>(
  1. Uri uri, {
  2. DataRequestMethod method = DataRequestMethod.GET,
  3. Map<String, String>? headers,
  4. Object? body,
  5. _OnSuccessGeneric<R>? onSuccess,
  6. _OnErrorGeneric<R>? onError,
  7. bool omitDefaultParams = false,
  8. bool returnBytes = false,
  9. DataRequestLabel? label,
  10. bool closeClientAfterRequest = true,
})
inherited

The function used to perform an HTTP request and return an R.

IMPORTANT:

  • uri takes the FULL Uri including query parameters
  • headers 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);
  }
}