Showing
10 changed files
with
208 additions
and
30 deletions
1 | -## [3.20.0] | ||
2 | -- Added GetConnect. | ||
3 | -- | ||
4 | - | 1 | +## [3.20.0] - Big update |
2 | +* Added GetConnect. | ||
3 | +- GetConnect is an easy way to communicate from your back to your front. With it you can: | ||
4 | +- Communicate through websockets | ||
5 | +- Send messages and events via websockets. | ||
6 | +- Listen to messages and events via websockets. | ||
7 | +- Make http requests (GET, PUT, POST, DELETE). | ||
8 | +- Add request modifiers (like attaching a token to each request made). | ||
9 | +- Add answer modifiers (how to change a value field whenever the answer arrives) | ||
10 | +- Add an authenticator, if the answer is 401, you can configure the renewal of your JWT, for example, and then it will again make the http request. | ||
11 | +- Set the number of attempts for the authenticator | ||
12 | +- Define a baseUrl for all requests | ||
13 | +- Define a standard encoder for your Model. | ||
14 | +- Note1: You will never need to use jsonEncoder. It will always be called automatically with each request. If you define an encoder for your model, it will return the instance of your model class ALREADY FILLED with server data. | ||
15 | +- Note2: all requests are safety, you do not need to insert try / catch in requests. It will always return a response. In case of an error code, Response.hasError will return true. The error code will always be returned, unless the error was a connection error, which will be returned Response.hasError, but with error code null. | ||
16 | +- These are relatively new features, and also inserted in separate containers. You don't have to use it if you don't want to. As it is relatively new, some functions, such as specific http methods, may be missing. | ||
17 | +* Translation to Korean (@rws08) | ||
18 | +* Fix Overlays state (@eduardoflorence) | ||
19 | +* Update chinese docs (@jonahzheng) | ||
20 | +* Added context.isDarkMode to context extensions | ||
21 | + | ||
5 | 22 | ||
6 | ## [3.17.1] | 23 | ## [3.17.1] |
7 | - Allow list.assignAll, map.assignAll and set.assignAll operate with null values | 24 | - Allow list.assignAll, map.assignAll and set.assignAll operate with null values |
1 |  | 1 |  |
2 | 2 | ||
3 | -_Languages: English (this file), [Chinese](README.zh-cn.md), [Brazilian Portuguese](README.pt-br.md), [Spanish](README-es.md), [Russian](README.ru.md), [Polish](README.pl.md), [Korean](README.ko-kr.md)._ | 3 | +**Languages: English (this file), [Chinese](README.zh-cn.md), [Brazilian Portuguese](README.pt-br.md), [Spanish](README-es.md), [Russian](README.ru.md), [Polish](README.pl.md), [Korean](README.ko-kr.md).** |
4 | 4 | ||
5 | [](https://pub.dev/packages/get) | 5 | [](https://pub.dev/packages/get) |
6 | [](https://pub.dev/packages/get/score) | 6 | [](https://pub.dev/packages/get/score) |
@@ -35,6 +35,9 @@ _Languages: English (this file), [Chinese](README.zh-cn.md), [Brazilian Portugue | @@ -35,6 +35,9 @@ _Languages: English (this file), [Chinese](README.zh-cn.md), [Brazilian Portugue | ||
35 | - [Change locale](#change-locale) | 35 | - [Change locale](#change-locale) |
36 | - [System locale](#system-locale) | 36 | - [System locale](#system-locale) |
37 | - [Change Theme](#change-theme) | 37 | - [Change Theme](#change-theme) |
38 | + - [GetConnect](#getconnect) | ||
39 | + - [Default configuration](#default-configuration) | ||
40 | + - [Custom configuration](#custom-configuration) | ||
38 | - [Other Advanced APIs](#other-advanced-apis) | 41 | - [Other Advanced APIs](#other-advanced-apis) |
39 | - [Optional Global Settings and Manual configurations](#optional-global-settings-and-manual-configurations) | 42 | - [Optional Global Settings and Manual configurations](#optional-global-settings-and-manual-configurations) |
40 | - [Local State Widgets](#local-state-widgets) | 43 | - [Local State Widgets](#local-state-widgets) |
@@ -380,6 +383,83 @@ Get.changeTheme(Get.isDarkMode? ThemeData.light(): ThemeData.dark()); | @@ -380,6 +383,83 @@ Get.changeTheme(Get.isDarkMode? ThemeData.light(): ThemeData.dark()); | ||
380 | 383 | ||
381 | When `.darkmode` is activated, it will switch to the _light theme_, and when the _light theme_ becomes active, it will change to _dark theme_. | 384 | When `.darkmode` is activated, it will switch to the _light theme_, and when the _light theme_ becomes active, it will change to _dark theme_. |
382 | 385 | ||
386 | +## GetConnect | ||
387 | +GetConnect is an easy way to communicate from your back to your front with http or websockets | ||
388 | + | ||
389 | +### Default configuration | ||
390 | +You can simply extend GetConnect and use the GET/POST/PUT/DELETE/SOCKET methods to communicate with your Rest API or websockets. | ||
391 | + | ||
392 | +```dart | ||
393 | +class UserProvider extends GetConnect { | ||
394 | + // Get request | ||
395 | + Future<Response> getUser(int id) => get('http://youapi/users/$id'); | ||
396 | + // Post request | ||
397 | + Future<Response> postUser(Map data) => post('http://youapi/users', body: data); | ||
398 | + // Post request with File | ||
399 | + Future<Response<CasesModel>> postCases(List<int> image) { | ||
400 | + final form = FormData({ | ||
401 | + 'file': MultipartFile(image, filename: 'avatar.png'), | ||
402 | + 'otherFile': MultipartFile(image, filename: 'cover.png'), | ||
403 | + }); | ||
404 | + return post('http://youapi/users/upload', form); | ||
405 | + } | ||
406 | + | ||
407 | + GetSocket userMessages() { | ||
408 | + return socket('https://yourapi/users/socket'); | ||
409 | + } | ||
410 | +} | ||
411 | +``` | ||
412 | +### Custom configuration | ||
413 | +GetConnect is highly customizable You can define base Url, as answer modifiers, as Requests modifiers, define an authenticator, and even the number of attempts in which it will try to authenticate itself, in addition to giving the possibility to define a standard decoder that will transform all your requests into your Models without any additional configuration. | ||
414 | + | ||
415 | +```dart | ||
416 | +class HomeProvider extends GetConnect { | ||
417 | + @override | ||
418 | + void onInit() { | ||
419 | + @override | ||
420 | + void onInit() { | ||
421 | + // All request will pass to jsonEncode so CasesModel.fromJson() | ||
422 | + httpClient.defaultDecoder = CasesModel.fromJson; | ||
423 | + httpClient.baseUrl = 'https://api.covid19api.com'; | ||
424 | + // baseUrl = 'https://api.covid19api.com'; // It define baseUrl to | ||
425 | + // Http and websockets if used with no [httpClient] instance | ||
426 | + | ||
427 | + // It's will attach 'apikey' property on header from all requests | ||
428 | + httpClient.addRequestModifier((request) { | ||
429 | + request.headers['apikey'] = '12345678'; | ||
430 | + return request; | ||
431 | + }); | ||
432 | + | ||
433 | + // Even if the server sends data from the country "Brazil", | ||
434 | + // it will never be displayed to users, because you remove | ||
435 | + // that data from the response, even before the response is delivered | ||
436 | + httpClient.addResponseModifier<CasesModel>((request, response) { | ||
437 | + CasesModel model = response.body; | ||
438 | + if (model.countries.contains('Brazil')) { | ||
439 | + model.countries.remove('Brazilll'); | ||
440 | + } | ||
441 | + }); | ||
442 | + | ||
443 | + httpClient.addAuthenticator((request) async { | ||
444 | + final response = await get("http://yourapi/token"); | ||
445 | + final token = response.body['token']; | ||
446 | + // Set the header | ||
447 | + request.headers['Authorization'] = "$token"; | ||
448 | + return request; | ||
449 | + }); | ||
450 | + | ||
451 | + //Autenticator will be called 3 times if HttpStatus is | ||
452 | + //HttpStatus.unauthorized | ||
453 | + httpClient.maxAuthRetries = 3; | ||
454 | + } | ||
455 | + } | ||
456 | + | ||
457 | + @override | ||
458 | + Future<Response<CasesModel>> getCases(String path) => get(path); | ||
459 | +} | ||
460 | +``` | ||
461 | + | ||
462 | + | ||
383 | ## Other Advanced APIs | 463 | ## Other Advanced APIs |
384 | 464 | ||
385 | ```dart | 465 | ```dart |
@@ -734,7 +814,7 @@ Is a `const Stateless` Widget that has a getter `controller` for a registered `C | @@ -734,7 +814,7 @@ Is a `const Stateless` Widget that has a getter `controller` for a registered `C | ||
734 | Widget build(BuildContext context) { | 814 | Widget build(BuildContext context) { |
735 | return Container( | 815 | return Container( |
736 | padding: EdgeInsets.all(20), | 816 | padding: EdgeInsets.all(20), |
737 | - child: Text( controller.title ), // just call `controller.something` | 817 | + child: Text(controller.title), // just call `controller.something` |
738 | ); | 818 | ); |
739 | } | 819 | } |
740 | } | 820 | } |
1 | import 'dart:async'; | 1 | import 'dart:async'; |
2 | import 'dart:convert'; | 2 | import 'dart:convert'; |
3 | 3 | ||
4 | -import 'package:meta/meta.dart'; | 4 | +import 'package:flutter/foundation.dart'; |
5 | 5 | ||
6 | import '../src/certificates/certificates.dart'; | 6 | import '../src/certificates/certificates.dart'; |
7 | import '../src/exceptions/exceptions.dart'; | 7 | import '../src/exceptions/exceptions.dart'; |
@@ -31,9 +31,11 @@ class GetHttpClient { | @@ -31,9 +31,11 @@ class GetHttpClient { | ||
31 | 31 | ||
32 | Duration timeout; | 32 | Duration timeout; |
33 | 33 | ||
34 | + bool errorSafety = true; | ||
35 | + | ||
34 | final HttpRequestBase _httpClient; | 36 | final HttpRequestBase _httpClient; |
35 | 37 | ||
36 | - final GetModifier _interceptor; | 38 | + final GetModifier _modifier; |
37 | 39 | ||
38 | GetHttpClient({ | 40 | GetHttpClient({ |
39 | this.userAgent = 'getx-client', | 41 | this.userAgent = 'getx-client', |
@@ -48,7 +50,27 @@ class GetHttpClient { | @@ -48,7 +50,27 @@ class GetHttpClient { | ||
48 | allowAutoSignedCert: allowAutoSignedCert, | 50 | allowAutoSignedCert: allowAutoSignedCert, |
49 | trustedCertificates: trustedCertificates, | 51 | trustedCertificates: trustedCertificates, |
50 | ), | 52 | ), |
51 | - _interceptor = GetModifier(); | 53 | + _modifier = GetModifier(); |
54 | + | ||
55 | + void addAuthenticator<T>(RequestModifier<T> auth) { | ||
56 | + _modifier.authenticator = auth as RequestModifier; | ||
57 | + } | ||
58 | + | ||
59 | + void addRequestModifier<T>(RequestModifier<T> interceptor) { | ||
60 | + _modifier.addRequestModifier<T>(interceptor); | ||
61 | + } | ||
62 | + | ||
63 | + void removeRequestModifier<T>(RequestModifier<T> interceptor) { | ||
64 | + _modifier.removeRequestModifier(interceptor); | ||
65 | + } | ||
66 | + | ||
67 | + void addResponseModifier<T>(ResponseModifier<T> interceptor) { | ||
68 | + _modifier.addResponseModifier(interceptor); | ||
69 | + } | ||
70 | + | ||
71 | + void removeResponseModifier<T>(ResponseModifier<T> interceptor) { | ||
72 | + _modifier.removeResponseModifier<T>(interceptor); | ||
73 | + } | ||
52 | 74 | ||
53 | Uri _createUri(String url, Map<String, dynamic> query) { | 75 | Uri _createUri(String url, Map<String, dynamic> query) { |
54 | if (baseUrl != null) { | 76 | if (baseUrl != null) { |
@@ -91,7 +113,9 @@ class GetHttpClient { | @@ -91,7 +113,9 @@ class GetHttpClient { | ||
91 | 113 | ||
92 | bodyBytes = utf8.encode(jsonString); | 114 | bodyBytes = utf8.encode(jsonString); |
93 | } on Exception catch (err) { | 115 | } on Exception catch (err) { |
94 | - throw UnexpectedFormat(err.toString()); | 116 | + if (!errorSafety) { |
117 | + throw UnexpectedFormat(err.toString()); | ||
118 | + } else {} | ||
95 | } | 119 | } |
96 | } | 120 | } |
97 | 121 | ||
@@ -155,15 +179,15 @@ class GetHttpClient { | @@ -155,15 +179,15 @@ class GetHttpClient { | ||
155 | request.headers[key] = value; | 179 | request.headers[key] = value; |
156 | }); | 180 | }); |
157 | 181 | ||
158 | - if (authenticate) await _interceptor.authenticator(request); | ||
159 | - await _interceptor.modifyRequest(request); | 182 | + if (authenticate) await _modifier.authenticator(request); |
183 | + await _modifier.modifyRequest(request); | ||
160 | 184 | ||
161 | var response = await _httpClient.send<T>(request); | 185 | var response = await _httpClient.send<T>(request); |
162 | 186 | ||
163 | - await _interceptor.modifyResponse(request, response); | 187 | + await _modifier.modifyResponse(request, response); |
164 | 188 | ||
165 | if (HttpStatus.unauthorized == response.statusCode && | 189 | if (HttpStatus.unauthorized == response.statusCode && |
166 | - _interceptor.authenticator != null && | 190 | + _modifier.authenticator != null && |
167 | requestNumber <= maxAuthRetries) { | 191 | requestNumber <= maxAuthRetries) { |
168 | return _performRequest( | 192 | return _performRequest( |
169 | handler, | 193 | handler, |
@@ -172,12 +196,32 @@ class GetHttpClient { | @@ -172,12 +196,32 @@ class GetHttpClient { | ||
172 | headers: request.headers, | 196 | headers: request.headers, |
173 | ); | 197 | ); |
174 | } else if (HttpStatus.unauthorized == response.statusCode) { | 198 | } else if (HttpStatus.unauthorized == response.statusCode) { |
175 | - throw UnauthorizedException(); | 199 | + if (!errorSafety) { |
200 | + throw UnauthorizedException(); | ||
201 | + } else { | ||
202 | + return Response<T>( | ||
203 | + request: request, | ||
204 | + headers: response.headers, | ||
205 | + statusCode: response.statusCode, | ||
206 | + body: response.body, | ||
207 | + statusText: response.statusText, | ||
208 | + ); | ||
209 | + } | ||
176 | } | 210 | } |
177 | 211 | ||
178 | return response; | 212 | return response; |
179 | } on Exception catch (err) { | 213 | } on Exception catch (err) { |
180 | - throw GetHttpException(err.toString()); | 214 | + if (!errorSafety) { |
215 | + throw GetHttpException(err.toString()); | ||
216 | + } else { | ||
217 | + return Response<T>( | ||
218 | + request: null, | ||
219 | + headers: null, | ||
220 | + statusCode: null, | ||
221 | + body: null, | ||
222 | + statusText: "$err", | ||
223 | + ); | ||
224 | + } | ||
181 | } | 225 | } |
182 | } | 226 | } |
183 | 227 | ||
@@ -267,6 +311,9 @@ class GetHttpClient { | @@ -267,6 +311,9 @@ class GetHttpClient { | ||
267 | ); | 311 | ); |
268 | return response; | 312 | return response; |
269 | } on Exception catch (e) { | 313 | } on Exception catch (e) { |
314 | + if (!errorSafety) { | ||
315 | + throw GetHttpException(e.toString()); | ||
316 | + } | ||
270 | return Future.value(Response<T>( | 317 | return Future.value(Response<T>( |
271 | request: null, | 318 | request: null, |
272 | statusCode: null, | 319 | statusCode: null, |
@@ -297,6 +344,9 @@ class GetHttpClient { | @@ -297,6 +344,9 @@ class GetHttpClient { | ||
297 | ); | 344 | ); |
298 | return response; | 345 | return response; |
299 | } on Exception catch (e) { | 346 | } on Exception catch (e) { |
347 | + if (!errorSafety) { | ||
348 | + throw GetHttpException(e.toString()); | ||
349 | + } | ||
300 | return Future.value(Response<T>( | 350 | return Future.value(Response<T>( |
301 | request: null, | 351 | request: null, |
302 | statusCode: null, | 352 | statusCode: null, |
@@ -320,6 +370,9 @@ class GetHttpClient { | @@ -320,6 +370,9 @@ class GetHttpClient { | ||
320 | ); | 370 | ); |
321 | return response; | 371 | return response; |
322 | } on Exception catch (e) { | 372 | } on Exception catch (e) { |
373 | + if (!errorSafety) { | ||
374 | + throw GetHttpException(e.toString()); | ||
375 | + } | ||
323 | return Future.value(Response<T>( | 376 | return Future.value(Response<T>( |
324 | request: null, | 377 | request: null, |
325 | statusCode: null, | 378 | statusCode: null, |
@@ -343,6 +396,9 @@ class GetHttpClient { | @@ -343,6 +396,9 @@ class GetHttpClient { | ||
343 | ); | 396 | ); |
344 | return response; | 397 | return response; |
345 | } on Exception catch (e) { | 398 | } on Exception catch (e) { |
399 | + if (!errorSafety) { | ||
400 | + throw GetHttpException(e.toString()); | ||
401 | + } | ||
346 | return Future.value(Response<T>( | 402 | return Future.value(Response<T>( |
347 | request: null, | 403 | request: null, |
348 | statusCode: null, | 404 | statusCode: null, |
1 | +import 'dart:async'; | ||
2 | + | ||
1 | import '../request/request.dart'; | 3 | import '../request/request.dart'; |
2 | import '../response/response.dart'; | 4 | import '../response/response.dart'; |
3 | 5 | ||
4 | -typedef RequestModifier = Future<Request> Function(Request request); | 6 | +typedef RequestModifier<T> = FutureOr<Request<T>> Function(Request<T> request); |
5 | 7 | ||
6 | -typedef ResponseModifier = Future<Null> Function( | ||
7 | - Request request, Response response); | 8 | +typedef ResponseModifier<T> = FutureOr Function( |
9 | + Request<T> request, Response<T> response); | ||
8 | 10 | ||
9 | typedef HandlerExecute<T> = Future<Request<T>> Function(); | 11 | typedef HandlerExecute<T> = Future<Request<T>> Function(); |
10 | 12 | ||
11 | -class GetModifier { | 13 | +class GetModifier<T> { |
12 | final _requestModifiers = <RequestModifier>[]; | 14 | final _requestModifiers = <RequestModifier>[]; |
13 | final _responseModifiers = <ResponseModifier>[]; | 15 | final _responseModifiers = <ResponseModifier>[]; |
14 | RequestModifier authenticator; | 16 | RequestModifier authenticator; |
15 | 17 | ||
16 | - void addRequestModifier(RequestModifier interceptor) { | ||
17 | - _requestModifiers.add(interceptor); | 18 | + void addRequestModifier<T>(RequestModifier<T> interceptor) { |
19 | + _requestModifiers.add(interceptor as RequestModifier); | ||
18 | } | 20 | } |
19 | 21 | ||
20 | - void removeRequestModifier(RequestModifier interceptor) { | 22 | + void removeRequestModifier<T>(RequestModifier<T> interceptor) { |
21 | _requestModifiers.remove(interceptor); | 23 | _requestModifiers.remove(interceptor); |
22 | } | 24 | } |
23 | 25 | ||
24 | - void addResponseModifier(ResponseModifier interceptor) { | ||
25 | - _responseModifiers.add(interceptor); | 26 | + void addResponseModifier<T>(ResponseModifier<T> interceptor) { |
27 | + _responseModifiers.add(interceptor as ResponseModifier); | ||
26 | } | 28 | } |
27 | 29 | ||
28 | - void removeResponseModifier(ResponseModifier interceptor) { | 30 | + void removeResponseModifier<T>(ResponseModifier<T> interceptor) { |
29 | _requestModifiers.remove(interceptor); | 31 | _requestModifiers.remove(interceptor); |
30 | } | 32 | } |
31 | 33 |
1 | +import 'package:flutter/foundation.dart'; | ||
2 | + | ||
1 | import '../request/request.dart'; | 3 | import '../request/request.dart'; |
2 | 4 | ||
3 | class MultipartFile { | 5 | class MultipartFile { |
4 | MultipartFile( | 6 | MultipartFile( |
5 | List<int> bytes, { | 7 | List<int> bytes, { |
6 | - this.filename, | 8 | + @required this.filename, |
7 | this.contentType = 'application/octet-stream', | 9 | this.contentType = 'application/octet-stream', |
8 | }) : length = bytes.length, | 10 | }) : length = bytes.length, |
9 | stream = BodyBytes.fromBytes(bytes); | 11 | stream = BodyBytes.fromBytes(bytes); |
@@ -2,7 +2,7 @@ import 'dart:async'; | @@ -2,7 +2,7 @@ import 'dart:async'; | ||
2 | import 'dart:convert'; | 2 | import 'dart:convert'; |
3 | import 'dart:typed_data'; | 3 | import 'dart:typed_data'; |
4 | 4 | ||
5 | -import 'package:meta/meta.dart'; | 5 | +import 'package:flutter/foundation.dart'; |
6 | 6 | ||
7 | import '../http.dart'; | 7 | import '../http.dart'; |
8 | import '../multipart/form_data.dart'; | 8 | import '../multipart/form_data.dart'; |
@@ -2,7 +2,6 @@ import 'dart:collection'; | @@ -2,7 +2,6 @@ import 'dart:collection'; | ||
2 | import 'dart:convert'; | 2 | import 'dart:convert'; |
3 | 3 | ||
4 | import 'package:flutter/foundation.dart'; | 4 | import 'package:flutter/foundation.dart'; |
5 | -import 'package:meta/meta.dart'; | ||
6 | 5 | ||
7 | import '../request/request.dart'; | 6 | import '../request/request.dart'; |
8 | import '../status/http_status.dart'; | 7 | import '../status/http_status.dart'; |
1 | +import 'dart:async'; | ||
2 | +import '../../../get_core/src/get_interface.dart'; | ||
3 | + | ||
4 | +extension LoopEventsExt on GetInterface { | ||
5 | + Future<T> toEnd<T>(FutureOr<T> computation()) async { | ||
6 | + await Future.delayed(Duration.zero); | ||
7 | + final val = computation(); | ||
8 | + return val; | ||
9 | + } | ||
10 | + | ||
11 | + FutureOr<T> asap<T>(T computation(), {bool Function() condition}) async { | ||
12 | + T val; | ||
13 | + if (condition == null || !condition()) { | ||
14 | + await Future.delayed(Duration.zero); | ||
15 | + val = computation(); | ||
16 | + } else { | ||
17 | + val = computation(); | ||
18 | + } | ||
19 | + return val; | ||
20 | + } | ||
21 | +} |
@@ -2,6 +2,7 @@ export 'context_extensions.dart'; | @@ -2,6 +2,7 @@ export 'context_extensions.dart'; | ||
2 | export 'double_extensions.dart'; | 2 | export 'double_extensions.dart'; |
3 | export 'duration_extensions.dart'; | 3 | export 'duration_extensions.dart'; |
4 | export 'dynamic_extensions.dart'; | 4 | export 'dynamic_extensions.dart'; |
5 | +export 'event_loop_extensions.dart'; | ||
5 | export 'internacionalization.dart'; | 6 | export 'internacionalization.dart'; |
6 | export 'num_extensions.dart'; | 7 | export 'num_extensions.dart'; |
7 | export 'string_extensions.dart'; | 8 | export 'string_extensions.dart'; |
1 | name: get | 1 | name: get |
2 | description: Open screens/snackbars/dialogs/bottomSheets without context, manage states and inject dependencies easily with GetX. | 2 | description: Open screens/snackbars/dialogs/bottomSheets without context, manage states and inject dependencies easily with GetX. |
3 | -version: 3.17.1 | 3 | +version: 3.20.0 |
4 | homepage: https://github.com/jonataslaw/getx | 4 | homepage: https://github.com/jonataslaw/getx |
5 | 5 | ||
6 | environment: | 6 | environment: |
-
Please register or login to post a comment