won

Merge commit '5d019b4f' into translate_korean

Showing 63 changed files with 3162 additions and 207 deletions
## [3.21.1]
- Allow null body to POST method on GetConnect
## [3.21.0] - Big update
- This update attaches two nice features developed by (@SchabanBo): *GetPage Children* And *GetMiddleware*
In previous versions, to create child pages, you should do something like:
```dart
GetPage(
name: '/home',
page: () => HomeView(),
binding: HomeBinding(),
),
GetPage(
name: '/home/products',
page: () => ProductsView(),
binding: ProductsBinding(),
),
GetPage(
name: '/home/products/electronics',
page: () => ElectronicsView(),
binding: ElectronicsBinding(),
),
```
Although the feature works well, it could be improved in several ways:
1- If you had many pages, the page file could become huge and difficult to read. Besides, it was difficult to know which page was the daughter of which module.
2- It was not possible to delegate the function of naming routes to a subroutine file.
With this update, it is possible to create a declarative structure, very similar to the Flutter widget tree for your route, which might look like this:
```dart
GetPage(
name: '/home',
page: () => HomeView(),
binding: HomeBinding(),
children: [
GetPage(
name: '/products',
page: () => ProductsView(),
binding: ProductsBinding(),
children: [
GetPage(
name: '/electronics',
page: () => ElectronicsView(),
binding: ElectronicsBinding(),
),
],
),
],
);
```
Thus, when accessing the url: '/home/products/electronics'
Or use Get.toNamed('/home/products/electronics') it will go directly to the page [ElectronicsView], because the child pages, automatically inherit the name of the ancestral page, so _with any small change on any father in the tree all children will be updated._ If you change [/products] to [/accessories], you don't nesse update on all child links.
However, the most powerful feature of this version is *GetMiddlewares*.
The GetPage has now new property that takes a list of GetMiddleWare than can perform actions and run them in the specific order.
### Priority
The Order of the Middlewares to run can pe set by the priority in the GetMiddleware.
```dart
final middlewares = [
GetMiddleware(priority: 2),
GetMiddleware(priority: 5),
GetMiddleware(priority: 4),
GetMiddleware(priority: -8),
];
```
those middlewares will be run in this order **-8 => 2 => 4 => 5**
### Redirect
This function will be called when the page of the called route is being searched for. It takes RouteSettings as a result to redirect to. Or give it null and there will be no redirecting.
```dart
GetPage redirect( ) {
final authService = Get.find<AuthService>();
return authService.authed.value ? null : RouteSettings(name: '/login')
}
```
### onPageCalled
This function will be called when this Page is called before anything created
you can use it to change something about the page or give it new page
```dart
GetPage onPageCalled(GetPage page) {
final authService = Get.find<AuthService>();
return page.copyWith(title: 'Welcome ${authService.UserName}');
}
```
### OnBindingsStart
This function will be called right before the Bindings are initialize.
Here you can change Bindings for this page.
```dart
List<Bindings> onBindingsStart(List<Bindings> bindings) {
final authService = Get.find<AuthService>();
if (authService.isAdmin) {
bindings.add(AdminBinding());
}
return bindings;
}
```
### OnPageBuildStart
This function will be called right after the Bindings are initialize.
Here you can do something after that you created the bindings and before creating the page widget.
```dart
GetPageBuilder onPageBuildStart(GetPageBuilder page) {
print('bindings are ready');
return page;
}
```
### OnPageBuilt
This function will be called right after the GetPage.page function is called and will give you the result of the function. and take the widget that will be showed.
### OnPageDispose
This function will be called right after disposing all the related objects (Controllers, views, ...) of the page.
## [3.20.1]
* Fix wrong reference with unnamed routes and added more tests
## [3.20.0] - Big update
* Added GetConnect.
- GetConnect is an easy way to communicate from your back to your front. With it you can:
- Communicate through websockets
- Send messages and events via websockets.
- Listen to messages and events via websockets.
- Make http requests (GET, PUT, POST, DELETE).
- Add request modifiers (like attaching a token to each request made).
- Add answer modifiers (how to change a value field whenever the answer arrives)
- 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.
- Set the number of attempts for the authenticator
- Define a baseUrl for all requests
- Define a standard encoder for your Model.
- 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.
- 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.
- 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.
* Translation to Korean (@rws08)
* Fix Overlays state (@eduardoflorence)
* Update chinese docs (@jonahzheng)
* Added context.isDarkMode to context extensions
## [3.17.1]
- Allow list.assignAll, map.assignAll and set.assignAll operate with null values
... ...
... ... @@ -125,11 +125,13 @@ class Controller extends GetxController {
```dart
class Home extends StatelessWidget {
// Cree una instancia de su clase usando Get.put() para que esté disponible para todas las rutas "secundarias" allí.
final Controller c = Get.put(Controller());
@override
Widget build(context) => Scaffold(
Widget build(context) {
// Cree una instancia de su clase usando Get.put() para que esté disponible para todas las rutas "secundarias" allí.
final Controller c = Get.put(Controller());
return Scaffold(
// Utilice Obx(()=> para actualizar Text() siempre que se cambie el recuento.
appBar: AppBar(title: Obx(() => Text("Clicks: " + c.count.string))),
... ... @@ -138,6 +140,7 @@ class Home extends StatelessWidget {
child: Text("Go to Other"), onPressed: () => Get.to(Other()))),
floatingActionButton:
FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment));
}
}
class Other extends StatelessWidget {
... ...
... ... @@ -123,11 +123,13 @@ class Controller extends GetxController{
```dart
class Home extends StatelessWidget {
// Get.put()을 사용하여 클래스를 인스턴스화하여 모든 "child'에서 사용가능하게 합니다.
final Controller c = Get.put(Controller());
@override
Widget build(context) => Scaffold(
Widget build(context) {
// Get.put()을 사용하여 클래스를 인스턴스화하여 모든 "child'에서 사용가능하게 합니다.
final Controller c = Get.put(Controller());
return Scaffold(
// count가 변경 될 때마다 Obx(()=> 를 사용하여 Text()에 업데이트합니다.
appBar: AppBar(title: Obx(() => Text("Clicks: ${c.count}"))),
... ... @@ -136,6 +138,7 @@ class Home extends StatelessWidget {
child: Text("Go to Other"), onPressed: () => Get.to(Other()))),
floatingActionButton:
FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment));
}
}
class Other extends StatelessWidget {
... ...
![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/get.png)
_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)._
**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).**
[![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get)
[![likes](https://badges.bar/get/likes)](https://pub.dev/packages/get/score)
... ... @@ -35,6 +35,17 @@ _Languages: English (this file), [Chinese](README.zh-cn.md), [Brazilian Portugue
- [Change locale](#change-locale)
- [System locale](#system-locale)
- [Change Theme](#change-theme)
- [GetConnect](#getconnect)
- [Default configuration](#default-configuration)
- [Custom configuration](#custom-configuration)
- [GetPage Middleware](#getpage-middleware)
- [Priority](#priority)
- [Redirect](#redirect)
- [onPageCalled](#onpagecalled)
- [OnBindingsStart](#onbindingsstart)
- [OnPageBuildStart](#onpagebuildstart)
- [OnPageBuilt](#onpagebuilt)
- [OnPageDispose](#onpagedispose)
- [Other Advanced APIs](#other-advanced-apis)
- [Optional Global Settings and Manual configurations](#optional-global-settings-and-manual-configurations)
- [Local State Widgets](#local-state-widgets)
... ... @@ -123,11 +134,13 @@ class Controller extends GetxController{
```dart
class Home extends StatelessWidget {
// Instantiate your class using Get.put() to make it available for all "child" routes there.
final Controller c = Get.put(Controller());
@override
Widget build(context) => Scaffold(
Widget build(context) {
// Instantiate your class using Get.put() to make it available for all "child" routes there.
final Controller c = Get.put(Controller());
return Scaffold(
// Use Obx(()=> to update Text() whenever count is changed.
appBar: AppBar(title: Obx(() => Text("Clicks: ${c.count}"))),
... ... @@ -136,6 +149,7 @@ class Home extends StatelessWidget {
child: Text("Go to Other"), onPressed: () => Get.to(Other()))),
floatingActionButton:
FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment));
}
}
class Other extends StatelessWidget {
... ... @@ -377,6 +391,158 @@ Get.changeTheme(Get.isDarkMode? ThemeData.light(): ThemeData.dark());
When `.darkmode` is activated, it will switch to the _light theme_, and when the _light theme_ becomes active, it will change to _dark theme_.
## GetConnect
GetConnect is an easy way to communicate from your back to your front with http or websockets
### Default configuration
You can simply extend GetConnect and use the GET/POST/PUT/DELETE/SOCKET methods to communicate with your Rest API or websockets.
```dart
class UserProvider extends GetConnect {
// Get request
Future<Response> getUser(int id) => get('http://youapi/users/$id');
// Post request
Future<Response> postUser(Map data) => post('http://youapi/users', body: data);
// Post request with File
Future<Response<CasesModel>> postCases(List<int> image) {
final form = FormData({
'file': MultipartFile(image, filename: 'avatar.png'),
'otherFile': MultipartFile(image, filename: 'cover.png'),
});
return post('http://youapi/users/upload', form);
}
GetSocket userMessages() {
return socket('https://yourapi/users/socket');
}
}
```
### Custom configuration
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.
```dart
class HomeProvider extends GetConnect {
@override
void onInit() {
// All request will pass to jsonEncode so CasesModel.fromJson()
httpClient.defaultDecoder = CasesModel.fromJson;
httpClient.baseUrl = 'https://api.covid19api.com';
// baseUrl = 'https://api.covid19api.com'; // It define baseUrl to
// Http and websockets if used with no [httpClient] instance
// It's will attach 'apikey' property on header from all requests
httpClient.addRequestModifier((request) {
request.headers['apikey'] = '12345678';
return request;
});
// Even if the server sends data from the country "Brazil",
// it will never be displayed to users, because you remove
// that data from the response, even before the response is delivered
httpClient.addResponseModifier<CasesModel>((request, response) {
CasesModel model = response.body;
if (model.countries.contains('Brazil')) {
model.countries.remove('Brazilll');
}
});
httpClient.addAuthenticator((request) async {
final response = await get("http://yourapi/token");
final token = response.body['token'];
// Set the header
request.headers['Authorization'] = "$token";
return request;
});
//Autenticator will be called 3 times if HttpStatus is
//HttpStatus.unauthorized
httpClient.maxAuthRetries = 3;
}
}
@override
Future<Response<CasesModel>> getCases(String path) => get(path);
}
```
## GetPage Middleware
The GetPage has now new property that takes a list of GetMiddleWare and run them in the specific order.
**Note**: When GetPage has a Middlewares, all the children of this page will have the same middlewares automatically.
### Priority
The Order of the Middlewares to run can pe set by the priority in the GetMiddleware.
```dart
final middlewares = [
GetMiddleware(priority: 2),
GetMiddleware(priority: 5),
GetMiddleware(priority: 4),
GetMiddleware(priority: -8),
];
```
those middlewares will be run in this order **-8 => 2 => 4 => 5**
### Redirect
This function will be called when the page of the called route is being searched for. It takes RouteSettings as a result to redirect to. Or give it null and there will be no redirecting.
```dart
GetPage redirect( ) {
final authService = Get.find<AuthService>();
return authService.authed.value ? null : RouteSettings(name: '/login')
}
```
### onPageCalled
This function will be called when this Page is called before anything created
you can use it to change something about the page or give it new page
```dart
GetPage onPageCalled(GetPage page) {
final authService = Get.find<AuthService>();
return page.copyWith(title: 'Welcome ${authService.UserName}');
}
```
### OnBindingsStart
This function will be called right before the Bindings are initialize.
Here you can change Bindings for this page.
```dart
List<Bindings> onBindingsStart(List<Bindings> bindings) {
final authService = Get.find<AuthService>();
if (authService.isAdmin) {
bindings.add(AdminBinding());
}
return bindings;
}
```
### OnPageBuildStart
This function will be called right after the Bindings are initialize.
Here you can do something after that you created the bindings and before creating the page widget.
```dart
GetPageBuilder onPageBuildStart(GetPageBuilder page) {
print('bindings are ready');
return page;
}
```
### OnPageBuilt
This function will be called right after the GetPage.page function is called and will give you the result of the function. and take the widget that will be showed.
### OnPageDispose
This function will be called right after disposing all the related objects (Controllers, views, ...) of the page.
## Other Advanced APIs
```dart
... ... @@ -731,7 +897,7 @@ Is a `const Stateless` Widget that has a getter `controller` for a registered `C
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(20),
child: Text( controller.title ), // just call `controller.something`
child: Text(controller.title), // just call `controller.something`
);
}
}
... ...
... ... @@ -102,11 +102,13 @@ Tworzymy View. Użyj StatelessWidget oszczędzajac przy tym RAM. Z Get nie będz
```dart
class Home extends StatelessWidget {
// Instantiate your class using Get.put() to make it available for all "child" routes there.
final Controller c = Get.put(Controller());
@override
Widget build(context) => Scaffold(
Widget build(context) {
// Instantiate your class using Get.put() to make it available for all "child" routes there.
final Controller c = Get.put(Controller());
return Scaffold(
// Use Obx(()=> to update Text() whenever count is changed.
appBar: AppBar(title: Obx(() => Text("Clicks: " + c.count.string))),
... ... @@ -115,6 +117,7 @@ class Home extends StatelessWidget {
child: Text("Go to Other"), onPressed: () => Get.to(Other()))),
floatingActionButton:
FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment));
}
}
class Other extends StatelessWidget {
... ...
... ... @@ -124,16 +124,23 @@ Crie sua View usando StatelessWidget, já que, usando Get, você não precisa ma
```dart
class Home extends StatelessWidget {
// Instancie sua classe usando Get.put() para torná-la disponível para todas as rotas subsequentes
final Controller c = Get.put(Controller());
@override
Widget build(context) => Scaffold(
appBar: AppBar(title: Obx(() => Text("Total de cliques: ${c.count}"))),
Widget build(context) {
// Instancie sua classe usando Get.put() para torná-la disponível para todas as rotas subsequentes
final Controller c = Get.put(Controller());
return Scaffold(
// Use Obx(()=> para atualizar Text() sempre que a contagem é alterada.
appBar: AppBar(title: Obx(() => Text("Total de cliques: ${c.count}"))),
// Troque o Navigator.push de 8 linhas por um simples Get.to(). Você não precisa do 'context'
body: Center(child: RaisedButton(
child: Text("Ir pra Outra tela"), onPressed: () => Get.to(Outra()))),
floatingActionButton: FloatingActionButton(child:
Icon(Icons.add), onPressed: c.increment));
body: Center(child: RaisedButton(
child: Text("Ir pra Outra tela"), onPressed: () => Get.to(Outra()))),
floatingActionButton:
FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment));
}
}
class Outra extends StatelessWidget {
... ...
... ... @@ -118,11 +118,13 @@ class Controller extends GetxController{
```dart
class Home extends StatelessWidget {
// Instantiate your class using Get.put() to make it available for all "child" routes there.
final Controller c = Get.put(Controller());
@override
Widget build(context) => Scaffold(
Widget build(context) {
// Instantiate your class using Get.put() to make it available for all "child" routes there.
final Controller c = Get.put(Controller());
return Scaffold(
// Use Obx(()=> to update Text() whenever count is changed.
appBar: AppBar(title: Obx(() => Text("Clicks: ${c.count}"))),
... ... @@ -131,6 +133,7 @@ class Home extends StatelessWidget {
child: Text("Go to Other"), onPressed: () => Get.to(Other()))),
floatingActionButton:
FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment));
}
}
class Other extends StatelessWidget {
... ...
... ... @@ -122,11 +122,13 @@ class Controller extends GetxController{
```dart
class Home extends StatelessWidget {
// 使用Get.put()实例化你的类,使其对当下的所有子路由可用。
final Controller c = Get.put(Controller());
@override
Widget build(context) => Scaffold(
Widget build(context) {
// 使用Get.put()实例化你的类,使其对当下的所有子路由可用。
final Controller c = Get.put(Controller());
return Scaffold(
// 使用Obx(()=>每当改变计数时,就更新Text()。
appBar: AppBar(title: Obx(() => Text("Clicks: ${c.count}"))),
... ... @@ -135,6 +137,7 @@ class Home extends StatelessWidget {
child: Text("Go to Other"), onPressed: () => Get.to(Other()))),
floatingActionButton:
FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment));
}
}
class Other extends StatelessWidget {
... ...
... ... @@ -29,7 +29,10 @@
.packages
.pub-cache/
.pub/
/build/
/linux/
/ios/
/android/
/web/
# Web related
lib/generated_plugin_registrant.dart
... ...
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'routes/app_pages.dart';
... ...
import 'package:dio/dio.dart';
import 'package:get/get.dart';
import '../data/home_api_provider.dart';
import '../data/home_repository.dart';
import '../domain/adapters/repository_adapter.dart';
... ... @@ -8,8 +8,8 @@ import '../presentation/controllers/home_controller.dart';
class HomeBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => Dio());
Get.lazyPut<IHomeRepository>(() => HomeRepository(dio: Get.find()));
Get.lazyPut<IHomeProvider>(() => HomeProvider());
Get.lazyPut<IHomeRepository>(() => HomeRepository(provider: Get.find()));
Get.lazyPut(() => HomeController(homeRepository: Get.find()));
}
}
... ...
import 'package:get/get.dart';
import '../domain/entity/cases_model.dart';
// ignore: one_member_abstracts
abstract class IHomeProvider {
Future<Response<CasesModel>> getCases(String path);
}
class HomeProvider extends GetConnect implements IHomeProvider {
@override
void onInit() {
httpClient.defaultDecoder = CasesModel.fromJson;
httpClient.baseUrl = 'https://api.covid19api.com';
}
@override
Future<Response<CasesModel>> getCases(String path) => get(path);
}
... ...
import 'package:dio/dio.dart';
import '../domain/adapters/repository_adapter.dart';
import '../domain/entity/cases_model.dart';
import 'home_api_provider.dart';
class HomeRepository implements IHomeRepository {
HomeRepository({this.dio});
final Dio dio;
HomeRepository({this.provider});
final IHomeProvider provider;
@override
Future<CasesModel> getCases() async {
try {
final response = await dio.get("https://api.covid19api.com/summary");
return CasesModel.fromJson(response.data as Map<String, dynamic>);
} on Exception catch (e) {
print(e.toString());
return Future.error(e.toString());
final cases = await provider.getCases("/summary");
if (cases.status.hasError) {
return Future.error(cases.statusText);
} else {
return cases.body;
}
}
}
... ...
... ... @@ -15,12 +15,12 @@ class CasesModel {
this.date,
});
factory CasesModel.fromRawJson(String str) =>
static CasesModel fromRawJson(String str) =>
CasesModel.fromJson(json.decode(str) as Map<String, dynamic>);
String toRawJson() => json.encode(toJson());
factory CasesModel.fromJson(Map<String, dynamic> json) => CasesModel(
static CasesModel fromJson(dynamic json) => CasesModel(
global: json["Global"] == null
? null
: Global.fromJson(json["Global"] as Map<String, dynamic>),
... ...
... ... @@ -34,7 +34,8 @@ class CountryView extends GetView<HomeController> {
final country = controller.state.countries[index];
return ListTile(
onTap: () {
Get.toNamed('/details', arguments: country);
Get.toNamed('/home/country/details',
arguments: country);
},
trailing: CircleAvatar(
backgroundImage: NetworkImage(
... ...
... ... @@ -68,7 +68,7 @@ class HomeView extends GetView<HomeController> {
),
shape: StadiumBorder(),
onPressed: () {
Get.toNamed('/country');
Get.toNamed('/home/country');
},
child: Text(
"Fetch by country",
... ...
... ... @@ -13,17 +13,20 @@ class AppPages {
static final routes = [
GetPage(
name: Routes.HOME,
page: () => HomeView(),
binding: HomeBinding(),
),
GetPage(
name: Routes.COUNTRY,
page: () => CountryView(),
),
GetPage(
name: Routes.DETAILS,
page: () => DetailsView(),
),
name: Routes.HOME,
page: () => HomeView(),
binding: HomeBinding(),
children: [
GetPage(
name: Routes.COUNTRY,
page: () => CountryView(),
children: [
GetPage(
name: Routes.DETAILS,
page: () => DetailsView(),
),
],
),
]),
];
}
... ...
... ... @@ -28,7 +28,6 @@ dependencies:
# Use with the CupertinoIcons class for iOS style icons.
get:
path: ../
dio: ^3.0.9
get_test: ^3.13.3
dependency_overrides:
... ...
... ... @@ -3,6 +3,7 @@
/// injection, and route management in a quick and practical way.
library get;
export 'get_connect/connect.dart';
export 'get_core/get_core.dart';
export 'get_instance/get_instance.dart';
export 'get_navigation/get_navigation.dart';
... ...
import '../get_instance/src/lifecycle.dart';
import 'http/src/certificates/certificates.dart';
import 'http/src/http.dart';
import 'http/src/response/response.dart';
import 'sockets/sockets.dart';
export 'http/src/certificates/certificates.dart';
export 'http/src/http.dart';
export 'http/src/multipart/form_data.dart';
export 'http/src/multipart/multipart_file.dart';
export 'http/src/response/response.dart';
export 'sockets/sockets.dart';
abstract class GetConnectInterface with GetLifeCycleBase {
List<GetSocket> sockets;
GetHttpClient get httpClient;
Future<Response<T>> get<T>(
String url, {
Map<String, String> headers,
String contentType,
Map<String, dynamic> query,
Decoder<T> decoder,
});
Future<Response<T>> request<T>(
String url,
String method, {
dynamic body,
String contentType,
Map<String, String> headers,
Map<String, dynamic> query,
Decoder<T> decoder,
});
Future<Response<T>> post<T>(
String url,
dynamic body, {
String contentType,
Map<String, String> headers,
Map<String, dynamic> query,
Decoder<T> decoder,
});
Future<Response<T>> put<T>(
String url,
dynamic body, {
String contentType,
Map<String, String> headers,
Map<String, dynamic> query,
Decoder<T> decoder,
});
Future<Response<T>> delete<T>(
String url, {
Map<String, String> headers,
String contentType,
Map<String, dynamic> query,
Decoder<T> decoder,
});
GetSocket socket(String url, {Duration ping = const Duration(seconds: 5)});
}
class GetConnect extends GetConnectInterface {
GetConnect({
this.userAgent = 'getx-client',
this.timeout = const Duration(seconds: 5),
this.followRedirects = true,
this.maxRedirects = 5,
this.maxAuthRetries = 1,
this.allowAutoSignedCert = false,
}) {
$configureLifeCycle();
}
bool allowAutoSignedCert;
String userAgent;
String baseUrl;
String defaultContentType = 'application/json; charset=utf-8';
bool followRedirects;
int maxRedirects;
int maxAuthRetries;
Decoder defaultDecoder;
Duration timeout;
List<TrustedCertificate> trustedCertificates;
GetHttpClient _httpClient;
List<GetSocket> _sockets;
@override
List<GetSocket> get sockets => _sockets ??= <GetSocket>[];
@override
GetHttpClient get httpClient => _httpClient ??= GetHttpClient(
userAgent: userAgent,
timeout: timeout,
followRedirects: followRedirects,
maxRedirects: maxRedirects,
maxAuthRetries: maxAuthRetries,
allowAutoSignedCert: allowAutoSignedCert,
baseUrl: baseUrl,
trustedCertificates: trustedCertificates,
);
@override
Future<Response<T>> get<T>(
String url, {
Map<String, String> headers,
String contentType,
Map<String, dynamic> query,
Decoder<T> decoder,
}) {
_checkIfDisposed();
return httpClient.get(
url,
headers: headers,
contentType: contentType,
query: query,
decoder: decoder,
);
}
@override
Future<Response<T>> post<T>(
String url,
dynamic body, {
String contentType,
Map<String, String> headers,
Map<String, dynamic> query,
Decoder<T> decoder,
}) {
_checkIfDisposed();
return httpClient.post<T>(
url,
body: body,
headers: headers,
contentType: contentType,
query: query,
decoder: decoder,
);
}
@override
Future<Response<T>> put<T>(
String url,
dynamic body, {
String contentType,
Map<String, String> headers,
Map<String, dynamic> query,
Decoder<T> decoder,
}) {
_checkIfDisposed();
return httpClient.put(
url,
body: body,
headers: headers,
contentType: contentType,
query: query,
decoder: decoder,
);
}
@override
Future<Response<T>> request<T>(
String url,
String method, {
dynamic body,
String contentType,
Map<String, String> headers,
Map<String, dynamic> query,
Decoder<T> decoder,
}) {
_checkIfDisposed();
return httpClient.put(
url,
body: body,
headers: headers,
contentType: contentType,
query: query,
decoder: decoder,
);
}
@override
Future<Response<T>> delete<T>(
String url, {
Map<String, String> headers,
String contentType,
Map<String, dynamic> query,
Decoder<T> decoder,
}) {
_checkIfDisposed();
return httpClient.delete(
url,
headers: headers,
contentType: contentType,
query: query,
decoder: decoder,
);
}
@override
GetSocket socket(String url, {Duration ping = const Duration(seconds: 5)}) {
_checkIfDisposed(isHttp: false);
final _url = baseUrl == null ? url : baseUrl + url;
final _socket = GetSocket(_url, ping: ping);
sockets.add(_socket);
return _socket;
}
bool _isDisposed = false;
bool get isDisposed => _isDisposed;
void _checkIfDisposed({bool isHttp = true}) {
if (_isDisposed) {
throw 'Can not emit events to disposed clients';
}
}
void dispose() {
if (_sockets != null) {
for (var socket in sockets) {
socket.close();
}
_sockets?.clear();
sockets = null;
}
if (_httpClient != null) {
httpClient.close();
_httpClient = null;
}
_isDisposed = true;
}
}
... ...
class TrustedCertificate {
final List<int> bytes;
TrustedCertificate(this.bytes);
}
... ...
class GetHttpException implements Exception {
final String message;
final Uri uri;
GetHttpException(this.message, [this.uri]);
@override
String toString() => message;
}
class UnauthorizedException implements Exception {
@override
String toString() {
return 'Operation Unauthorized';
}
}
class UnexpectedFormat implements Exception {
final String message;
UnexpectedFormat(this.message);
@override
String toString() {
return 'Unexpected format: $message';
}
}
... ...
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 'interceptors/get_modifiers.dart';
typedef Decoder<T> = T Function(dynamic data);
class GetHttpClient {
String userAgent;
String baseUrl;
String defaultContentType = 'application/json; charset=utf-8';
bool followRedirects;
int maxRedirects;
int maxAuthRetries;
Decoder defaultDecoder;
Duration timeout;
bool errorSafety = true;
final HttpRequestBase _httpClient;
final GetModifier _modifier;
GetHttpClient({
this.userAgent = 'getx-client',
this.timeout = const Duration(seconds: 8),
this.followRedirects = true,
this.maxRedirects = 5,
this.maxAuthRetries = 1,
bool allowAutoSignedCert = false,
this.baseUrl,
List<TrustedCertificate> trustedCertificates,
}) : _httpClient = HttpRequestImpl(
allowAutoSignedCert: allowAutoSignedCert,
trustedCertificates: trustedCertificates,
),
_modifier = GetModifier();
void addAuthenticator<T>(RequestModifier<T> auth) {
_modifier.authenticator = auth as RequestModifier;
}
void addRequestModifier<T>(RequestModifier<T> interceptor) {
_modifier.addRequestModifier<T>(interceptor);
}
void removeRequestModifier<T>(RequestModifier<T> interceptor) {
_modifier.removeRequestModifier(interceptor);
}
void addResponseModifier<T>(ResponseModifier<T> interceptor) {
_modifier.addResponseModifier(interceptor);
}
void removeResponseModifier<T>(ResponseModifier<T> interceptor) {
_modifier.removeResponseModifier<T>(interceptor);
}
Uri _createUri(String url, Map<String, dynamic> query) {
if (baseUrl != null) {
url = baseUrl + url;
}
final uri = Uri.parse(url);
if (query != null) {
uri.replace(queryParameters: query);
}
return uri;
}
Future<Request<T>> _requestWithBody<T>(
String url,
String contentType,
dynamic body,
String method,
Map<String, dynamic> query,
Decoder<T> decoder,
) async {
List<int> bodyBytes;
BodyBytes bodyStream;
final headers = <String, String>{};
headers['content-type'] = contentType ?? defaultContentType;
headers['user-agent'] = userAgent;
if (body is FormData) {
bodyBytes = await body.toBytes();
headers['content-length'] = bodyBytes.length.toString();
} else if (body is Map || body is List) {
var jsonString = json.encode(body);
//TODO check this implementation
if (contentType != null) {
if (contentType.toLowerCase() == 'application/x-www-form-urlencoded') {
var paramName = 'param';
jsonString = '$paramName=${Uri.encodeQueryComponent(jsonString)}';
}
}
bodyBytes = utf8.encode(jsonString);
headers['content-length'] = bodyBytes.length.toString();
} else if (body == null) {
headers['content-length'] = '0';
} else {
if (!errorSafety) {
throw UnexpectedFormat('body cannot be ${body.runtimeType}');
}
}
if (bodyBytes != null) {
bodyStream = BodyBytes.fromBytes(bodyBytes);
}
final uri = _createUri(url, query);
return Request(
method: method,
url: uri,
headers: headers,
bodyBytes: bodyStream,
followRedirects: followRedirects,
maxRedirects: maxRedirects,
);
}
void _setSimpleHeaders(
Map<String, String> headers,
String contentType,
) {
headers['content-type'] = contentType ?? defaultContentType;
headers['user-agent'] = userAgent;
}
Future<Response<T>> _performRequest<T>(
HandlerExecute<T> handler, {
bool authenticate = false,
int requestNumber = 1,
Map<String, String> headers,
}) async {
try {
var request = await handler();
headers?.forEach((key, value) {
request.headers[key] = value;
});
if (authenticate) await _modifier.authenticator(request);
await _modifier.modifyRequest(request);
var response = await _httpClient.send<T>(request);
await _modifier.modifyResponse(request, response);
if (HttpStatus.unauthorized == response.statusCode &&
_modifier.authenticator != null &&
requestNumber <= maxAuthRetries) {
return _performRequest(
handler,
authenticate: true,
requestNumber: requestNumber + 1,
headers: request.headers,
);
} else if (HttpStatus.unauthorized == response.statusCode) {
if (!errorSafety) {
throw UnauthorizedException();
} else {
return Response<T>(
request: request,
headers: response.headers,
statusCode: response.statusCode,
body: response.body,
statusText: response.statusText,
);
}
}
return response;
} on Exception catch (err) {
if (!errorSafety) {
throw GetHttpException(err.toString());
} else {
return Response<T>(
request: null,
headers: null,
statusCode: null,
body: null,
statusText: "$err",
);
}
}
}
Future<Request<T>> _get<T>(
String url,
String contentType,
Map<String, dynamic> query,
Decoder<T> decoder,
) {
final headers = <String, String>{};
_setSimpleHeaders(headers, contentType);
final uri = _createUri(url, query);
return Future.value(Request<T>(
method: 'get',
url: uri,
headers: headers,
decoder: decoder ?? (defaultDecoder as Decoder<T>),
));
}
Future<Request<T>> _post<T>(
String url, {
String contentType,
@required dynamic body,
Map<String, dynamic> query,
Decoder<T> decoder,
}) {
return _requestWithBody<T>(
url,
contentType,
body,
'post',
query,
decoder,
);
}
Future<Request<T>> _request<T>(
String url,
String method, {
String contentType,
@required dynamic body,
@required Map<String, dynamic> query,
Decoder<T> decoder,
}) {
return _requestWithBody(url, contentType, body, method, query, decoder);
}
Future<Request<T>> _put<T>(
String url, {
String contentType,
@required dynamic body,
@required Map<String, dynamic> query,
Decoder<T> decoder,
}) {
return _requestWithBody(url, contentType, body, 'put', query, decoder);
}
Request<T> _delete<T>(
String url,
String contentType,
Map<String, dynamic> query,
Decoder<T> decoder,
) {
final headers = <String, String>{};
_setSimpleHeaders(headers, contentType);
final uri = _createUri(url, query);
return Request<T>(
method: 'delete', url: uri, headers: headers, decoder: decoder);
}
Future<Response<T>> post<T>(
String url, {
dynamic body,
String contentType,
Map<String, String> headers,
Map<String, dynamic> query,
Decoder<T> decoder,
// List<MultipartFile> files,
}) async {
try {
var response = await _performRequest<T>(
() => _post<T>(
url,
contentType: contentType,
body: body,
query: query,
decoder: decoder,
// files: files,
),
headers: headers,
);
return response;
} on Exception catch (e) {
if (!errorSafety) {
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>> request<T>(
String url,
String method, {
Map<String, dynamic> body,
String contentType,
Map<String, String> headers,
Map<String, dynamic> query,
Decoder<T> decoder,
}) async {
try {
var response = await _performRequest(
() => _request(
url,
method,
contentType: contentType,
query: query,
body: body,
decoder: decoder,
),
headers: headers,
);
return response;
} on Exception catch (e) {
if (!errorSafety) {
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>> put<T>(
String url, {
dynamic body,
String contentType,
Map<String, String> headers,
Map<String, dynamic> query,
Decoder<T> decoder,
}) async {
try {
var response = await _performRequest(
() => _put(
url,
contentType: contentType,
query: query,
body: body,
decoder: decoder,
),
headers: headers,
);
return response;
} on Exception catch (e) {
if (!errorSafety) {
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>> get<T>(
String url, {
Map<String, String> headers,
String contentType,
Map<String, dynamic> query,
Decoder<T> decoder,
}) async {
try {
var response = await _performRequest<T>(
() => _get<T>(url, contentType, query, decoder),
headers: headers,
);
return response;
} on Exception catch (e) {
if (!errorSafety) {
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>> delete<T>(
String url, {
Map<String, String> headers,
String contentType,
Map<String, dynamic> query,
Decoder<T> decoder,
}) async {
try {
var response = await _performRequest(
() async => _delete<T>(url, contentType, query, decoder),
headers: headers,
);
return response;
} on Exception catch (e) {
if (!errorSafety) {
throw GetHttpException(e.toString());
}
return Future.value(Response<T>(
request: null,
statusCode: null,
body: null,
statusText: 'Can not connect to server. Reason: $e',
));
}
}
void close() {
_httpClient.close();
}
}
... ...
import 'dart:async';
import 'dart:convert';
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 'request_base.dart';
/// A `dart:html` implementation of `HttpRequestBase`.
class HttpRequestImpl implements HttpRequestBase {
HttpRequestImpl({
bool allowAutoSignedCert = true,
List<TrustedCertificate> trustedCertificates,
});
/// The currently active XHRs.
final _xhrs = <html.HttpRequest>{};
///This option requires that you submit credentials for requests
///on different sites. The default is false
bool withCredentials = false;
/// Sends an HTTP request and asynchronously returns the response.
@override
Future<Response<T>> send<T>(Request<T> request) async {
var bytes = await request.bodyBytes.toBytes();
html.HttpRequest xhr;
// if (request.files != null) {
// var data = html.FormData();
// if (request.files != null) {
// for (MultipartFile element in request.files) {
// var stream = element.finalize();
// data.appendBlob(element., html.File(element.finalize(),
// element.filename),
// element.filename);
// }
// }
// xhr = await html.HttpRequest.request('${request.url}',
// method: request.method, sendData: data);
// } else {
// xhr = html.HttpRequest()
// ..open(request.method, '${request.url}', async: true);
// }
xhr = html.HttpRequest()
..open(request.method, '${request.url}', async: true); // check this
_xhrs.add(xhr);
xhr
..responseType = 'blob'
..withCredentials = withCredentials;
request.headers.forEach(xhr.setRequestHeader);
var completer = Completer<Response<T>>();
xhr.onLoad.first.then((_) {
var blob = xhr.response as html.Blob ?? html.Blob([]);
var reader = html.FileReader();
reader.onLoad.first.then((_) async {
var bodyBytes = BodyBytes.fromBytes(reader.result as Uint8List);
final stringBody =
await bodyBytesToString(bodyBytes, xhr.responseHeaders);
T body;
try {
if (request.decoder == null) {
body = jsonDecode(stringBody) as T;
} else {
body = request.decoder(jsonDecode(stringBody));
}
// body = request.decoder(stringBody);
} on Exception catch (_) {
body = stringBody as T;
}
// final body = jsonDecode(stringBody);
final response = Response<T>(
bodyBytes: bodyBytes,
statusCode: xhr.status,
request: request,
headers: xhr.responseHeaders,
statusText: xhr.statusText,
body: body,
);
completer.complete(response);
});
reader.onError.first.then((error) {
completer.completeError(
GetHttpException(error.toString(), request.url),
StackTrace.current,
);
});
reader.readAsArrayBuffer(blob);
});
xhr.onError.first.then((_) {
completer.completeError(
GetHttpException('XMLHttpRequest error.', request.url),
StackTrace.current);
});
xhr.send(bytes);
try {
return await completer.future;
} finally {
_xhrs.remove(xhr);
}
}
/// Closes the client and abort all active requests.
@override
void close() {
for (var xhr in _xhrs) {
xhr.abort();
}
}
}
... ...
import 'dart:convert';
import 'dart:io' as io;
import '../certificates/certificates.dart';
import '../exceptions/exceptions.dart';
import '../request/request.dart';
import '../response/response.dart';
import 'request_base.dart';
/// A `dart:io` implementation of `HttpRequestBase`.
class HttpRequestImpl extends HttpRequestBase {
io.HttpClient _httpClient;
io.SecurityContext _securityContext;
HttpRequestImpl({
bool allowAutoSignedCert = true,
List<TrustedCertificate> trustedCertificates,
}) {
_httpClient = io.HttpClient();
if (trustedCertificates != null) {
_securityContext = io.SecurityContext();
for (final trustedCertificate in trustedCertificates) {
_securityContext
.setTrustedCertificatesBytes(List.from(trustedCertificate.bytes));
}
}
_httpClient = io.HttpClient(context: _securityContext);
_httpClient.badCertificateCallback = (_, __, ___) => allowAutoSignedCert;
}
@override
Future<Response<T>> send<T>(Request<T> request) async {
var requestBody = await request.bodyBytes.toBytes();
var stream = BodyBytes.fromBytes(requestBody ?? const []);
try {
var ioRequest = (await _httpClient.openUrl(request.method, request.url))
..followRedirects = request.followRedirects
..persistentConnection = request.persistentConnection
..maxRedirects = request.maxRedirects
..contentLength = requestBody.length ?? -1;
request.headers.forEach(ioRequest.headers.set);
var response = await stream.pipe(ioRequest) as io.HttpClientResponse;
var headers = <String, String>{};
response.headers.forEach((key, values) {
headers[key] = values.join(',');
});
final bodyBytes = BodyBytes(response);
final stringBody = await bodyBytesToString(bodyBytes, headers);
T body;
try {
if (request.decoder == null) {
body = jsonDecode(stringBody) as T;
} else {
body = request.decoder(jsonDecode(stringBody));
}
} on Exception catch (_) {
body = stringBody as T;
}
return Response(
headers: headers,
request: request,
statusCode: response.statusCode,
statusText: response.reasonPhrase,
bodyBytes: bodyBytes,
body: body,
);
} on io.HttpException catch (error) {
throw GetHttpException(error.message, error.uri);
}
}
/// Closes the HttpClient.
@override
void close() {
if (_httpClient != null) {
_httpClient.close(force: true);
_httpClient = null;
}
}
}
extension FileExt on io.FileSystemEntity {
String get fileName {
return this?.path?.split(io.Platform.pathSeparator)?.last;
}
}
... ...
import '../certificates/certificates.dart';
import '../request/request.dart';
import '../response/response.dart';
import 'request_base.dart';
class HttpRequestImpl extends HttpRequestBase {
HttpRequestImpl({
bool allowAutoSignedCert = true,
List<TrustedCertificate> trustedCertificates,
});
@override
void close() {}
@override
Future<Response<T>> send<T>(Request<T> request) {
throw UnimplementedError();
}
}
... ...
import '../request/request.dart';
import '../response/response.dart';
/// Abstract interface of [HttpRequestImpl].
abstract class HttpRequestBase {
/// Sends an HTTP [Request].
Future<Response<T>> send<T>(Request<T> request);
/// Closes the [Request] and cleans up any resources associated with it.
void close();
}
... ...
import 'dart:async';
import '../request/request.dart';
import '../response/response.dart';
typedef RequestModifier<T> = FutureOr<Request<T>> Function(Request<T> request);
typedef ResponseModifier<T> = FutureOr Function(
Request<T> request, Response<T> response);
typedef HandlerExecute<T> = Future<Request<T>> Function();
class GetModifier<T> {
final _requestModifiers = <RequestModifier>[];
final _responseModifiers = <ResponseModifier>[];
RequestModifier authenticator;
void addRequestModifier<T>(RequestModifier<T> interceptor) {
_requestModifiers.add(interceptor as RequestModifier);
}
void removeRequestModifier<T>(RequestModifier<T> interceptor) {
_requestModifiers.remove(interceptor);
}
void addResponseModifier<T>(ResponseModifier<T> interceptor) {
_responseModifiers.add(interceptor as ResponseModifier);
}
void removeResponseModifier<T>(ResponseModifier<T> interceptor) {
_requestModifiers.remove(interceptor);
}
Future<void> modifyRequest(Request request) async {
if (_requestModifiers.isNotEmpty) {
for (var interceptor in _requestModifiers) {
await interceptor(request);
}
}
}
Future<void> modifyResponse(Request request, Response response) async {
if (_responseModifiers.isNotEmpty) {
for (var interceptor in _responseModifiers) {
await interceptor(request, response);
}
}
}
}
... ...
import 'dart:async';
import 'dart:convert';
import 'dart:math';
import '../../../../get_rx/src/rx_stream/rx_stream.dart';
import '../request/request.dart';
import '../utils/utils.dart';
import 'multipart_file.dart';
class FormData {
FormData(Map<String, dynamic> map) : boundary = _getBoundary() {
urlEncode(map, '', false, (key, value) {
if (value == null) return;
(value is MultipartFile)
? files.add(MapEntry(key, value))
: fields.add(MapEntry(key, value.toString()));
return;
});
}
static const int _maxBoundaryLength = 70;
static String _getBoundary() {
final _random = Random();
var list = List<int>.generate(_maxBoundaryLength - GET_BOUNDARY.length,
(_) => boundaryCharacters[_random.nextInt(boundaryCharacters.length)],
growable: false);
return '$GET_BOUNDARY${String.fromCharCodes(list)}';
}
final String boundary;
/// The form fields to send for this request.
final fields = <MapEntry<String, String>>[];
/// The [files] to send for this request
final files = <MapEntry<String, MultipartFile>>[];
/// Returns the header string for a field. The return value is guaranteed to
/// contain only ASCII characters.
String _fieldHeader(String name, String value) {
var header =
'content-disposition: form-data; name="${browserEncode(name)}"';
if (!isPlainAscii(value)) {
header = '$header\r\n'
'content-type: text/plain; charset=utf-8\r\n'
'content-transfer-encoding: binary';
}
return '$header\r\n\r\n';
}
/// Returns the header string for a file. The return value is guaranteed to
/// contain only ASCII characters.
String _fileHeader(MapEntry<String, MultipartFile> file) {
var header =
'content-disposition: form-data; name="${browserEncode(file.key)}"';
if (file.value.filename != null) {
header = '$header; filename="${browserEncode(file.value.filename)}"';
}
header = '$header\r\n'
'content-type: ${file.value.contentType}';
return '$header\r\n\r\n';
}
/// The length of the request body from this [FormData]
int get length {
var length = 0;
for (final item in fields) {
length += '--'.length +
_maxBoundaryLength +
'\r\n'.length +
utf8.encode(_fieldHeader(item.key, item.value)).length +
utf8.encode(item.value).length +
'\r\n'.length;
}
for (var file in files) {
length += '--'.length +
_maxBoundaryLength +
'\r\n'.length +
utf8.encode(_fileHeader(file)).length +
file.value.length +
'\r\n'.length;
}
return length + '--'.length + _maxBoundaryLength + '--\r\n'.length;
}
Future<List<int>> toBytes() {
final getStream = GetStream<List<int>>();
for (final item in fields) {
stringToBytes('--$boundary\r\n', getStream);
stringToBytes(_fieldHeader(item.key, item.value), getStream);
stringToBytes(item.value, getStream);
writeLine(getStream);
}
Future.forEach<MapEntry<String, MultipartFile>>(files, (file) {
stringToBytes('--$boundary\r\n', getStream);
stringToBytes(_fileHeader(file), getStream);
return streamToFuture(file.value.stream, getStream)
.then((_) => writeLine(getStream));
}).then((_) {
stringToBytes('--$boundary--\r\n', getStream);
getStream.close();
});
return BodyBytes(getStream.stream).toBytes();
}
}
... ...
import 'package:flutter/foundation.dart';
import '../request/request.dart';
class MultipartFile {
MultipartFile(
List<int> bytes, {
@required this.filename,
this.contentType = 'application/octet-stream',
}) : length = bytes.length,
stream = BodyBytes.fromBytes(bytes);
final String contentType;
/// This stream will emit the file content of File.
final BodyBytes stream;
final int length;
final String filename;
}
... ...
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import '../http.dart';
import '../multipart/form_data.dart';
class Request<T> {
/// Headers attach to this [Request]
final Map<String, String> headers;
/// The [Uri] from request
final Uri url;
final Decoder<T> decoder;
/// The Http Method from this [Request]
/// ex: `GET`,`POST`,`PUT`,`DELETE`
final String method;
/// The BodyBytes of body from this [Request]
final BodyBytes bodyBytes;
/// When true, the client will follow redirects to resolves this [Request]
final bool followRedirects;
/// The maximum number of redirects if [followRedirects] is true.
final int maxRedirects;
final bool persistentConnection;
final FormData files;
const Request._({
@required this.method,
@required this.bodyBytes,
@required this.url,
@required this.headers,
@required this.followRedirects,
@required this.maxRedirects,
@required this.files,
@required this.persistentConnection,
@required this.decoder,
});
factory Request({
@required Uri url,
@required String method,
@required Map<String, String> headers,
BodyBytes bodyBytes,
bool followRedirects = true,
int maxRedirects = 4,
FormData files,
bool persistentConnection = true,
final Decoder<T> decoder,
}) {
assert(url != null);
assert(method != null);
assert(followRedirects != null);
if (followRedirects) {
assert(maxRedirects != null);
assert(maxRedirects > 0);
}
return Request._(
url: url,
method: method,
bodyBytes: bodyBytes ??= BodyBytes.fromBytes(const []),
headers: Map.from(headers ??= <String, String>{}),
followRedirects: followRedirects,
maxRedirects: maxRedirects,
files: files,
persistentConnection: persistentConnection,
decoder: decoder,
);
}
}
class BodyBytes extends StreamView<List<int>> {
BodyBytes(Stream<List<int>> stream) : super(stream);
factory BodyBytes.fromBytes(List<int> bytes) =>
BodyBytes(Stream.fromIterable([bytes]));
Future<Uint8List> toBytes() {
var completer = Completer<Uint8List>();
var sink = ByteConversionSink.withCallback(
(bytes) => completer.complete(
Uint8List.fromList(bytes),
),
);
listen(sink.add,
onError: completer.completeError,
onDone: sink.close,
cancelOnError: true);
return completer.future;
}
Future<String> bytesToString([Encoding encoding = utf8]) =>
encoding.decodeStream(this);
}
... ...
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.bodyBytes,
this.statusText = '',
this.headers = const {},
@required this.body,
});
/// The Http [Request] linked with this [Response].
final Request request;
/// The response headers.
final Map<String, String> headers;
/// The status code returned by the server.
final int statusCode;
/// Human-readable context for [statusCode].
final String statusText;
/// [HttpStatus] from [Response]. `status.connectionError` is true
/// when statusCode is null. `status.isUnauthorized` is true when
/// statusCode is equal `401`. `status.isNotFound` is true when
/// statusCode is equal `404`. `status.isServerError` is true when
/// statusCode is between `500` and `599`.
HttpStatus get status => HttpStatus(statusCode);
/// `hasError` is true when statusCode is not between 200 and 299.
bool get hasError => status.hasError;
/// `isOk` is true when statusCode is between 200 and 299.
bool get isOk => !hasError;
/// `unauthorized` is true when statusCode is equal `401`.
bool get unauthorized => status.isUnauthorized;
/// The response body as a Stream of Bytes.
final BodyBytes bodyBytes;
/// The decoded body of this [Response]. You can access the
/// body parameters as Map
/// Ex: body['title'];
final T body;
}
Future<String> bodyBytesToString(
BodyBytes bodyBytes, Map<String, String> headers) {
return bodyBytes.bytesToString(_encodingForHeaders(headers));
}
/// Returns the encoding to use for a response with the given headers.
///
/// Defaults to [latin1] if the headers don't specify a charset or if that
/// charset is unknown.
Encoding _encodingForHeaders(Map<String, String> headers) =>
_encodingForCharset(_contentTypeForHeaders(headers).parameters['charset']);
/// Returns the [Encoding] that corresponds to [charset].
///
/// Returns [fallback] if [charset] is null or if no [Encoding] was found that
/// corresponds to [charset].
Encoding _encodingForCharset(String charset, [Encoding fallback = latin1]) {
if (charset == null) return fallback;
return Encoding.getByName(charset) ?? fallback;
}
/// Returns the [MediaType] object for the given headers's content-type.
///
/// Defaults to `application/octet-stream`.
HeaderValue _contentTypeForHeaders(Map<String, String> headers) {
var contentType = headers['content-type'];
if (contentType != null) return HeaderValue.parse(contentType);
return HeaderValue('application/octet-stream');
}
class HeaderValue {
String _value;
Map<String, String> _parameters;
Map<String, String> _unmodifiableParameters;
HeaderValue([this._value = '', Map<String, String> parameters]) {
if (parameters != null) {
_parameters = HashMap<String, String>.from(parameters);
}
}
static HeaderValue parse(String value,
{String parameterSeparator = ';',
String valueSeparator,
bool preserveBackslash = false}) {
var result = HeaderValue();
result._parse(value, parameterSeparator, valueSeparator, preserveBackslash);
return result;
}
String get value => _value;
void _ensureParameters() {
_parameters ??= HashMap<String, String>();
}
Map<String, String> get parameters {
_ensureParameters();
_unmodifiableParameters ??= UnmodifiableMapView(_parameters);
return _unmodifiableParameters;
}
@override
String toString() {
var stringBuffer = StringBuffer();
stringBuffer.write(_value);
if (parameters != null && parameters.isNotEmpty) {
_parameters.forEach((name, value) {
stringBuffer..write('; ')..write(name)..write('=')..write(value);
});
}
return stringBuffer.toString();
}
void _parse(String value, String parameterSeparator, String valueSeparator,
bool preserveBackslash) {
var index = 0;
bool done() => index == value.length;
void bump() {
while (!done()) {
if (value[index] != ' ' && value[index] != '\t') return;
index++;
}
}
String parseValue() {
var start = index;
while (!done()) {
if (value[index] == ' ' ||
value[index] == '\t' ||
value[index] == valueSeparator ||
value[index] == parameterSeparator) {
break;
}
index++;
}
return value.substring(start, index);
}
void expect(String expected) {
if (done() || value[index] != expected) {
throw StateError('Failed to parse header value');
}
index++;
}
void maybeExpect(String expected) {
if (value[index] == expected) index++;
}
void parseParameters() {
var parameters = HashMap<String, String>();
_parameters = UnmodifiableMapView(parameters);
String parseParameterName() {
var start = index;
while (!done()) {
if (value[index] == ' ' ||
value[index] == '\t' ||
value[index] == '=' ||
value[index] == parameterSeparator ||
value[index] == valueSeparator) {
break;
}
index++;
}
return value.substring(start, index).toLowerCase();
}
String parseParameterValue() {
if (!done() && value[index] == '\"') {
var stringBuffer = StringBuffer();
index++;
while (!done()) {
if (value[index] == '\\') {
if (index + 1 == value.length) {
throw StateError('Failed to parse header value');
}
if (preserveBackslash && value[index + 1] != '\"') {
stringBuffer.write(value[index]);
}
index++;
} else if (value[index] == '\"') {
index++;
break;
}
stringBuffer.write(value[index]);
index++;
}
return stringBuffer.toString();
} else {
var val = parseValue();
return val == '' ? null : val;
}
}
while (!done()) {
bump();
if (done()) return;
var name = parseParameterName();
bump();
if (done()) {
parameters[name] = null;
return;
}
maybeExpect('=');
bump();
if (done()) {
parameters[name] = null;
return;
}
var value = parseParameterValue();
if (name == 'charset' && value != null) {
value = value.toLowerCase();
}
parameters[name] = value;
bump();
if (done()) return;
if (value[index] == valueSeparator) return;
expect(parameterSeparator);
}
}
bump();
_value = parseValue();
bump();
if (done()) return;
maybeExpect(parameterSeparator);
parseParameters();
}
}
... ...
class HttpStatus {
HttpStatus(this.code);
final int code;
static const int continue_ = 100;
static const int switchingProtocols = 101;
static const int processing = 102;
static const int ok = 200;
static const int created = 201;
static const int accepted = 202;
static const int nonAuthoritativeInformation = 203;
static const int noContent = 204;
static const int resetContent = 205;
static const int partialContent = 206;
static const int multiStatus = 207;
static const int alreadyReported = 208;
static const int imUsed = 226;
static const int multipleChoices = 300;
static const int movedPermanently = 301;
static const int found = 302;
static const int movedTemporarily = 302; // Common alias for found.
static const int seeOther = 303;
static const int notModified = 304;
static const int useProxy = 305;
static const int temporaryRedirect = 307;
static const int permanentRedirect = 308;
static const int badRequest = 400;
static const int unauthorized = 401;
static const int paymentRequired = 402;
static const int forbidden = 403;
static const int notFound = 404;
static const int methodNotAllowed = 405;
static const int notAcceptable = 406;
static const int proxyAuthenticationRequired = 407;
static const int requestTimeout = 408;
static const int conflict = 409;
static const int gone = 410;
static const int lengthRequired = 411;
static const int preconditionFailed = 412;
static const int requestEntityTooLarge = 413;
static const int requestUriTooLong = 414;
static const int unsupportedMediaType = 415;
static const int requestedRangeNotSatisfiable = 416;
static const int expectationFailed = 417;
static const int misdirectedRequest = 421;
static const int unprocessableEntity = 422;
static const int locked = 423;
static const int failedDependency = 424;
static const int upgradeRequired = 426;
static const int preconditionRequired = 428;
static const int tooManyRequests = 429;
static const int requestHeaderFieldsTooLarge = 431;
static const int connectionClosedWithoutResponse = 444;
static const int unavailableForLegalReasons = 451;
static const int clientClosedRequest = 499;
static const int internalServerError = 500;
static const int notImplemented = 501;
static const int badGateway = 502;
static const int serviceUnavailable = 503;
static const int gatewayTimeout = 504;
static const int httpVersionNotSupported = 505;
static const int variantAlsoNegotiates = 506;
static const int insufficientStorage = 507;
static const int loopDetected = 508;
static const int notExtended = 510;
static const int networkAuthenticationRequired = 511;
static const int networkConnectTimeoutError = 599;
bool get connectionError => code == null;
bool get isUnauthorized => code == unauthorized;
bool get isForbidden => code == forbidden;
bool get isNotFound => code == notFound;
bool get isServerError =>
between(internalServerError, networkConnectTimeoutError);
bool between(int begin, int end) {
return !connectionError && code >= begin && code <= end;
}
bool get isOk => between(200, 299);
bool get hasError => !isOk;
}
... ...
import 'dart:async';
import 'dart:convert';
import '../../../../get_rx/src/rx_stream/rx_stream.dart';
import '../request/request.dart';
bool isTokenChar(int byte) {
return byte > 31 && byte < 128 && !SEPARATOR_MAP[byte];
}
bool isValueChar(int byte) {
return (byte > 31 && byte < 128) ||
(byte == CharCode.SP) ||
(byte == CharCode.HT);
}
class CharCode {
static const int HT = 9;
static const int LF = 10;
static const int CR = 13;
static const int SP = 32;
static const int COMMA = 44;
static const int SLASH = 47;
static const int ZERO = 48;
static const int ONE = 49;
static const int COLON = 58;
static const int SEMI_COLON = 59;
}
const bool F = false;
const bool T = true;
const SEPARATOR_MAP = [
F, F, F, F, F, F, F, F, F, T, F, F, F, F, F, F, F, F, F, F, F, F, F, F, //
F, F, F, F, F, F, F, F, T, F, T, F, F, F, F, F, T, T, F, F, T, F, F, T, //
F, F, F, F, F, F, F, F, F, F, T, T, T, T, T, T, T, F, F, F, F, F, F, F, //
F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, T, T, T, F, F, //
F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, //
F, F, F, T, F, T, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, //
F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, //
F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, //
F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, //
F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, //
F, F, F, F, F, F, F, F, F, F, F, F, F, F, F, F
];
String validateField(String field) {
for (var i = 0; i < field.length; i++) {
if (!isTokenChar(field.codeUnitAt(i))) {
throw FormatException(
'Invalid HTTP header field name: ${json.encode(field)}', field, i);
}
}
return field.toLowerCase();
}
BodyBytes toBodyBytes(Stream<List<int>> stream) {
if (stream is BodyBytes) return stream;
return BodyBytes(stream);
}
final _asciiOnly = RegExp(r'^[\x00-\x7F]+$');
final newlineRegExp = RegExp(r'\r\n|\r|\n');
/// Returns whether [string] is composed entirely of ASCII-compatible
/// characters.
bool isPlainAscii(String string) => _asciiOnly.hasMatch(string);
StringBuffer urlEncode(
dynamic sub,
String path,
bool encode,
String Function(String key, Object value) handler,
) {
var urlData = StringBuffer('');
var leftBracket = '[';
var rightBracket = ']';
if (encode) {
leftBracket = '%5B';
rightBracket = '%5D';
}
var encodeComponent = encode ? Uri.encodeQueryComponent : (e) => e;
if (sub is Map) {
sub.forEach((key, value) {
if (path == '') {
urlEncode(
value,
'${encodeComponent(key as String)}',
encode,
handler,
);
} else {
urlEncode(
value,
'$path$leftBracket${encodeComponent(key as String)}$rightBracket',
encode,
handler,
);
}
});
} else {
throw 'FormData need be a Map';
}
return urlData;
}
const String GET_BOUNDARY = 'getx-http-boundary-';
Future streamToFuture(Stream stream, GetStream sink) {
var completer = Completer();
stream.listen(sink.add,
onError: sink.addError, onDone: () => completer.complete());
return completer.future;
}
void stringToBytes(String string, GetStream stream) {
stream.add(utf8.encode(string));
}
/// Encode [value] like browsers
String browserEncode(String value) {
return value.replaceAll(newlineRegExp, '%0D%0A').replaceAll('"', '%22');
}
void writeLine(GetStream stream) => stream.add([13, 10]);
const List<int> boundaryCharacters = <int>[
43,
95,
45,
46,
48,
49,
50,
51,
52,
53,
54,
55,
56,
57,
65,
66,
67,
68,
69,
70,
71,
72,
73,
74,
75,
76,
77,
78,
79,
80,
81,
82,
83,
84,
85,
86,
87,
88,
89,
90,
97,
98,
99,
100,
101,
102,
103,
104,
105,
106,
107,
108,
109,
110,
111,
112,
113,
114,
115,
116,
117,
118,
119,
120,
121,
122
];
... ...
import 'src/sockets_stub.dart'
if (dart.library.html) 'src/sockets_html.dart'
if (dart.library.io) 'src/sockets_io.dart';
class GetSocket extends BaseWebSocket {
GetSocket(
String url, {
Duration ping = const Duration(seconds: 5),
}) : super(url, ping: ping);
}
... ...
import 'dart:convert';
class Close {
final String message;
final int reason;
Close(this.message, this.reason);
@override
String toString() {
return 'Closed by server [$reason => $message]!';
}
}
typedef OpenSocket = void Function();
typedef CloseSocket = void Function(Close);
typedef MessageSocket = void Function(dynamic val);
class SocketNotifier {
var _onMessages = <MessageSocket>[];
var _onEvents = <String, MessageSocket>{};
var _onCloses = <CloseSocket>[];
var _onErrors = <CloseSocket>[];
OpenSocket open;
void addMessages(MessageSocket socket) {
_onMessages.add((socket));
}
void addEvents(String event, MessageSocket socket) {
_onEvents[event] = socket;
}
void addCloses(CloseSocket socket) {
_onCloses.add(socket);
}
void addErrors(CloseSocket socket) {
_onErrors.add((socket));
}
void notifyData(dynamic data) {
for (var item in _onMessages) {
item(data);
}
if (data is String) {
_tryOn(data);
}
}
void notifyClose(Close err) {
for (var item in _onCloses) {
item(err);
}
}
void notifyError(Close err) {
// rooms.removeWhere((key, value) => value.contains(_ws));
for (var item in _onErrors) {
item(err);
}
}
void _tryOn(String message) {
try {
var msg = jsonDecode(message);
final event = msg['type'];
final data = msg['data'];
if (_onEvents.containsKey(event)) {
_onEvents[event](data);
}
} on Exception catch (_) {
return;
}
}
void dispose() {
_onMessages = null;
_onEvents = null;
_onCloses = null;
_onErrors = null;
}
}
... ...
import 'dart:async';
import 'dart:convert';
// ignore: avoid_web_libraries_in_flutter
import 'dart:html';
import '../../../get_core/get_core.dart';
import 'socket_notifier.dart';
enum ConnectionStatus {
connecting,
connected,
closed,
}
class BaseWebSocket {
String url;
WebSocket socket;
SocketNotifier socketNotifier = SocketNotifier();
Duration ping;
bool isDisposed = false;
BaseWebSocket(this.url, {this.ping = const Duration(seconds: 5)}) {
url = url.startsWith('https')
? url.replaceAll('https:', 'wss:')
: url.replaceAll('http:', 'ws:');
}
ConnectionStatus connectionStatus;
Timer _t;
void connect() {
try {
connectionStatus = ConnectionStatus.connecting;
socket = WebSocket(url);
socket.onOpen.listen((e) {
socketNotifier?.open();
_t = Timer?.periodic(ping, (t) {
socket.send('');
});
connectionStatus = ConnectionStatus.connected;
});
socket.onMessage.listen((event) {
socketNotifier.notifyData(event.data);
});
socket.onClose.listen((e) {
_t?.cancel();
connectionStatus = ConnectionStatus.closed;
socketNotifier.notifyClose(Close(e.reason, e.code));
});
socket.onError.listen((event) {
_t?.cancel();
socketNotifier.notifyError(Close(event.toString(), 0));
connectionStatus = ConnectionStatus.closed;
});
} on Exception catch (e) {
_t?.cancel();
socketNotifier.notifyError(Close(e.toString(), 500));
connectionStatus = ConnectionStatus.closed;
// close(500, e.toString());
}
}
// ignore: use_setters_to_change_properties
void onOpen(OpenSocket fn) {
socketNotifier.open = fn;
}
void onClose(CloseSocket fn) {
socketNotifier.addCloses(fn);
}
void onError(CloseSocket fn) {
socketNotifier.addErrors(fn);
}
void onMessage(MessageSocket fn) {
socketNotifier.addMessages(fn);
}
void on(String event, MessageSocket message) {
socketNotifier.addEvents(event, message);
}
void close([int status, String reason]) {
if (socket != null) socket.close(status, reason);
}
void send(dynamic data) {
if (connectionStatus == ConnectionStatus.closed) {
connect();
}
if (socket != null && socket.readyState == WebSocket.OPEN) {
socket.send(data);
} else {
Get.log('WebSocket not connected, message $data not sent');
}
}
void emit(String event, dynamic data) {
send(jsonEncode({'type': event, 'data': data}));
}
void dispose() {
socketNotifier.dispose();
socketNotifier = null;
isDisposed = true;
}
}
... ...
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import '../../../get_core/get_core.dart';
import 'socket_notifier.dart';
enum ConnectionStatus {
connecting,
connected,
closed,
}
class BaseWebSocket {
String url;
WebSocket socket;
SocketNotifier socketNotifier = SocketNotifier();
bool isDisposed = false;
BaseWebSocket(this.url, {this.ping = const Duration(seconds: 5)});
Duration ping;
bool allowSelfSigned = true;
ConnectionStatus connectionStatus;
Future connect() async {
if (isDisposed) {
socketNotifier = SocketNotifier();
}
try {
connectionStatus = ConnectionStatus.connecting;
socket = allowSelfSigned
? await _connectForSelfSignedCert(url)
: await WebSocket.connect(url);
socket.pingInterval = ping;
socketNotifier?.open();
connectionStatus = ConnectionStatus.connected;
socket.listen((data) {
socketNotifier.notifyData(data);
}, onError: (err) {
socketNotifier.notifyError(Close(err.toString(), 1005));
}, onDone: () {
connectionStatus = ConnectionStatus.closed;
socketNotifier
.notifyClose(Close('Connection Closed', socket.closeCode));
}, cancelOnError: true);
return;
} on SocketException catch (e) {
connectionStatus = ConnectionStatus.closed;
socketNotifier.notifyError(Close(e.osError.message, e.osError.errorCode));
return;
}
}
// ignore: use_setters_to_change_properties
void onOpen(OpenSocket fn) {
socketNotifier.open = fn;
}
void onClose(CloseSocket fn) {
socketNotifier.addCloses(fn);
}
void onError(CloseSocket fn) {
socketNotifier.addErrors(fn);
}
void onMessage(MessageSocket fn) {
socketNotifier.addMessages(fn);
}
void on(String event, MessageSocket message) {
socketNotifier.addEvents(event, message);
}
void close([int status, String reason]) {
if (socket != null) {
socket.close(status, reason);
}
}
void send(dynamic data) async {
if (connectionStatus == ConnectionStatus.closed) {
await connect();
}
if (socket != null) {
socket.add(data);
}
}
void dispose() {
socketNotifier.dispose();
socketNotifier = null;
isDisposed = true;
}
void emit(String event, dynamic data) {
send(jsonEncode({'type': event, 'data': data}));
}
Future<WebSocket> _connectForSelfSignedCert(String url) async {
try {
var r = Random();
var key = base64.encode(List<int>.generate(8, (_) => r.nextInt(255)));
var client = HttpClient(context: SecurityContext());
client.badCertificateCallback = (cert, host, port) {
Get.log(
'BaseWebSocket: Allow self-signed certificate => $host:$port. ');
return true;
};
var request = await client.getUrl(Uri.parse(url));
request.headers.add('Connection', 'Upgrade');
request.headers.add('Upgrade', 'websocket');
request.headers.add('Sec-WebSocket-Version', '13');
request.headers.add('Sec-WebSocket-Key', key.toLowerCase());
var response = await request.close();
// ignore: close_sinks
var socket = await response.detachSocket();
var webSocket = WebSocket.fromUpgradedSocket(
socket,
serverSide: false,
);
return webSocket;
} on Exception catch (_) {
rethrow;
}
}
}
... ...
class BaseWebSocket {
String url;
Duration ping;
BaseWebSocket(this.url, {this.ping = const Duration(seconds: 5)}) {
throw 'To use sockets you need dart:io or dart:html';
}
void close([int status, String reason]) {
throw 'To use sockets you need dart:io or dart:html';
}
}
... ...
... ... @@ -221,7 +221,7 @@ class GetInstance {
S _startController<S>({String tag}) {
final key = _getKey(S, tag);
final i = _singl[key].getDependency() as S;
if (i is GetLifeCycle) {
if (i is GetLifeCycleBase) {
if (i.onStart != null) {
i.onStart();
Get.log('"$key" has been initialized');
... ...
... ... @@ -9,6 +9,7 @@ export 'src/routes/custom_transition.dart';
export 'src/routes/default_route.dart';
export 'src/routes/get_route.dart';
export 'src/routes/observers/route_observer.dart';
export 'src/routes/route_middleware.dart';
export 'src/routes/transitions_type.dart';
export 'src/snackbar/snack.dart';
export 'src/snackbar/snack_route.dart';
... ...
... ... @@ -1043,15 +1043,17 @@ Since version 2.8 it is possible to access the properties
return WidgetsBinding.instance;
}
///The window to which this binding is bound.
ui.Window get window => ui.window;
//TODO: Change to ui.SingletonFlutterWindow rather dynamic
//when Flutter update stable. dynamic is used to avoid Breaking Changes
/// The window to which this binding is bound.
dynamic get window => ui.window;
Locale get deviceLocale => window.locale;
Locale get deviceLocale => ui.window.locale;
///The number of device pixels for each logical pixel.
double get pixelRatio => window.devicePixelRatio;
double get pixelRatio => ui.window.devicePixelRatio;
Size get size => window.physicalSize / pixelRatio;
Size get size => ui.window.physicalSize / pixelRatio;
///The horizontal extent of this size.
double get width => size.width;
... ... @@ -1061,14 +1063,14 @@ Since version 2.8 it is possible to access the properties
///The distance from the top edge to the first unpadded pixel,
///in physical pixels.
double get statusBarHeight => window.padding.top;
double get statusBarHeight => ui.window.padding.top;
///The distance from the bottom edge to the first unpadded pixel,
///in physical pixels.
double get bottomBarHeight => window.padding.bottom;
double get bottomBarHeight => ui.window.padding.bottom;
///The system-reported text scale.
double get textScaleFactor => window.textScaleFactor;
double get textScaleFactor => ui.window.textScaleFactor;
/// give access to TextTheme.of(context)
TextTheme get textTheme => theme?.textTheme;
... ... @@ -1080,7 +1082,8 @@ Since version 2.8 it is possible to access the properties
bool get isDarkMode => (theme.brightness == Brightness.dark);
/// Check if dark mode theme is enable on platform on android Q+
bool get isPlatformDarkMode => (window.platformBrightness == Brightness.dark);
bool get isPlatformDarkMode =>
(ui.window.platformBrightness == Brightness.dark);
/// give access to Theme.of(context).iconTheme.color
Color get iconColor => theme?.iconTheme?.color;
... ...
... ... @@ -188,44 +188,7 @@ class GetCupertinoApp extends StatelessWidget {
super(key: key);
Route<dynamic> generator(RouteSettings settings) {
final match = Get.routeTree.matchRoute(settings.name);
Get.parameters = match?.parameters;
if (match?.route == null) {
return GetPageRoute(
page: unknownRoute.page,
parameter: unknownRoute.parameter,
settings:
RouteSettings(name: settings.name, arguments: settings.arguments),
curve: unknownRoute.curve,
opaque: unknownRoute.opaque,
customTransition: unknownRoute.customTransition,
binding: unknownRoute.binding,
bindings: unknownRoute.bindings,
transitionDuration:
(unknownRoute.transitionDuration ?? Get.defaultTransitionDuration),
transition: unknownRoute.transition,
popGesture: unknownRoute.popGesture,
fullscreenDialog: unknownRoute.fullscreenDialog,
);
}
return GetPageRoute(
page: match.route.page,
parameter: match.route.parameter,
settings:
RouteSettings(name: settings.name, arguments: settings.arguments),
curve: match.route.curve,
opaque: match.route.opaque,
customTransition: match.route.customTransition,
binding: match.route.binding,
bindings: match.route.bindings,
transitionDuration:
(match.route.transitionDuration ?? Get.defaultTransitionDuration),
transition: match.route.transition,
popGesture: match.route.popGesture,
fullscreenDialog: match.route.fullscreenDialog,
);
return PageRedirect(settings, unknownRoute).page();
}
List<Route<dynamic>> initialRoutesGenerate(String name) {
... ...
... ... @@ -199,44 +199,7 @@ class GetMaterialApp extends StatelessWidget {
super(key: key);
Route<dynamic> generator(RouteSettings settings) {
final match = Get.routeTree.matchRoute(settings.name);
Get.parameters = match?.parameters;
if (match?.route == null) {
return GetPageRoute(
page: unknownRoute.page,
parameter: unknownRoute.parameter,
settings:
RouteSettings(name: settings.name, arguments: settings.arguments),
curve: unknownRoute.curve,
opaque: unknownRoute.opaque,
customTransition: unknownRoute.customTransition,
binding: unknownRoute.binding,
bindings: unknownRoute.bindings,
transitionDuration:
(unknownRoute.transitionDuration ?? Get.defaultTransitionDuration),
transition: unknownRoute.transition,
popGesture: unknownRoute.popGesture,
fullscreenDialog: unknownRoute.fullscreenDialog,
);
}
return GetPageRoute(
page: match.route.page,
parameter: match.route.parameter,
settings:
RouteSettings(name: settings.name, arguments: settings.arguments),
curve: match.route.curve,
opaque: match.route.opaque,
customTransition: match.route.customTransition,
binding: match.route.binding,
bindings: match.route.bindings,
transitionDuration:
(match.route.transitionDuration ?? Get.defaultTransitionDuration),
transition: match.route.transition,
popGesture: match.route.popGesture,
fullscreenDialog: match.route.fullscreenDialog,
);
return PageRedirect(settings, unknownRoute).page();
}
List<Route<dynamic>> initialRoutesGenerate(String name) {
... ...
import 'package:flutter/widgets.dart';
import '../../get_navigation.dart';
import '../routes/get_route.dart';
class ParseRouteTree {
final List<_ParseRouteTreeNode> _nodes = <_ParseRouteTreeNode>[];
// bool _hasDefaultRoute = false;
void addRoute(GetPage route) {
var path = route.name;
... ... @@ -46,8 +46,57 @@ class ParseRouteTree {
}
parent = node;
}
// Add Page children.
for (var page in _flattenPage(route)) {
addRoute(page);
}
}
List<GetPage> _flattenPage(GetPage route) {
final result = <GetPage>[];
if (route.children == null || route.children.isEmpty) {
return result;
}
final parentPath = route.name;
for (var page in route.children) {
// Add Parent middlewares to children
final pageMiddlewares = page.middlewares ?? <GetMiddleware>[];
pageMiddlewares.addAll(route.middlewares ?? <GetMiddleware>[]);
result.add(_addChild(page, parentPath, pageMiddlewares));
final children = _flattenPage(page);
for (var child in children) {
pageMiddlewares.addAll(child.middlewares ?? <GetMiddleware>[]);
result.add(_addChild(child, parentPath, pageMiddlewares));
}
}
return result;
}
/// Change the Path for a [GetPage]
GetPage _addChild(
GetPage origin, String parentPath, List<GetMiddleware> middlewares) =>
GetPage(
name: parentPath + origin.name,
page: origin.page,
title: origin.title,
alignment: origin.alignment,
transition: origin.transition,
binding: origin.binding,
bindings: origin.bindings,
curve: origin.curve,
customTransition: origin.customTransition,
fullscreenDialog: origin.fullscreenDialog,
maintainState: origin.maintainState,
opaque: origin.opaque,
parameter: origin.parameter,
popGesture: origin.popGesture,
settings: origin.settings,
transitionDuration: origin.transitionDuration,
middlewares: middlewares,
);
_GetPageMatch matchRoute(String path) {
var usePath = path;
if (usePath.startsWith("/")) {
... ...
... ... @@ -11,29 +11,31 @@ import 'default_transitions.dart';
import 'transitions_type.dart';
class GetPageRoute<T> extends PageRoute<T> {
GetPageRoute({
RouteSettings settings,
this.transitionDuration = const Duration(milliseconds: 300),
this.opaque = true,
this.parameter,
this.curve,
this.alignment,
this.transition,
this.popGesture,
this.customTransition,
this.barrierDismissible = false,
this.barrierColor,
this.binding,
this.bindings,
this.routeName,
this.page,
this.barrierLabel,
this.maintainState = true,
bool fullscreenDialog = false,
}) : assert(opaque != null),
GetPageRoute(
{RouteSettings settings,
this.transitionDuration = const Duration(milliseconds: 300),
this.opaque = true,
this.parameter,
this.curve,
this.alignment,
this.transition,
this.popGesture,
this.customTransition,
this.barrierDismissible = false,
this.barrierColor,
this.binding,
this.bindings,
this.routeName,
this.page,
this.barrierLabel,
this.maintainState = true,
bool fullscreenDialog = false,
this.middlewares})
: assert(opaque != null),
assert(barrierDismissible != null),
assert(maintainState != null),
assert(fullscreenDialog != null),
reference = "$routeName: ${page.hashCode}",
super(settings: settings, fullscreenDialog: fullscreenDialog);
@override
... ... @@ -43,6 +45,8 @@ class GetPageRoute<T> extends PageRoute<T> {
final String routeName;
final String reference;
final CustomTransition customTransition;
final Bindings binding;
... ... @@ -65,6 +69,8 @@ class GetPageRoute<T> extends PageRoute<T> {
final Alignment alignment;
final List<GetMiddleware> middlewares;
@override
final Color barrierColor;
... ... @@ -110,15 +116,21 @@ class GetPageRoute<T> extends PageRoute<T> {
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
Get.reference = settings.name ?? routeName;
// Get.reference = settings.name ?? routeName;
Get.reference = reference;
final middlewareRunner = MiddlewareRunner(middlewares);
final bindingsToBind = middlewareRunner.runOnBindingsStart(bindings);
binding?.dependencies();
if (bindings != null) {
for (final binding in bindings) {
if (bindingsToBind != null) {
for (final binding in bindingsToBind) {
binding.dependencies();
}
}
// final pageWidget = page();
return page();
final pageToBuild = middlewareRunner.runOnPageBuildStart(page);
return middlewareRunner.runOnPageBuilt(pageToBuild());
}
static bool isPopGestureInProgress(PageRoute<dynamic> route) {
... ... @@ -372,11 +384,18 @@ class GetPageRoute<T> extends PageRoute<T> {
@override
void dispose() {
super.dispose();
// if (Get.smartManagement != SmartManagement.onlyBuilder) {
// WidgetsBinding.instance.addPostFrameCallback((_) => GetInstance()
// .removeDependencyByRoute("${settings?.name ?? routeName}"));
// }
if (Get.smartManagement != SmartManagement.onlyBuilder) {
WidgetsBinding.instance.addPostFrameCallback((_) => GetInstance()
.removeDependencyByRoute("${settings?.name ?? routeName}"));
WidgetsBinding.instance.addPostFrameCallback(
(_) => GetInstance().removeDependencyByRoute("$reference"));
}
super.dispose();
final middlewareRunner = MiddlewareRunner(middlewares);
middlewareRunner.runOnPageDispose();
}
}
... ...
import 'package:flutter/widgets.dart';
import '../../../get_instance/get_instance.dart';
import '../../get_navigation.dart';
import 'custom_transition.dart';
import 'transitions_type.dart';
... ... @@ -20,6 +22,8 @@ class GetPage {
final Duration transitionDuration;
final bool fullscreenDialog;
final RouteSettings settings;
final List<GetPage> children;
final List<GetMiddleware> middlewares;
const GetPage({
@required this.name,
... ... @@ -38,8 +42,52 @@ class GetPage {
this.transition,
this.customTransition,
this.fullscreenDialog = false,
this.children,
this.middlewares,
}) : assert(page != null),
assert(name != null),
assert(maintainState != null),
assert(fullscreenDialog != null);
GetPage copyWith({
String name,
GetPageBuilder page,
bool popGesture,
Map<String, String> parameter,
String title,
Transition transition,
Curve curve,
Alignment alignment,
bool maintainState,
bool opaque,
Bindings binding,
List<Bindings> bindings,
CustomTransition customTransition,
Duration transitionDuration,
bool fullscreenDialog,
RouteSettings settings,
List<GetPage> children,
List<GetMiddleware> middlewares,
}) {
return GetPage(
name: name ?? this.name,
page: page ?? this.page,
popGesture: popGesture ?? this.popGesture,
parameter: parameter ?? this.parameter,
title: title ?? this.title,
transition: transition ?? this.transition,
curve: curve ?? this.curve,
alignment: alignment ?? this.alignment,
maintainState: maintainState ?? this.maintainState,
opaque: opaque ?? this.opaque,
binding: binding ?? this.binding,
bindings: bindings ?? this.bindings,
customTransition: customTransition ?? this.customTransition,
transitionDuration: transitionDuration ?? this.transitionDuration,
fullscreenDialog: fullscreenDialog ?? this.fullscreenDialog,
settings: settings ?? this.settings,
children: children ?? this.children,
middlewares: middlewares ?? this.middlewares,
);
}
}
... ...
... ... @@ -115,7 +115,8 @@ class GetObserver extends NavigatorObserver {
value.removed = '';
value.previous = _extractRouteName(previousRoute) ?? '';
value.isSnackbar = newRoute.isSnackbar ? true : value.isSnackbar ?? false;
value.isBottomSheet = newRoute.isBottomSheet ? true : value.isBottomSheet ?? false;
value.isBottomSheet =
newRoute.isBottomSheet ? true : value.isBottomSheet ?? false;
value.isDialog = newRoute.isDialog ? true : value.isDialog ?? false;
});
... ... @@ -154,7 +155,8 @@ class GetObserver extends NavigatorObserver {
value.removed = '';
value.previous = newRoute.name ?? '';
value.isSnackbar = currentRoute.isSnackbar ? false : value.isSnackbar;
value.isBottomSheet = currentRoute.isBottomSheet ? false : value.isBottomSheet;
value.isBottomSheet =
currentRoute.isBottomSheet ? false : value.isBottomSheet;
value.isDialog = currentRoute.isDialog ? false : value.isDialog;
});
... ... @@ -184,7 +186,8 @@ class GetObserver extends NavigatorObserver {
value.removed = '';
value.previous = '$oldName';
value.isSnackbar = currentRoute.isSnackbar ? false : value.isSnackbar;
value.isBottomSheet = currentRoute.isBottomSheet ? false : value.isBottomSheet;
value.isBottomSheet =
currentRoute.isBottomSheet ? false : value.isBottomSheet;
value.isDialog = currentRoute.isDialog ? false : value.isDialog;
});
... ... @@ -198,14 +201,15 @@ class GetObserver extends NavigatorObserver {
final currentRoute = _RouteData.ofRoute(route);
Get.log("REMOVING ROUTE $routeName");
_routeSend?.update((value) {
value.route = previousRoute;
value.isBack = false;
value.removed = routeName ?? '';
value.previous = routeName ?? '';
value.isSnackbar = currentRoute.isSnackbar ? false : value.isSnackbar;
value.isBottomSheet = currentRoute.isBottomSheet ? false : value.isBottomSheet;
value.isBottomSheet =
currentRoute.isBottomSheet ? false : value.isBottomSheet;
value.isDialog = currentRoute.isDialog ? false : value.isDialog;
});
... ...
import 'package:flutter/cupertino.dart';
import '../../../get.dart';
abstract class _RouteMiddleware {
/// The Order of the Middlewares to run.
///
/// {@tool snippet}
/// This Middewares will be called in this order.
/// ```dart
/// final middlewares = [
/// GetMiddleware(priority: 2),
/// GetMiddleware(priority: 5),
/// GetMiddleware(priority: 4),
/// GetMiddleware(priority: -8),
/// ];
/// ```
/// -8 => 2 => 4 => 5
/// {@end-tool}
int priority;
/// This function will be called when the page of
/// the called route is being searched for.
/// It take RouteSettings as a result an redirect to the new settings or
/// give it null and there will be no redirecting.
/// {@tool snippet}
/// ```dart
/// GetPage redirect(String route) {
/// final authService = Get.find<AuthService>();
/// return authService.authed.value ? null : RouteSettings(name: '/login');
/// }
/// ```
/// {@end-tool}
RouteSettings redirect(String route);
/// This function will be called when this Page is called
/// you can use it to change something about the page or give it new page
/// {@tool snippet}
/// ```dart
/// GetPage onPageCalled(GetPage page) {
/// final authService = Get.find<AuthService>();
/// return page.copyWith(title: 'Wellcome ${authService.UserName}');
/// }
/// ```
/// {@end-tool}
GetPage onPageCalled(GetPage page);
/// This function will be called right before the [Bindings] are initialize.
/// Here you can change [Bindings] for this page
/// {@tool snippet}
/// ```dart
/// List<Bindings> onBindingsStart(List<Bindings> bindings) {
/// final authService = Get.find<AuthService>();
/// if (authService.isAdmin) {
/// bindings.add(AdminBinding());
/// }
/// return bindings;
/// }
/// ```
/// {@end-tool}
List<Bindings> onBindingsStart(List<Bindings> bindings);
/// This function will be called right after the [Bindings] are initialize.
GetPageBuilder onPageBuildStart(GetPageBuilder page);
/// This function will be called right after the
/// GetPage.page function is called and will give you the result
/// of the function. and take the widget that will be showed.
Widget onPageBuilt(Widget page);
void onPageDispose();
}
/// The Page Middlewares.
/// The Functions will be called in this order
/// (( [redirect] -> [onPageCalled] -> [onBindingsStart] ->
/// [onPageBuildStart] -> [onPageBuilt] -> [onPageDispose] ))
class GetMiddleware implements _RouteMiddleware {
@override
int priority = 0;
GetMiddleware({this.priority});
@override
RouteSettings redirect(String route) => null;
@override
GetPage onPageCalled(GetPage page) => page;
@override
List<Bindings> onBindingsStart(List<Bindings> bindings) => bindings;
@override
GetPageBuilder onPageBuildStart(GetPageBuilder page) => page;
@override
Widget onPageBuilt(Widget page) => page;
@override
void onPageDispose() {}
}
class MiddlewareRunner {
MiddlewareRunner(this._middlewares);
final List<GetMiddleware> _middlewares;
List<GetMiddleware> _getMiddlewares() {
if (_middlewares != null) {
_middlewares.sort((a, b) => a.priority.compareTo(b.priority));
return _middlewares;
}
return <GetMiddleware>[];
}
GetPage runOnPageCalled(GetPage page) {
_getMiddlewares().forEach((element) {
page = element.onPageCalled(page);
});
return page;
}
RouteSettings runRedirect(String route) {
RouteSettings to;
_getMiddlewares().forEach((element) {
to = element.redirect(route);
});
if (to != null) {
Get.log('Redirect to $to');
}
return to;
}
List<Bindings> runOnBindingsStart(List<Bindings> bindings) {
_getMiddlewares().forEach((element) {
bindings = element.onBindingsStart(bindings);
});
return bindings;
}
GetPageBuilder runOnPageBuildStart(GetPageBuilder page) {
_getMiddlewares().forEach((element) {
page = element.onPageBuildStart(page);
});
return page;
}
Widget runOnPageBuilt(Widget page) {
_getMiddlewares().forEach((element) {
page = element.onPageBuilt(page);
});
return page;
}
void runOnPageDispose() =>
_getMiddlewares().forEach((element) => element.onPageDispose());
}
class PageRedirect {
GetPage route;
GetPage unknownRoute;
RouteSettings settings;
bool isUnknown;
PageRedirect(this.settings, this.unknownRoute,
{this.isUnknown = false, this.route});
// redirect all pages that needes redirecting
GetPageRoute page() {
while (needRecheck()) {}
return isUnknown
? GetPageRoute(
page: unknownRoute.page,
parameter: unknownRoute.parameter,
settings: RouteSettings(
name: settings.name, arguments: settings.arguments),
curve: unknownRoute.curve,
opaque: unknownRoute.opaque,
customTransition: unknownRoute.customTransition,
binding: unknownRoute.binding,
bindings: unknownRoute.bindings,
transitionDuration: (unknownRoute.transitionDuration ??
Get.defaultTransitionDuration),
transition: unknownRoute.transition,
popGesture: unknownRoute.popGesture,
fullscreenDialog: unknownRoute.fullscreenDialog,
middlewares: unknownRoute.middlewares,
)
: GetPageRoute(
page: route.page,
parameter: route.parameter,
settings: RouteSettings(
name: settings.name, arguments: settings.arguments),
curve: route.curve,
opaque: route.opaque,
customTransition: route.customTransition,
binding: route.binding,
bindings: route.bindings,
transitionDuration:
(route.transitionDuration ?? Get.defaultTransitionDuration),
transition: route.transition,
popGesture: route.popGesture,
fullscreenDialog: route.fullscreenDialog,
middlewares: route.middlewares);
}
/// check if redirect is needed
bool needRecheck() {
final match = Get.routeTree.matchRoute(settings.name);
Get.parameters = match?.parameters;
// No Match found
if (match?.route == null) {
isUnknown = true;
return false;
}
final runner = MiddlewareRunner(match.route.middlewares);
route = runner.runOnPageCalled(match.route);
// No middlewares found return match.
if (match.route.middlewares == null || match.route.middlewares.isEmpty) {
return false;
}
final newSettings = runner.runRedirect(settings.name);
if (newSettings == null) {
return false;
}
settings = newSettings;
return true;
}
}
... ...
... ... @@ -3,7 +3,6 @@ part of rx_types;
/// global object that registers against `GetX` and `Obx`, and allows the
/// reactivity
/// of those `Widgets` and Rx values.
RxInterface getObs;
mixin RxObjectMixin<T> on NotifyManager<T> {
T _value;
... ... @@ -104,8 +103,8 @@ mixin RxObjectMixin<T> on NotifyManager<T> {
/// Returns the current [value]
T get value {
if (getObs != null) {
getObs.addListener(subject);
if (RxInterface.proxy != null) {
RxInterface.proxy.addListener(subject);
}
return _value;
}
... ... @@ -235,10 +234,14 @@ class RxString extends _RxImpl<String> {
class Rx<T> extends _RxImpl<T> {
Rx([T initial]) : super(initial);
// TODO: Look for a way to throw the Exception with proper details when the
// value [T] doesn't implement toJson().
@override
dynamic toJson() => (value as dynamic)?.toJson();
dynamic toJson() {
try {
return (value as dynamic)?.toJson();
} on Exception catch (_) {
throw '$T has not method [toJson]';
}
}
}
extension StringExtension on String {
... ...
... ... @@ -15,6 +15,8 @@ abstract class RxInterface<T> {
/// Close the Rx Variable
void close();
static RxInterface proxy;
/// Calls [callback] with current value, when the value changes.
StreamSubscription<T> listen(void Function(T event) onData,
{Function onError, void Function() onDone, bool cancelOnError});
... ...
... ... @@ -87,8 +87,8 @@ class RxList<E> extends ListMixin<E>
@override
@protected
List<E> get value {
if (getObs != null) {
getObs.addListener(subject);
if (RxInterface.proxy != null) {
RxInterface.proxy.addListener(subject);
}
return _value;
}
... ...
... ... @@ -39,8 +39,8 @@ class RxMap<K, V> extends MapMixin<K, V>
@override
@protected
Map<K, V> get value {
if (getObs != null) {
getObs.addListener(subject);
if (RxInterface.proxy != null) {
RxInterface.proxy.addListener(subject);
}
return _value;
}
... ...
... ... @@ -61,8 +61,8 @@ class RxSet<E> extends SetMixin<E>
@override
@protected
Set<E> get value {
if (getObs != null) {
getObs.addListener(subject);
if (RxInterface.proxy != null) {
RxInterface.proxy.addListener(subject);
}
return _value;
}
... ...
... ... @@ -113,8 +113,8 @@ class GetXState<T extends DisposableInterface> extends State<GetX<T>> {
}
Widget get notifyChildren {
final observer = getObs;
getObs = _observer;
final observer = RxInterface.proxy;
RxInterface.proxy = _observer;
final result = widget.builder(controller);
if (!_observer.canUpdate) {
throw """
... ... @@ -126,7 +126,7 @@ class GetXState<T extends DisposableInterface> extends State<GetX<T>> {
If you need to update a parent widget and a child widget, wrap each one in an Obx/GetX.
""";
}
getObs = observer;
RxInterface.proxy = observer;
return result;
}
... ...
... ... @@ -41,8 +41,8 @@ class _ObxState extends State<ObxWidget> {
}
Widget get notifyChilds {
final observer = getObs;
getObs = _observer;
final observer = RxInterface.proxy;
RxInterface.proxy = _observer;
final result = widget.build();
if (!_observer.canUpdate) {
throw """
... ... @@ -54,7 +54,7 @@ class _ObxState extends State<ObxWidget> {
If you need to update a parent widget and a child widget, wrap each one in an Obx/GetX.
""";
}
getObs = observer;
RxInterface.proxy = observer;
return result;
}
... ...
... ... @@ -59,6 +59,12 @@ extension ContextExtensionss on BuildContext {
/// similar to [MediaQuery.of(context).padding]
ThemeData get theme => Theme.of(this);
/// Check if dark mode theme is enable
bool get isDarkMode => (theme.brightness == Brightness.dark);
/// give access to Theme.of(context).iconTheme.color
Color get iconColor => theme.iconTheme.color;
/// similar to [MediaQuery.of(context).padding]
TextTheme get textTheme => Theme.of(this).textTheme;
... ...
import 'dart:async';
import '../../../get_core/src/get_interface.dart';
extension LoopEventsExt on GetInterface {
Future<T> toEnd<T>(FutureOr<T> computation()) async {
await Future.delayed(Duration.zero);
final val = computation();
return val;
}
FutureOr<T> asap<T>(T computation(), {bool Function() condition}) async {
T val;
if (condition == null || !condition()) {
await Future.delayed(Duration.zero);
val = computation();
} else {
val = computation();
}
return val;
}
}
... ...
... ... @@ -2,6 +2,7 @@ export 'context_extensions.dart';
export 'double_extensions.dart';
export 'duration_extensions.dart';
export 'dynamic_extensions.dart';
export 'event_loop_extensions.dart';
export 'internacionalization.dart';
export 'num_extensions.dart';
export 'string_extensions.dart';
... ...
name: get
description: Open screens/snackbars/dialogs/bottomSheets without context, manage states and inject dependencies easily with GetX.
version: 3.17.1
version: 3.21.1
homepage: https://github.com/jonataslaw/getx
environment:
... ...
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:get/get.dart';
import 'utils/wrapper.dart';
void main() {
testWidgets("Test dispose dependencies with unnamed routes", (tester) async {
await tester.pumpWidget(
Wrapper(child: Container()),
);
expect(Get.isRegistered<Controller2>(), false);
expect(Get.isRegistered<Controller>(), false);
Get.to(First());
await tester.pumpAndSettle();
expect(find.byType(First), findsOneWidget);
expect(Get.isRegistered<Controller>(), true);
Get.to(Second());
await tester.pumpAndSettle();
expect(find.byType(Second), findsOneWidget);
expect(Get.isRegistered<Controller>(), true);
expect(Get.isRegistered<Controller2>(), true);
Get.back();
await tester.pumpAndSettle();
expect(find.byType(First), findsOneWidget);
expect(Get.isRegistered<Controller>(), true);
expect(Get.isRegistered<Controller2>(), false);
Get.back();
await tester.pumpAndSettle();
expect(Get.isRegistered<Controller>(), false);
expect(Get.isRegistered<Controller2>(), false);
});
}
class Controller extends GetxController {}
class Controller2 extends GetxController {}
class First extends StatelessWidget {
@override
Widget build(BuildContext context) {
Get.put(Controller());
return Center(
child: Text("first"),
);
}
}
class Second extends StatelessWidget {
@override
Widget build(BuildContext context) {
Get.put(Controller2());
return Center(
child: Text("second"),
);
}
}
... ...
import 'package:flutter/cupertino.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:get/get.dart';
import 'get_main_test.dart';
class RedirectMiddleware extends GetMiddleware {
@override
RouteSettings redirect(String route) => RouteSettings(name: '/second');
}
void main() {
testWidgets("Middleware redirect smoke test", (tester) async {
await tester.pumpWidget(
GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => Container()),
GetPage(
name: '/first',
page: () => FirstScreen(),
middlewares: [RedirectMiddleware()]),
GetPage(name: '/second', page: () => SecondScreen()),
GetPage(name: '/third', page: () => ThirdScreen()),
],
),
);
Get.toNamed('/first');
await tester.pumpAndSettle();
print(Get.routing.current);
expect(find.byType(SecondScreen), findsOneWidget);
});
}
... ...
import 'package:flutter/cupertino.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:get/get.dart';
import 'package:get/get_navigation/src/root/parse_route.dart';
void main() {
test('Parse Page with children', () {
final tree = ParseRouteTree();
final pageTree = GetPage(name: '/city', page: () => Container(), children: [
GetPage(name: '/home', page: () => Container(), children: [
GetPage(name: '/bed-room', page: () => Container()),
GetPage(name: '/living-room', page: () => Container()),
]),
GetPage(name: '/work', page: () => Container(), children: [
GetPage(name: '/office', page: () => Container(), children: [
GetPage(name: '/pen', page: () => Container()),
GetPage(name: '/paper', page: () => Container()),
]),
GetPage(name: '/meeting-room', page: () => Container()),
]),
]);
tree.addRoute(pageTree);
final searchRoute = '/city/work/office/pen';
final match = tree.matchRoute(searchRoute);
expect(match, isNotNull);
expect(match.route.name, searchRoute);
});
test('Parse Page without children', () {
final tree = ParseRouteTree();
final pageTree = [
GetPage(name: '/city', page: () => Container()),
GetPage(name: '/city/home', page: () => Container()),
GetPage(name: '/city/home/bed-room', page: () => Container()),
GetPage(name: '/city/home/living-room', page: () => Container()),
GetPage(name: '/city/work', page: () => Container()),
GetPage(name: '/city/work/office', page: () => Container()),
GetPage(name: '/city/work/office/pen', page: () => Container()),
GetPage(name: '/city/work/office/paper', page: () => Container()),
GetPage(name: '/city/work/meeting-room', page: () => Container()),
];
for (var p in pageTree) {
tree.addRoute(p);
}
final searchRoute = '/city/work/office/pen';
final match = tree.matchRoute(searchRoute);
expect(match, isNotNull);
expect(match.route.name, searchRoute);
});
}
... ...