Jonatas

update to 3.22.0

## [3.22.0]
- Added: more multipart options. Now you can send as multipart:
File:
'file':MultipartFile(File('./images/avatar.png'), filename: 'avatar.png'),
String path:
'file':MultipartFile('./images/avatar.png', filename: 'avatar.png'),
Or bytes (Flutter web work only with bytes):
'file':MultipartFile(File('file').readAsBytesSync(), filename: 'avatar.png'),
- Improve: auto jsonDecode occurs only if response.header.contentType is "application/json"
- Added: Upload Progress to MultipartRequest
- Added support to List<MultipartFile> (@jasonlaw)
- Improve and fix requests types (@eduardoflorence)
- Fix HeaderValue variables with same name (@haidang93)
## [3.21.3]
- Improve multipart file and defaultDecoder on GetConnect
... ...
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'routes/app_pages.dart';
... ...
... ... @@ -127,6 +127,7 @@ class GetConnect extends GetConnectInterface {
Map<String, String> headers,
Map<String, dynamic> query,
Decoder<T> decoder,
Progress uploadProgress,
}) {
_checkIfDisposed();
return httpClient.post<T>(
... ... @@ -136,6 +137,7 @@ class GetConnect extends GetConnectInterface {
contentType: contentType,
query: query,
decoder: decoder,
uploadProgress: uploadProgress,
);
}
... ... @@ -147,6 +149,7 @@ class GetConnect extends GetConnectInterface {
Map<String, String> headers,
Map<String, dynamic> query,
Decoder<T> decoder,
Progress uploadProgress,
}) {
_checkIfDisposed();
return httpClient.put<T>(
... ... @@ -156,6 +159,7 @@ class GetConnect extends GetConnectInterface {
contentType: contentType,
query: query,
decoder: decoder,
uploadProgress: uploadProgress,
);
}
... ... @@ -168,6 +172,7 @@ class GetConnect extends GetConnectInterface {
Map<String, String> headers,
Map<String, dynamic> query,
Decoder<T> decoder,
Progress uploadProgress,
}) {
_checkIfDisposed();
return httpClient.request<T>(
... ... @@ -178,6 +183,7 @@ class GetConnect extends GetConnectInterface {
contentType: contentType,
query: query,
decoder: decoder,
uploadProgress: uploadProgress,
);
}
... ...
import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import '../src/certificates/certificates.dart';
import '../src/exceptions/exceptions.dart';
import '../src/http_impl/http_request_stub.dart'
if (dart.library.html) 'http_impl/http_request_html.dart'
if (dart.library.io) 'http_impl/http_request_io.dart';
import '../src/http_impl/request_base.dart';
import '../src/multipart/form_data.dart';
import '../src/request/request.dart';
import '../src/response/response.dart';
import '../src/status/http_status.dart';
import 'http/interface/request_base.dart';
import 'http/stub/http_request_stub.dart'
if (dart.library.html) 'http/html/http_request_html.dart'
if (dart.library.io) 'http/io/http_request_io.dart';
import 'interceptors/get_modifiers.dart';
typedef Decoder<T> = T Function(dynamic data);
typedef Progress = Function(double percent);
class GetHttpClient {
String userAgent;
String baseUrl;
... ... @@ -90,9 +91,10 @@ class GetHttpClient {
String method,
Map<String, dynamic> query,
Decoder<T> decoder,
Progress uploadProgress,
) async {
List<int> bodyBytes;
BodyBytes bodyStream;
BodyBytesStream bodyStream;
final headers = <String, String>{};
headers['user-agent'] = userAgent;
... ... @@ -125,11 +127,10 @@ class GetHttpClient {
}
if (bodyBytes != null) {
bodyStream = BodyBytes.fromBytes(bodyBytes);
bodyStream = _trackProgress(bodyBytes, uploadProgress);
}
final uri = _createUri(url, query);
return Request<T>(
method: method,
url: uri,
... ... @@ -141,6 +142,27 @@ class GetHttpClient {
);
}
BodyBytesStream _trackProgress(
List<int> bodyBytes,
Progress uploadProgress,
) {
var total = 0;
var length = bodyBytes.length;
var byteStream =
Stream.fromIterable(bodyBytes.map((i) => [i])).transform<List<int>>(
StreamTransformer.fromHandlers(handleData: (data, sink) {
total += data.length;
if (uploadProgress != null) {
var percent = total / length * 100;
uploadProgress(percent);
}
sink.add(data);
}),
);
return BodyBytesStream(byteStream);
}
void _setSimpleHeaders(
Map<String, String> headers,
String contentType,
... ... @@ -187,6 +209,8 @@ class GetHttpClient {
headers: response.headers,
statusCode: response.statusCode,
body: response.body,
bodyBytes: response.bodyBytes,
bodyString: response.bodyString,
statusText: response.statusText,
);
}
... ... @@ -232,6 +256,7 @@ class GetHttpClient {
@required dynamic body,
Map<String, dynamic> query,
Decoder<T> decoder,
@required Progress uploadProgress,
}) {
return _requestWithBody<T>(
url,
... ... @@ -240,6 +265,7 @@ class GetHttpClient {
'post',
query,
decoder ?? (defaultDecoder as Decoder<T>),
uploadProgress,
);
}
... ... @@ -250,6 +276,7 @@ class GetHttpClient {
@required dynamic body,
@required Map<String, dynamic> query,
Decoder<T> decoder,
@required Progress uploadProgress,
}) {
return _requestWithBody<T>(
url,
... ... @@ -258,6 +285,7 @@ class GetHttpClient {
method,
query,
decoder ?? (defaultDecoder as Decoder<T>),
uploadProgress,
);
}
... ... @@ -267,6 +295,7 @@ class GetHttpClient {
@required dynamic body,
@required Map<String, dynamic> query,
Decoder<T> decoder,
@required Progress uploadProgress,
}) {
return _requestWithBody<T>(
url,
... ... @@ -275,6 +304,7 @@ class GetHttpClient {
'put',
query,
decoder ?? (defaultDecoder as Decoder<T>),
uploadProgress,
);
}
... ... @@ -303,6 +333,7 @@ class GetHttpClient {
Map<String, String> headers,
Map<String, dynamic> query,
Decoder<T> decoder,
Progress uploadProgress,
// List<MultipartFile> files,
}) async {
try {
... ... @@ -313,7 +344,7 @@ class GetHttpClient {
body: body,
query: query,
decoder: decoder,
// files: files,
uploadProgress: uploadProgress,
),
headers: headers,
);
... ... @@ -323,9 +354,6 @@ class GetHttpClient {
throw GetHttpException(e.toString());
}
return Future.value(Response<T>(
request: null,
statusCode: null,
body: null,
statusText: 'Can not connect to server. Reason: $e',
));
}
... ... @@ -339,6 +367,7 @@ class GetHttpClient {
Map<String, String> headers,
Map<String, dynamic> query,
Decoder<T> decoder,
Progress uploadProgress,
}) async {
try {
var response = await _performRequest<T>(
... ... @@ -349,6 +378,7 @@ class GetHttpClient {
query: query,
body: body,
decoder: decoder,
uploadProgress: uploadProgress,
),
headers: headers,
);
... ... @@ -358,9 +388,6 @@ class GetHttpClient {
throw GetHttpException(e.toString());
}
return Future.value(Response<T>(
request: null,
statusCode: null,
body: null,
statusText: 'Can not connect to server. Reason: $e',
));
}
... ... @@ -373,6 +400,7 @@ class GetHttpClient {
Map<String, String> headers,
Map<String, dynamic> query,
Decoder<T> decoder,
Progress uploadProgress,
}) async {
try {
var response = await _performRequest<T>(
... ... @@ -382,6 +410,7 @@ class GetHttpClient {
query: query,
body: body,
decoder: decoder,
uploadProgress: uploadProgress,
),
headers: headers,
);
... ... @@ -391,9 +420,6 @@ class GetHttpClient {
throw GetHttpException(e.toString());
}
return Future.value(Response<T>(
request: null,
statusCode: null,
body: null,
statusText: 'Can not connect to server. Reason: $e',
));
}
... ... @@ -417,14 +443,72 @@ class GetHttpClient {
throw GetHttpException(e.toString());
}
return Future.value(Response<T>(
request: null,
statusCode: null,
body: null,
statusText: 'Can not connect to server. Reason: $e',
));
}
}
// Future<Response<T>> download<T>(
// String url,
// String path, {
// Map<String, String> headers,
// String contentType = 'application/octet-stream',
// Map<String, dynamic> query,
// }) async {
// try {
// var response = await _performRequest<T>(
// () => _get<T>(url, contentType, query, null),
// headers: headers,
// );
// response.bodyBytes.listen((value) {});
// return response;
// } on Exception catch (e) {
// if (!errorSafety) {
// throw GetHttpException(e.toString());
// }
// return Future.value(Response<T>(
// statusText: 'Can not connect to server. Reason: $e',
// ));
// }
// int byteCount = 0;
// int totalBytes = httpResponse.contentLength;
// Directory appDocDir = await getApplicationDocumentsDirectory();
// String appDocPath = appDocDir.path;
// File file = File(path);
// var raf = file.openSync(mode: FileMode.write);
// Completer completer = Completer<String>();
// httpResponse.listen(
// (data) {
// byteCount += data.length;
// raf.writeFromSync(data);
// if (onDownloadProgress != null) {
// onDownloadProgress(byteCount, totalBytes);
// }
// },
// onDone: () {
// raf.closeSync();
// completer.complete(file.path);
// },
// onError: (e) {
// raf.closeSync();
// file.deleteSync();
// completer.completeError(e);
// },
// cancelOnError: true,
// );
// return completer.future;
// }
Future<Response<T>> delete<T>(
String url, {
Map<String, String> headers,
... ... @@ -443,9 +527,6 @@ class GetHttpClient {
throw GetHttpException(e.toString());
}
return Future.value(Response<T>(
request: null,
statusCode: null,
body: null,
statusText: 'Can not connect to server. Reason: $e',
));
}
... ...
// import 'dart:html' as html;
List<int> fileToBytes(dynamic data) {
if (data is List<int>) {
return data;
} else {
throw FormatException('File is not [File] or [String] or [List<int>]');
}
}
// void writeOnFile(List<int> bytes) {
// var blob = html.Blob(["data"], 'text/plain', 'native');
// var anchorElement = html.AnchorElement(
// href: html.Url.createObjectUrlFromBlob(blob).toString(),
// )
// ..setAttribute("download", "data.txt")
// ..click();
// }
... ...
import 'dart:async';
import 'dart:html' as html;
import 'dart:typed_data';
import '../certificates/certificates.dart';
import '../exceptions/exceptions.dart';
import '../request/request.dart';
import '../response/response.dart';
import 'body_decoder.dart';
import 'request_base.dart';
import '../../certificates/certificates.dart';
import '../../exceptions/exceptions.dart';
import '../../request/request.dart';
import '../../response/response.dart';
import '../interface/request_base.dart';
import '../utils/body_decoder.dart';
/// A `dart:html` implementation of `HttpRequestBase`.
class HttpRequestImpl implements HttpRequestBase {
... ... @@ -44,12 +45,24 @@ class HttpRequestImpl implements HttpRequestBase {
var reader = html.FileReader();
reader.onLoad.first.then((_) async {
var bodyBytes = BodyBytes.fromBytes(reader.result as Uint8List);
var bodyBytes = BodyBytesStream.fromBytes(reader.result as Uint8List);
final stringBody =
await bodyBytesToString(bodyBytes, xhr.responseHeaders);
final body = bodyDecoded<T>(request, stringBody);
String contentType;
if (xhr.responseHeaders.containsKey('content-type')) {
contentType = xhr.responseHeaders['content-type'];
} else {
contentType = 'application/json';
}
// xhr.responseHeaders.containsKey(key)
final body = bodyDecoded<T>(
request,
stringBody,
contentType,
);
final response = Response<T>(
bodyBytes: bodyBytes,
... ... @@ -58,6 +71,7 @@ class HttpRequestImpl implements HttpRequestBase {
headers: xhr.responseHeaders,
statusText: xhr.statusText,
body: body,
bodyString: stringBody,
);
completer.complete(response);
});
... ...
import '../request/request.dart';
import '../response/response.dart';
import '../../request/request.dart';
import '../../response/response.dart';
/// Abstract interface of [HttpRequestImpl].
abstract class HttpRequestBase {
... ...
import 'dart:io';
List<int> fileToBytes(dynamic data) {
if (data is File) {
return data.readAsBytesSync();
} else if (data is String) {
if (File(data).existsSync()) {
return File(data).readAsBytesSync();
} else {
throw 'File [data] not exists';
}
} else if (data is List<int>) {
return data;
} else {
throw FormatException('File is not [File] or [String] or [List<int>]');
}
}
void writeOnFile(List<int> bytes) {}
... ...
import 'dart:async';
import 'dart:io' as io;
import '../certificates/certificates.dart';
import '../exceptions/exceptions.dart';
import '../request/request.dart';
import '../response/response.dart';
import 'body_decoder.dart';
import 'request_base.dart';
import '../../certificates/certificates.dart';
import '../../exceptions/exceptions.dart';
import '../../request/request.dart';
import '../../response/response.dart';
import '../interface/request_base.dart';
import '../utils/body_decoder.dart';
/// A `dart:io` implementation of `HttpRequestBase`.
class HttpRequestImpl extends HttpRequestBase {
... ... @@ -32,7 +33,7 @@ class HttpRequestImpl extends HttpRequestBase {
@override
Future<Response<T>> send<T>(Request<T> request) async {
var requestBody = await request.bodyBytes.toBytes();
var stream = BodyBytes.fromBytes(requestBody ?? const []);
var stream = BodyBytesStream.fromBytes(requestBody ?? const []);
try {
var ioRequest = (await _httpClient.openUrl(request.method, request.url))
... ... @@ -42,6 +43,12 @@ class HttpRequestImpl extends HttpRequestBase {
..contentLength = requestBody.length ?? -1;
request.headers.forEach(ioRequest.headers.set);
// var response = await stream.map((s) {
// received += s.length;
// print("${(received / total) * 100} %");
// return s;
// }).pipe(ioRequest) as io.HttpClientResponse;
var response = await stream.pipe(ioRequest) as io.HttpClientResponse;
var headers = <String, String>{};
... ... @@ -49,10 +56,16 @@ class HttpRequestImpl extends HttpRequestBase {
headers[key] = values.join(',');
});
final bodyBytes = BodyBytes(response);
final bodyBytes = BodyBytesStream(response);
final stringBody = await bodyBytesToString(bodyBytes, headers);
final body = bodyDecoded<T>(request, stringBody);
// response.headers.contentType.mimeType == 'application/json'
final body = bodyDecoded<T>(
request,
stringBody,
response.headers.contentType.mimeType,
);
return Response(
headers: headers,
... ... @@ -61,6 +74,7 @@ class HttpRequestImpl extends HttpRequestBase {
statusText: response.reasonPhrase,
bodyBytes: bodyBytes,
body: body,
bodyString: stringBody,
);
} on io.HttpException catch (error) {
throw GetHttpException(error.message, error.uri);
... ... @@ -77,8 +91,8 @@ class HttpRequestImpl extends HttpRequestBase {
}
}
extension FileExt on io.FileSystemEntity {
String get fileName {
return this?.path?.split(io.Platform.pathSeparator)?.last;
}
}
// extension FileExt on io.FileSystemEntity {
// String get fileName {
// return this?.path?.split(io.Platform.pathSeparator)?.last;
// }
// }
... ...
void writeOnFile(List<int> bytes) {}
List<int> fileToBytes(dynamic data) {
throw UnimplementedError();
}
... ...
import '../certificates/certificates.dart';
import '../request/request.dart';
import '../response/response.dart';
import 'request_base.dart';
import '../../certificates/certificates.dart';
import '../../request/request.dart';
import '../../response/response.dart';
import '../interface/request_base.dart';
class HttpRequestImpl extends HttpRequestBase {
HttpRequestImpl({
... ...
import 'dart:convert';
import '../../../../get_core/get_core.dart';
import '../../../../../get_core/get_core.dart';
import '../request/request.dart';
import '../../request/request.dart';
T bodyDecoded<T>(Request<T> request, String stringBody) {
T bodyDecoded<T>(Request<T> request, String stringBody, String mimeType) {
T body;
var bodyToDecode;
if (mimeType.contains('application/json')) {
try {
bodyToDecode = jsonDecode(stringBody);
} on FormatException catch (_) {
Get.log('Cannot decode body in json');
Get.log('Cannot decode server response to json');
bodyToDecode = stringBody;
}
}
try {
if (request.decoder == null) {
... ...
... ... @@ -89,7 +89,7 @@ class FormData {
}
Future<List<int>> toBytes() {
return BodyBytes(_encode()).toBytes();
return BodyBytesStream(_encode()).toBytes();
}
Stream<List<int>> _encode() async* {
... ...
import 'package:flutter/foundation.dart';
import '../http/stub/file_decoder_stub.dart'
if (dart.library.html) '../http/html/file_decoder_html.dart'
if (dart.library.io) '../http/io/file_decoder_io.dart';
import '../request/request.dart';
class MultipartFile {
MultipartFile(
List<int> bytes, {
dynamic data, {
@required this.filename,
this.contentType = 'application/octet-stream',
}) : length = bytes.length,
stream = BodyBytes.fromBytes(bytes);
}) : _bytes = fileToBytes(data) {
_length = _bytes.length;
_stream = BodyBytesStream.fromBytes(_bytes);
}
final List<int> _bytes;
final String contentType;
/// This stream will emit the file content of File.
final BodyBytes stream;
BodyBytesStream _stream;
int _length;
BodyBytesStream get stream => _stream;
final int length;
int get length => _length;
final String filename;
}
... ...
... ... @@ -20,8 +20,8 @@ class Request<T> {
/// ex: `GET`,`POST`,`PUT`,`DELETE`
final String method;
/// The BodyBytes of body from this [Request]
final BodyBytes bodyBytes;
/// The BodyBytesStream of body from this [Request]
final BodyBytesStream bodyBytes;
/// When true, the client will follow redirects to resolves this [Request]
final bool followRedirects;
... ... @@ -49,7 +49,7 @@ class Request<T> {
@required Uri url,
@required String method,
@required Map<String, String> headers,
BodyBytes bodyBytes,
BodyBytesStream bodyBytes,
bool followRedirects = true,
int maxRedirects = 4,
FormData files,
... ... @@ -66,7 +66,7 @@ class Request<T> {
return Request._(
url: url,
method: method,
bodyBytes: bodyBytes ??= BodyBytes.fromBytes(const []),
bodyBytes: bodyBytes ??= BodyBytesStream.fromBytes(const []),
headers: Map.from(headers ??= <String, String>{}),
followRedirects: followRedirects,
maxRedirects: maxRedirects,
... ... @@ -77,11 +77,11 @@ class Request<T> {
}
}
class BodyBytes extends StreamView<List<int>> {
BodyBytes(Stream<List<int>> stream) : super(stream);
class BodyBytesStream extends StreamView<List<int>> {
BodyBytesStream(Stream<List<int>> stream) : super(stream);
factory BodyBytes.fromBytes(List<int> bytes) =>
BodyBytes(Stream.fromIterable([bytes]));
factory BodyBytesStream.fromBytes(List<int> bytes) =>
BodyBytesStream(Stream.fromIterable([bytes]));
Future<Uint8List> toBytes() {
var completer = Completer<Uint8List>();
... ...
import 'dart:collection';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import '../request/request.dart';
import '../status/http_status.dart';
class Response<T> {
const Response({
@required this.request,
@required this.statusCode,
// ignore: always_require_non_null_named_parameters
this.request,
this.statusCode,
this.bodyBytes,
this.bodyString,
this.statusText = '',
this.headers = const {},
@required this.body,
this.body,
});
/// The Http [Request] linked with this [Response].
... ... @@ -46,7 +43,10 @@ class Response<T> {
bool get unauthorized => status.isUnauthorized;
/// The response body as a Stream of Bytes.
final BodyBytes bodyBytes;
final BodyBytesStream bodyBytes;
/// The response body as a Stream of Bytes.
final String bodyString;
/// The decoded body of this [Response]. You can access the
/// body parameters as Map
... ... @@ -55,7 +55,7 @@ class Response<T> {
}
Future<String> bodyBytesToString(
BodyBytes bodyBytes, Map<String, String> headers) {
BodyBytesStream bodyBytes, Map<String, String> headers) {
return bodyBytes.bytesToString(_encodingForHeaders(headers));
}
... ...
... ... @@ -52,9 +52,9 @@ String validateField(String field) {
return field.toLowerCase();
}
BodyBytes toBodyBytes(Stream<List<int>> stream) {
if (stream is BodyBytes) return stream;
return BodyBytes(stream);
BodyBytesStream toBodyBytesStream(Stream<List<int>> stream) {
if (stream is BodyBytesStream) return stream;
return BodyBytesStream(stream);
}
final _asciiOnly = RegExp(r'^[\x00-\x7F]+$');
... ...
name: get
description: Open screens/snackbars/dialogs/bottomSheets without context, manage states and inject dependencies easily with GetX.
version: 3.21.3
version: 3.22.0
homepage: https://github.com/jonataslaw/getx
environment:
... ...