Jonny Borges
Committed by GitHub

Merge pull request #825 from jonataslaw/master

Update to 3.20.0
Showing 49 changed files with 3219 additions and 68 deletions
## [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
... ...
![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/get.png)
*Idiomas: Español (este archivo), [Lengua china](README.zh-cn.md), [Inglés](README.md), [Portugués de Brasil](README.pt-br.md), [Ruso](README.ru.md), [Polaco](README.pl.md).*
*Idiomas: Español (este archivo), [Lengua china](README.zh-cn.md), [Inglés](README.md), [Portugués de Brasil](README.pt-br.md), [Ruso](README.ru.md), [Polaco](README.pl.md), [Coreano](README.ko-kr.md).*
[![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get)
![building](https://github.com/jonataslaw/get/workflows/build/badge.svg)
... ... @@ -125,11 +125,13 @@ class Controller extends GetxController {
```dart
class Home extends StatelessWidget {
@override
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());
@override
Widget build(context) => Scaffold(
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 {
... ...
![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/get.png)
_Languages: [영어](README.md), [중국어](README.zh-cn.md), [브라질 포르투칼어](README.pt-br.md), [스페인어](README-es.md), [러시아어](README.ru.md), [폴란드어](README.pl.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)
![building](https://github.com/jonataslaw/get/workflows/build/badge.svg)
[![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](https://pub.dev/packages/effective_dart)
[![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N)
[![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://communityinviter.com/apps/getxworkspace/getx)
[![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g)
<a href="https://github.com/Solido/awesome-flutter">
<img alt="Awesome Flutter" src="https://img.shields.io/badge/Awesome-Flutter-blue.svg?longCache=true&style=flat-square" />
</a>
<a href="https://www.buymeacoffee.com/jonataslaw" target="_blank"><img src="https://i.imgur.com/aV6DDA7.png" alt="Buy Me A Coffee" style="height: 41px !important;width: 174px !important; box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" > </a>
![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/getx.png)
- [Get에 대하여](#Get에-대하여)
- [설치](#설치)
- [GetX를 사용한 Counter 앱](#GetX를-사용한-Counter-앱)
- [세가지 주요점](#세가지-주요점)
- [상태 관리](#상태-관리)
- [반응형 상태 관리자](#반응형-상태-관리자)
- [상태 관리에 대한 자세한 내용](#상태-관리에-대한-자세한-내용)
- [라우트 관리](#라우트-관리)
- [라우트 관리에 대한 자세한 내용](#라우트-관리에-대한-자세한-내용)
- [종속성 관리](#종속성-관리)
- [종속성 관리에 대한 자세한 내용](#종속성-관리에-대한-자세한-내용)
- [기능들](#기능들)
- [국제화](#국제화)
- [번역](#번역)
- [번역 사용법](#번역-사용법)
- [지역화](#지역화)
- [지역 변경](#지역-변경)
- [시스템 지역](#시스템-지역)
- [테마 변경](#테마-변경)
- [기타 고급 API](#기타-고급-API)
- [선택적 전역 설정과 수동 구성](#선택적-전역-설정과-수동-구성)
- [지역 상태 위젯들](#지역-상태-위젯들)
- [ValueBuilder](#valuebuilder)
- [ObxValue](#obxvalue)
- [유용한 팁](#유용한-팁)
- [GetView](#getview)
- [GetWidget](#getwidget)
- [GetxService](#getxservice)
- [2.0의 주요 변경점](#2.0의-주요-변경점)
- [왜-Getx인가?](#왜-Getx인가?)
- [커뮤니티](#커뮤니티)
- [커뮤니티 채널](#커뮤니티-채널)
- [기여하는 방법](#기여하는-방법)
- [기사 및 비디오](#기사-및-비디오)
# Get에 대하여
- GetX는 Flutter를 위한 매우 가볍고 강력한 솔루션입니다. 고성능 상태 관리, 지능형 종속성 주입 및 빠르고 실용적인 라우트 관리가 결합되어 있습니다.
- GetX는 라이브러리의 모든 사항에 대해서 **생산성, 성능, 조직화**의 3 가지 기본 원칙을 가지고 있습니다.
- **성능:** GetX는 성능과 최소한의 리소스 소비에 중점을 둡니다. GetX는 Streams나 ChangeNotifier를 사용하지 않습니다.
- **생산성:** GetX는 쉽고 친숙한 구문을 사용합니다. 원하시는 것보다 Getx에는 항상 더 쉬운 방법이 있습니다. 개발 시간을 아끼고 애플리케이션을 최대 성능으로 제공할 수 있습니다.
일반적으로 개발자는 메모리에서 컨트롤러들을 제거하는 데 관심을 가져야합니다. GetX에서는 리소스가 기본적으로 사용되지 않으면 메모리에서 제거되므로 필요하지 않습니다. 만약 메모리에 유지하려면 종속성에서 "permanent : true"를 명시적으로 선언해야합니다. 이렇게하면 시간을 절약 할 수있을뿐만 아니라 불필요한 메모리 종속성이 발생할 위험이 줄어 듭니다. 종속성은 기본적으로 lazy로 로드됩니다.
- **조직화:** GetX는 화면, 프레젠테이션 로직, 비즈니스 로직, 종속성 주입 및 네비게이션을 완전히 분리 할 수 있습니다. 라우트간 전환을 하는데에 컨텍스트가 필요하지 않아 위젯 트리(시각객체)에 독립적입니다. inheritedWidget을 통해 컨트롤러/블록에 접근하는 데 컨텍스트가 필요하지 않아 시각화 계층에서 프레젠테이션 로직과 비즈니스 로직을 완전히 분리됩니다. 이 GetX는 자체 종속성 주입 기능을 사용하여 DI를 뷰에서 완전히 분리하기 때문에 다중 Provider를 통해 위젯 트리에서 컨트롤러/모델/블록으로 주입 할 필요가 없습니다.
GetX를 사용하면 기본적으로 클린 코드를 가지게 되어 애플리케이션의 각 기능을 쉽게 찾을 수있습니다. 이것은 유지 보수를 용이하게 하며 모듈의 공유가 가능하고 Flutter에서는 생각할 수 없었던 것들도 전부 가능합니다.
BLoC은 Flutter에서 코드를 구성하기 위한 시작점으로 비즈니스 로직과 시각객체를 분리합니다. Getx는 비즈니스 로직 뿐만 아니라 프레젠테이션 로직을 분리하는 자연스러운 진화입니다. 추가로 종속성 주입과 라우트 또한 분리되고 데이터 계층이 모두로부터 분리됩니다. Hello World를 구현하는 것보다 더 쉽게 모든 것이 어디 있는지 알수 있습니다.
Flutter SDK와 함께 GetX를 사용하면 가장 쉽고 실용적이며 확장 가능한 고성능 어플리케이션을 만들수 있습니다. 초보자에게는 쉬우며 전문가에게는 정확하고 완벽하게 동작하는 대규모 생태계가 함께합니다. 안전하고 안정적이며 업데이트되고 기본 Flutter SDK에 없는 광범위한 API 빌드를 제공합니다.
- GetX는 비대하지 않습니다. 아무 걱정없이 프로그래밍을 시작할 수 있는 다양한 기능이 있지만 각 기능은 별도의 컨테이너에 있으며 사용한 후에만 시작됩니다. 만약 상태 관리만 사용하면 오직 상태 관리만 컴파일 됩니다. 라우트만 사용하는 경우 상태 관리는 컴파일되지 않습니다.
- GetX는 거대한 생태계, 대규모 커뮤니티, 수많은 공동 작업자를 보유하고 있으며 Flutter가 존재하는 한 유지됩니다. GetX도 Android, iOS, 웹, Mac, Linux, Windows 및 서버에서 동일한 코드로 실행할 수 있습니다.
**[Get Server](https://github.com/jonataslaw/get_server)를 사용한 백엔드에는 프런트엔드에서 만든 코드를 완전히 재사용 할 수 있습니다.**
**추가로 [Get CLI](https://github.com/jonataslaw/get_cli)를 프런트엔드와 서버 양쪽에서 사용하면 전체 개발 프로세스를 자동화 할 수 있습니다.**
**추가로 생산성 향상을 위해 [VSCode 확장](https://marketplace.visualstudio.com/items?itemName=get-snippets.get-snippets)과 [Android Studio/Intellij 확장](https://plugins.jetbrains.com/plugin/14975-getx-snippets)이 있습니다.**
# 설치
pubspec.yaml 파일에 Get 추가:
```yaml
dependencies:
get:
```
사용할 파일에 Import get:
```dart
import 'package:get/get.dart';
```
# GetX를 사용한 Counter 앱
Flutter의 새 프로젝트에서 기본적으로 생성 되는 "counter" 프로젝트는 100줄이 넘습니다 (코멘트 포함). Get의 힘을 보여주기 위해 클릭 할 때마다 상태를 변경하고, 페이지 사이를 전환하고, 화면 사이의 상태를 공유하는 "counter"를 만드는 방법을 주석이 포함된 26줄의 코드로 보여줄 것입니다.
- 1 단계:
MaterialApp 에 "Get"을 추가하여 GetMaterialApp 으로 변경합니다.
```dart
void main() => runApp(GetMaterialApp(home: Home()));
```
- 주석: 이는 Flutter의 MaterialApp을 변경하지 않으며 GetMaterialApp 또한 수정 된 MaterialApp이 아니고, 기본 MaterialApp을 자식으로 갖는 사전 구성된 위젯 일뿐입니다. 수동으로 구성 할 수 있지만 반드시 필요한 것은 아닙니다. GetMaterialApp은 라우트를 생성하고 추가하며, 번역을 추가하고, 라우트 탐색에 필요한 모든 것을 추가합니다. 만약 상태 관리 또는 종속성 관리에만 Get을 사용하는 경우 GetMaterialApp을 사용할 필요가 없습니다. GetMaterialApp은 라우트, 스택바, 국제화, bottomSheets, 다이얼로그 및 컨텍스트 부재와 라우트에 연관된 상위 api들에 필요합니다.
- 주석²: 이 단계는 라우트 관리 (`Get.to ()`,`Get.back ()` 등)를 사용하려는 경우에만 필요합니다. 사용하지 않을 경우 1 단계를 수행 할 필요가 없습니다.
- 2 단계:
비즈니스 로직 클래스를 만들고 모든 변수, 함수, 컨트롤러를 포함하십시오.
".obs"를 이용하면 간단히 모든 변수를 observable로 만들수 있습니다.
```dart
class Controller extends GetxController{
var count = 0.obs;
increment() => count++;
}
```
- 3 단계:
StatelessWidget를 이용해 View를 만들어 RAM을 아끼고 StatefulWidget은 더 이상 사용하지 않아도 됩니다.
```dart
class Home extends StatelessWidget {
@override
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}"))),
// 8줄의 Navigator.push를 간단한 Get.to()로 변경합니다. context는 필요없습니다.
body: Center(child: RaisedButton(
child: Text("Go to Other"), onPressed: () => Get.to(Other()))),
floatingActionButton:
FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment));
}
}
class Other extends StatelessWidget {
// 다른 페이지에서 사용되는 컨트롤러를 Get으로 찾아서 redirect 할 수 있습니다.
final Controller c = Get.find();
@override
Widget build(context){
// 업데이트된 count 변수에 연결
return Scaffold(body: Center(child: Text("${c.count}")));
}
}
```
결론:
![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/counter-app-gif.gif)
이것은 간단한 프로젝트 이지만 Get이 얼마나 강력한지 명확히 보여줍니다. 프로젝트가 성장하면 차이점이 더 커질 것 입니다.
Get은 팀단위 업무에 맞춰 디자인되었지만 개별 개발자의 작업도 단순화합니다.
마감일을 개선하고 성능의 손실 없이 재시간에 제공하십시오. Get은 모두를 위한 것은 아니지만 위의 설명에 해당사항이 있으면 당신을 위한 것입니다!
# 세가지 주요점
## 상태 관리
Get은 두가지 상태 관리자가 있습니다: 단순 상태관리자(GetBuilder라고 함)와 반응형 상태관리자(GetX/Obx)
### 반응형 상태 관리자
반응형 프로그래밍은 복잡하다고 알려져있기 때문에 많은 사람들에게 소외될 수 있습니다. GetX가 매우 단순하게 반응형 프로그래밍을 바꿉니다:
- StreamControllers를 만들 필요가 없습니다.
- 각 변수에 대해 StreamBuilder를 만들 필요가 없습니다.
- 각각의 상태(state)를 위한 클래스를 만들 필요가 없습니다.
- 초기값을 위한 get이 필요하지 않습니다.
- 코드 생성기를 사용할 필요가 없스빈다.
Get의 반응형 프로그램은 setState를 사용하는 것 만큼 쉽습니다.
매번 변경되기를 원하고 모든 위젯에서 자동적으로 반영되는 변수가 있다고 가정해봅시다.
여기 name 변수가 있습니다:
```dart
var name = 'Jonatas Borges';
```
".obs"만 끝에 추가하여 observable로 만듭니다:
```dart
var name = 'Jonatas Borges'.obs;
```
아래와 같이 간단히 보여주고 싶은 UI에 추가하면 값이 변경될때마다 화면에 업데이트 됩니다:
```dart
Obx(() => Text("${controller.name}"));
```
이게 다 입니다. _정말_ 간단하죠.
### 상태 관리에 대한 자세한 내용
**상태 관리에 대한 자세한 설명은 [여기](./documentation/en_US/state_management.md)를 보십시오. 여기에서 더 많은 예제와 단순 상태 관리자와 반응형 상태 관리자의 차이점을 볼 수 있습니다.**
GetX 능력에 대한 좋은 아이디어를 얻을 수 있습니다.
## 라우트 관리
만약 라우트/스낵바/다이얼로그/bottomsheets을 context 없이 사용하려면 GetX는 훌륭한 대안입니다. 여기를 보십시오:
MaterialApp 앞에 "Get"을 추가해서 GetMaterialApp으로 변경합니다.
```dart
GetMaterialApp( // Before: MaterialApp(
home: MyHome(),
)
```
새로운 화면으로 이동합니다:
```dart
Get.to(NextScreen());
```
명칭으로 새로운 화면으로 이동합니다. 명칭으로 라우트하는 더 자세한 사항은 [여기](./documentation/en_US/route_management.md#navigation-with-named-routes) 있습니다.
```dart
Get.toNamed('/details');
```
스낵바, 다이얼로그, bottomsheets 또는 Navigator.pop(context);로 닫아야 하는 어떤것도 닫게 합니다:
```dart
Get.back();
```
다음 화면으로 이동하고 이전 화면으로 돌아갈 필요가 없는 경우 (스플래시, 로그인화면 등..)
```dart
Get.off(NextScreen());
```
다음 화면으로 이동하고 이전 화면들 모두 닫는 경우 (쇼핑카트, 투표, 테스트에 유용)
```dart
Get.offAll(NextScreen());
```
이러한 작업을 수행하기 위해 컨텍스트를 사용할 필요가 없다는 것을 보셨나요? 이것이 Get 라우트 관리를 사용하는 가장 큰 장점 중 하나입니다. 이를 통해 걱정없이 컨트롤러 클래스 내에서 이러한 모든 메서드를 실행할 수 있습니다.
### 라우트 관리에 대한 자세한 내용
**Get은 명명된 라우트로 작업하고 더 쉬운 방식으로 라우트의 제어를 제공합니다! [여기](./documentation/en_US/route_management.md)에 더 자세한 문서가 있습니다.**
## 종속성 관리
Get은 간단하고 강력한 종속성 관리자를 가지고 있어 Bloc나 Controller와 유사한 클래스를 Provide context, inheritedWidget 없이 1줄의 코드로 끌어낼 수 있습니다:
```dart
Controller controller = Get.put(Controller()); // Rather Controller controller = Controller();
```
- 주석: Get의 상태 관리자를 사용중이면 뷰를 controller에 더 쉽게 연결할 수 있는 바인딩 api에 더 주의를 기울이십시오.
사용 중인 클래스에서 클래스를 인스턴스화하는 대신에 Get 인스턴스에서 인스턴스화하면 앱에서 해당 클래스를 사용할 수 있습니다.
그래서 controller(또는 Bloc)를 정상적으로 사용할 수 있습니다.
**팁:** Get 종속성 관리는 패키지의 다른 부분과 분리되어서 예제 앱이 이미 상태 관리자(하나여도 상관없음)를 사용중이면 모두 다시 작성할 필요 없이 아무 문제 없이 종속성 주입을 사용할 수 있습니다.
```dart
controller.fetchApi();
```
여러 경로를 통해 이동했고 controller에 남아있는 데이터가 필요가 있다고 가정하십시오. Get_it이나 Provider와 조합된 상태 관리자가 필요합니다. 맞습니까? Get은 아닙니다. 다른 추가적인 종속성이 필요없이 controller를 Get의 "find"로 찾으면 됩니다:
```dart
Controller controller = Get.find();
//마법처럼 Get이 controller를 찾아서 가져올 것 입니다. 백만개의 인스턴스화 contrller를 가질수 있고 Get은 올바른 controller를 항상 가져다 줄 것입니다.
```
그리고나서 가져온 controller 데이터를 사용할 수 있습니다:
```dart
Text(controller.textFromApi);
```
### 종속성 관리에 대한 자세한 내용
**종속성 관리에 대한 더 제사한 사항은 [여기](./documentation/en_US/dependency_management.md)에 있습니다.**
# 기능들
## 국제화
### 번역
번역은 간단한 key-value 맵으로 유지됩니다.
커스텀 번역을 추가하려면 `Translations`으로 확장하여 클래스를 만드세요.
```dart
import 'package:get/get.dart';
class Messages extends Translations {
@override
Map<String, Map<String, String>> get keys => {
'en_US': {
'hello': 'Hello World',
},
'de_DE': {
'hello': 'Hallo Welt',
}
};
}
```
#### 번역 사용법
단지 `.tr`로 명시된 키만 추가하면 `Get.locale`과 `Get.fallbackLocale`의 현재값을 사용해서 번역될 것 입니다.
```dart
Text('title'.tr);
```
### 지역화
`GetMaterialApp`의 파라미터를 전달하여 지역과 번역어를 정의합니다.
```dart
return GetMaterialApp(
translations: Messages(), // 번역들
locale: Locale('en', 'US'), // 해당 지역의 번역이 표시
fallbackLocale: Locale('en', 'UK'), // 잘못된 지역이 선택된 경우 복구될 지역을 지정
);
```
#### 지역 변경
지역을 업데이트할때 `Get.updateLocale(locale)`를 콜하십시오. 새로운 지역을 사용하여 자동적으로 번역합니다.
```dart
var locale = Locale('en', 'US');
Get.updateLocale(locale);
```
#### 시스템 지역
`Get.deviceLocale`를 사용해서 시스템 지역을 읽어옵니다.
```dart
return GetMaterialApp(
locale: Get.deviceLocale,
);
```
## 테마 변경
테마를 업데이트하기 위해 `GetMaterialApp` 보다 더 상위 위젯을 사용하지 말아 주십시오. 이러면 중복 키가 트리거 될 수 있습니다. 많은 사람들이 테마를 바꾸기 위해 "ThemeProvider" 위젯을 사용하고 있는데 **GetX**는 이런 방식이 필요 없습니다.
다른 표준사항은 없이 `Get.changeTheme`로 추가하고 간단하게 커스텀 테마를 만들수 있습니다:
```dart
Get.changeTheme(ThemeData.light());
```
`onTap`에 테마 변경이 있는 버튼 같은 무언가를 만들고 싶다면 두개의 **GetX™** API를 조합하여 가능합니다:
- 다크`테마`를 사용중인지 확인합니다.
- 그리고 `테마` 변경 API 를 `onPressed`에 넣으면 됩니다:
```dart
Get.changeTheme(Get.isDarkMode? ThemeData.light(): ThemeData.dark());
```
`.darkmode`가 활성활 될때 _light theme_ 로 바뀔것 이고 _light theme_ 가 활성화되면 _dark theme_ 로 변경될 것입니다.
## 기타 고급 API
```dart
// currentScreen에서 현재 인수들을 제공
Get.arguments
// 이전 경로의 이름을 제공
Get.previousRoute
// rawRoute.isFirst()와 같은 접근에 필요한 원시 경로를 제공
Get.rawRoute
// GetObserver로 부터 Rounting API의 접근을 제공
Get.routing
// snackbar가 열려 있는지 확인
Get.isSnackbarOpen
// dialog가 열려 있는지 확인
Get.isDialogOpen
// bottomsheet가 열려 있는지 확인
Get.isBottomSheetOpen
// 1개의 경로 제거
Get.removeRoute()
// 값이 true가 될때까지 반복적으로 되돌림
Get.until()
// 다음 경로로 이동하고 값이 true가 될때까지 이전 모든 경로를 제거
Get.offUntil()
// 명명된 다음 경로로 이동하고 값이 true가 될때까지 이전 모든 경로를 제거
Get.offNamedUntil()
// 앱이 구동중인 플랫폼을 확인
GetPlatform.isAndroid
GetPlatform.isIOS
GetPlatform.isMacOS
GetPlatform.isWindows
GetPlatform.isLinux
GetPlatform.isFuchsia
// 장치 타입을 확인
GetPlatform.isMobile
GetPlatform.isDesktop
// 모든 플랫폼은 독립적으로 웹을 제공합니다!
// Windows, iOS, OSX, Android 등의
// 브러우저에서 구동중이면 알 수 있습니다.
GetPlatform.isWeb
// MediaQuery.of(context).size.height 과 동일
// 하지만 불변함.
Get.height
Get.width
// Navigator의 현재 context를 제공
Get.context
// 코드 어디에서든지 foreground에서 snackbar/dialog/bottomsheet의 context를 제공
Get.contextOverlay
// 주석: 다음 메소드는 context의 확장입니다.
// UI의 모든 위치에서 컨텍스트에 액세스 할 수 있으므로 UI 코드의 어느 곳에서나 사용할 수 있습니다.
// 변경되는 height/width(데스크탑이나 브라우저와 같이 늘어날 수 있는 것)가 필요하면 context를 사용해야함
context.width
context.height
// 화면의 절반, 1/3 등을 정의할 수 있는 기능을 제공합니다.
// 반응성이 높은 앱에 유용합니다.
// param dividedBy (double) optional - default: 1
// param reducedBy (double) optional - default: 0
context.heightTransformer()
context.widthTransformer()
/// MediaQuery.of(context).size 와 유사함
context.mediaQuerySize()
/// MediaQuery.of(context).padding 와 유사함
context.mediaQueryPadding()
/// MediaQuery.of(context).viewPadding 와 유사함
context.mediaQueryViewPadding()
/// MediaQuery.of(context).viewInsets; 와 유사함
context.mediaQueryViewInsets()
/// MediaQuery.of(context).orientation; 와 유사함
context.orientation()
/// 장치의 가로 모드 확인
context.isLandscape()
/// 장치의 세로 모드 확인
context.isPortrait()
/// MediaQuery.of(context).devicePixelRatio; 와 유사함
context.devicePixelRatio()
/// MediaQuery.of(context).textScaleFactor; 와 유사함
context.textScaleFactor()
/// 화면에서 shortestSide를 제공
context.mediaQueryShortestSide()
/// True if width be larger than 800
context.showNavbar()
/// shortestSide가 600p 미만이면 True
context.isPhone()
/// shortestSide가 600p 이상이면 True
context.isSmallTablet()
/// shortestSide가 720p 이상이면 True
context.isLargeTablet()
/// 현재 장치가 Tablet이면 True
context.isTablet()
/// 화면 사이즈에 따라 value<T>를 반환
/// 반환될 수 있는 값들:
/// watch: shortestSide가 300 미만일 때
/// mobile: shortestSide가 600 미만일 때
/// tablet: shortestSide가 1200 미만일 때
/// desktop: shortestSide가 1200 이상일 때
context.responsiveValue<T>()
```
### 선택적 전역 설정과 수동 구성
GetMaterialApp은 모든 것이 구성되어 있지만 원한다면 수동으로 Get을 구성할 수 있습니다.
```dart
MaterialApp(
navigatorKey: Get.key,
navigatorObservers: [GetObserver()],
);
```
`GetObserver`안에 Middleware를 사용할 수 있고 이로 인한 어떤 영향도 없습니다.
```dart
MaterialApp(
navigatorKey: Get.key,
navigatorObservers: [
GetObserver(MiddleWare.observer) // Here
],
);
```
`Get`을 위한 _Global Settings_ 을 만들수 있습니다. 어떠한 라우트도 포함되기 전에 `Get.config`에 추가하십시오.
또는 `GetMaterialApp`에 직접 추가 하십시오.
```dart
GetMaterialApp(
enableLog: true,
defaultTransition: Transition.fade,
opaqueRoute: Get.isOpaqueRouteDefault,
popGesture: Get.isPopGestureEnable,
transitionDuration: Get.defaultDurationTransition,
defaultGlobalState: Get.defaultGlobalState,
);
Get.config(
enableLog = true,
defaultPopGesture = true,
defaultTransition = Transitions.cupertino
)
```
선택적으로 `Get`으로 모든 로그 메세지를 리다이렉트 할 수 있습니다.
만약 유명한 로그 패키지를 사용하고 싶으면
여기에서 원하는 로그가 있습니다:
```dart
GetMaterialApp(
enableLog: true,
logWriterCallback: localLogWriter,
);
void localLogWriter(String text, {bool isError = false}) {
// pass the message to your favourite logging package here
// please note that even if enableLog: false log messages will be pushed in this callback
// you get check the flag if you want through GetConfig.isLogEnable
}
```
### 지역 상태 위젯들
이러한 위젯은 단일값을 관리하고 지역적이고 임시적인 상태를 유지합니다.
우리는 반응적이고 단순함을 위해 추가할 수 있습니다.
예를 들어 `TextField`의 obscureText의 전환으로 사용하거나
커스텀된 확장되는 패널을 만들거나
`Scaffold`의 body가 변경되는 동안 `BottomNavigationBar`의 현재 index를 수정할 수 있습니다.
#### ValueBuilder
업데이트된 값을 되돌려 받는 `.setState`로 작동하는 `StatefulWidget`의 단순화 입니다.
```dart
ValueBuilder<bool>(
initialValue: false,
builder: (value, updateFn) => Switch(
value: value,
onChanged: updateFn, // same signature! you could use ( newValue ) => updateFn( newValue )
),
// if you need to call something outside the builder method.
onUpdate: (value) => print("Value updated: $value"),
onDispose: () => print("Widget unmounted"),
),
```
#### ObxValue
[`ValueBuilder`](#valuebuilder)와 비슷하지만 Rx 인스턴스(마법같은 .obs를 기억하세요)를 전달하고 자동적으로 업데이트되는 반응형 버전입니다... 놀랍지 않습니까?
```dart
ObxValue((data) => Switch(
value: data.value,
onChanged: data, // Rx에는 호출가능한 함수가 있습니다! (flag) => data.value = flag, 가 사용가능 합니다.
),
false.obs,
),
```
## 유용한 팁
`.obs`(_Rx_ 타입이라고 알려진)는 다양한 내부 메소드와 연산자가 있습니다.
> `.obs`프로퍼티가 **실제 값**이라고 _믿는_ 것은 일반적이지만 실수하지 마십시오!
> 다트의 컴파일러는 충분히 똑똑하고 코드가 깔끔하기 때문에 변수의 타입 선언을 하지 않습니다.
> 하지만:
```dart
var message = 'Hello world'.obs;
print( 'Message "$message" has Type ${message.runtimeType}');
```
`message`가 실제 String 값을 _출력_ 하더라도 타입은 **RxString**입니다!
그래서 `message.substring( 0, 4 )`은 사용하지 못합니다.
_observable(.obs)_ 안의 실제 값에 접근해야 합니다:
가장 많이 사용되는 방법은 `.value`지만 사용할 수 있었는지 알고 있었나요...
```dart
final name = 'GetX'.obs;
// 현재 값과 다른 값이면 stream을 업데이트만 합니다.
name.value = 'Hey';
// 모든 Rx 프로퍼티가 "호출 가능"하고 새로운 값을 반환합니다.
// 하지만 이 접근방식은 `null`를 허용하지 않고 UI가 재구축하지 않습니다.
name('Hello');
// getter와 과 같이 'Hello'를 출력합니다.
name() ;
/// 숫자 타입들:
final count = 0.obs;
// 기존 숫자 타입으로 모든 변형 불가 작업을 사용할수 있습니다.
count + 1;
// 주의하세요! 아래는 `count`가 final이 아닌 경우에만 유효합니다.
count += 1;
// 값들을 비교할 수도 있습니다:
count > 2;
/// booleans:
final flag = false.obs;
// true/false 사이의 전환이 됩니다.
flag.toggle();
/// 모든 타입들:
// `값`을 null로 셋합니다.
flag.nil();
// 모든 toString(), toJson() 함수들은 `값`으로 전달됩니다.
print( count ); // RxInt 내부에서 `toString()`이 호출됩니다.
final abc = [0,1,2].obs;
// 값을 json 배열로 바꾸고 RxList를 출력합니다.
// Json은 모든 Rx 타입들을 지원합니다!
print('json: ${jsonEncode(abc)}, type: ${abc.runtimeType}');
// RxMap, RxList 그리고 RxSet은 그들의 native 타입들을 확장한 특별한 Rx 타입들입니다.
// 반응형이긴 하지만 일반 list로서 RxList가 동작합니다!
abc.add(12); // list에 12가 들어가고 stream을 업데이트합니다.
abc[3]; // List와 같이 인덱스 3을 읽습니다.
// 동등비교는 Rx와 값에서 동작하지만 해시코드는 항상 값으로부터 받습니다.
final number = 12.obs;
print( number == 12 ); // prints > true
/// 커스텀 Rx 모델들:
// toJson(), toString()은 child에게 지연됩니다. 그래서 이것들을 재정의 하고 직접 관찰하여 print() 할수 있습니다.
class User {
String name, last;
int age;
User({this.name, this.last, this.age});
@override
String toString() => '$name $last, $age years old';
}
final user = User(name: 'John', last: 'Doe', age: 33).obs;
// `user`는 "반응형"이지만 내부 프로퍼티들은 아닙니다!
// 그래서 만약 내부의 변수를 바꾸면...
user.value.name = 'Roi';
// 위젯은 재구성 되지 않을것 입니다!
// user의 내부의 무언가가 바뀌어도 `Rx`는 알 수가 않습니다.
// 그래서 커스텀 클래스들은 수동으로 바뀌었다고 "알릴" 필요가 있습니다.
user.refresh();
// 또는 `update()` 함수를 사용할 수 있습니다!
user.update((value){
value.name='Roi';
});
print( user );
```
#### GetView
이 위젯을 사랑합니다. 매우 간단하고 유용합니다!
등록된 `Controller`인 `controller`의 gettr로 가져온 `const Stateless`위젯 입니다. 이게 전부입니다.
```dart
class AwesomeController extends GetxController {
final String title = 'My Awesome View';
}
// controller를 등록할때 사용한 `타입`을 전달하는 것을 항상 기억하세요!
class AwesomeView extends GetView<AwesomeController> {
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(20),
child: Text( controller.title ), // 단지 `controller.something`을 호출합니다.
);
}
}
```
#### GetWidget
대부분의 사람들이 이 위젯에대해 모르거나 사용법을 완전히 혼동합니다.
사용 사례는 매우 드물지만 매우 구체적입니다: Controller를 `caches`합니다.
_cache_ 이기 때문에 `const Stateless`가 될 수 없습니다.
> 그러면 언제 Controller를 "cache"할 필요가 있을까요?
만약 **GetX**의 기능 중 또 다른 "흔하지 않은" 기능을 사용하는 경우:`Get.create()`
`Get.create(()=>Controller())`가 `Get.find<Controller>()`을 호출할 때마다 새로운 `Controller`를 생성할 것 입니다.
여기서 `GetWidget`이 빛나게 됩니다... 예를 들어 Todo 리스트를 유지하려고 사용할 때 입니다.
위젯이 "재구성"될때 동일한 controller 인스턴스를 유지할 것입니다.
#### GetxService
이 클래스틑 `GetxController`와 같이 동일한 생성주기(`onInit()`, `onReady()`, `onClose()`)를 공유합니다.
하지만 이안에 "로직"은 없습니다. 단지 **GetX** 종속성 주입 시스템이 하위클래스를 메모리에서 삭제할 수 없음을 알립니다.
그래서 `Get.find()`로 활성화하고 항상 접근하는 "서비스들"을 유지하는데 매우 유용합니다. :
`ApiService`, `StorageService`, `CacheService`.
```dart
Future<void> main() async {
await initServices(); /// 서비스들 초기화를 기다림.
runApp(SomeApp());
}
/// 플러터 앱이 실행되기 전에 서비스들을 초기화하는 현명한 방법입니다.
/// 실행 흐름을 제어 할수 있으므로(테마 구성, apiKey, 사용자가 정의한 언어등을 로드해야 할 필요가 있으므로
/// ApiService의 구동전에 SettingService를 로드해야 합니다.
/// 그래서 GetMaterialApp()은 재구성하지 않고 직접적으로 값을 가져옵니다.
void initServices() async {
print('starting services ...');
/// 여기에서 get_storage, hive, shared_pref 초기화를 하세요.
/// 또는 연결 고정 또는 비동기적인 무엇이든 하세요.
await Get.putAsync(() => DbService().init());
await Get.putAsync(SettingsService()).init();
print('All services started...');
}
class DbService extends GetxService {
Future<DbService> init() async {
print('$runtimeType delays 2 sec');
await 2.delay();
print('$runtimeType ready!');
return this;
}
}
class SettingsService extends GetxService {
void init() async {
print('$runtimeType delays 1 sec');
await 1.delay();
print('$runtimeType ready!');
}
}
```
`GetxService`를 실질적으로 지우는 한가지 방법은 앱의 "Hot Reboot"과 같은 `Get.reset()`뿐 입니다.
따라서 앱 실행중 절대로 유지되어야 하는 클래스 인스턴스가 필요하면 `GetxService`를 사용하세요.
# 2.0의 주요 변경점
1- Rx 타입들:
| 이전 | 이후 |
| ------- | ---------- |
| StringX | `RxString` |
| IntX | `RxInt` |
| MapX | `RxMap` |
| ListX | `RxList` |
| NumX | `RxNum` |
| DoubleX | `RxDouble` |
RxController와 GetBuilder는 합쳐졌습니다. 더이상 사용할 controller를 기억시킬 필요가 없습니다. GetxController를 사용하세요. 단순 및 반응형 상태관리 모두에서 잘 동작합니다.
2- 명명된 라우트
이전:
```dart
GetMaterialApp(
namedRoutes: {
'/': GetRoute(page: Home()),
}
)
```
지금:
```dart
GetMaterialApp(
getPages: [
GetPage(name: '/', page: () => Home()),
]
)
```
무엇이 달라졌습니까?
종종 매개 변수 또는 로그인 토큰에 의해 표시 할 페이지를 결정해야 할 수 있습니다. 이전 접근 방식은 이를 허용하지 않았기 때문에 유연하지 않았습니다.
페이지를 함수에 삽입하면 앱이 시작된 이후 라우트가 메모리에 할당되지 않고 이러한 유형의 접근 방식이 가능하기 때문에 RAM 소비가 크게 감소했습니다:
```dart
GetStorage box = GetStorage();
GetMaterialApp(
getPages: [
GetPage(name: '/', page:(){
return box.hasData('token') ? Home() : Login();
})
]
)
```
# 왜 Getx인가?
1- 플러터가 업데이트된 이후 자주 많은 패키지가 깨졌을 것입니다. 때때로 컴파일중 에러가 발생하고 종종 이에 대해 답변을 해줄 사람이 없었을 겁니다. 그리고 개발자는 에러가 어디에서 발생했는지 추적해서 알아야합니다. 그리고 오직 리포지트리를 통해서 이슈를 오픈하고 해결책을 찾아야합니다. Get은 개발을 위한 주 리소스들(상태, 종속성, 라우트 관리)을 중앙화합니다. pubspec에 단일 패키지를 추가하고 작업을 시작 할 수 있습니다. 플러터가 업데이트 된 이후에도 Get 의존을 업데이트하면 작업할 수 있습니다. Get은 호환성 이슈도 해결합니다. 한 버전에서 종속적으로 사용하여 다른 버전에서 다른 버전을 사용할때 패키지 버전이 다른 버전과 호환되지 않는 경우가 몇 번입니까? 모든 것이 동일한 패키지에 있고 완벽하게 호환되므로 Get을 사용하면 문제가 되지 않습니다.
2- 플러터는 쉽고 놀랍지만 대다수의 개발자들이 원하지 않는 몇가지 상용구가 있습니다. `Navigator.of(context).push (context, builder [...]` 같은 것들 입니다. Get은 개발을 단순화합니다. 라우트를 위해 8줄의 코드를 작성하고 `Get.to(Home())`만 하면 다음 페이지로 갈 수 있습니다. 동적 웹 url은 현재 플러터에서 정말로 고통스러운 것이고 GetX로 하는 것은 정말 간단합니다. 플러터에서 상태와 종속성을 관리하는 것은 pub에서 수백가지의 패턴이라 많은 논의를 생산합니다. 그러나 변수 끝에 ".obs"를 추가하고 위젯을 Obx 안에 배치하는 것만큼 쉬운 것은 없습니다. 이것으로 해당 변수가 업데이트되면 화면에 자동으로 업데이트됩니다.
3- 성능에 대하여 걱정하지 않아도 됩니다. 플러터의 성능은 이미 놀랍습니다. 하지만 상태관리자를 사용하고 blocs/stores/controllers 등의 클래스들을 로케이터로 배포하는 것을 상상해보십시오. 종속성이 필요 없는 경우 종속성 제외를 수동으로 호출해야 합니다. 하지만 간단하게 controller를 사용하고 이것들을 더이상 사용하지 않을때 간단하게 메모리에서 삭제될수 있을까요? 이것이 GetX가 하는 일입니다. SmartManagement를 사용하면 사용하지 않는 모든것이 메모리에서 삭제되기 때문에 프로그래밍 말고 다른 걱정을 할 필요가 없습니다. 이러한 로직을 만들지 않고도 최소한의 리소스만 사용함을 보장합니다.
4- 실질적으로 분리됨. "비즈니스 로직으로부터 뷰를 분리"라는 개념을 들어보셨을 겁니다. 이것은 BLoC, MVC, MVVM의 특징이 아니며 이미 나와 있는 또 다른 표준 개념입니다. 그러나 이 개념은 context의 사용으로 인해 플러터에서 종종 완화됩니다.
만약 InheritedWidget을 찾기 위해 context가 필요하면 뷰나 파라미터로 conetext를 전달해야 합니다. 저는 특히 이 방식이 매우 별로이고 팀의 작업이 항상 뷰의 비즈니스 로직에 의존하게 됩니다. GetX는 표준 접근에서 비정통적이고 StatefulWidgets, InitState 등의 사용을 완전 배제하지 않지만 항상 깔끔하게 유사한 접근을 제공합니다. 예를 들어 Controllers의 수명주기에서 APIREST 요청이 필요할 때 뷰에 어떤 것도 의존할 필요가 없습니다. http 호출의 초기화를 onInit 에서 사용가능 하고 데이터가 도착하면 변수들이 채워집니다. GetX는 완전히 반응형이며(실제 스트림으로 동작) 아이탬중 하나가 채워지면 이 변수를 사용중인 모든 위젯이 자동적으로 화면에서 갱신됩니다. 이를 통해 UI 전문가는 위젯으로만 작업하고 사용자 이벤트(예 : 버튼 클릭) 이외의 비즈니스 로직에 아무것도 보낼 필요가 없으며 비즈니스 로직을 개발하는 사람들은 비즈니스 로직을 별도로 만들고 테스트 할 수 있습니다.
이 라이브러리는 항상 업데이트되고 새로운 기능이 포함됩니다. 자유롭게 PR을 제공하고 여기에 기여하세요.
# 커뮤니티
## 커뮤니티 채널
GetX에는 매우 활동적이고 유용한 커뮤니티가 있습니다. 이 프레임워크의 사용과 관련하여 질문이 있거나 도움이 필요한 경우 커뮤니티 채널에 가입하십시오. 질문에 대한 답변이 더 빨리 제공되며 가장 적합한 장소가 될 것입니다. 이 저장소는 이슈오픈 및 리소스 요청 전용이지만 GetX 커뮤니티의 일부에 속해있습니다.
| **Slack** | **Discord** | **Telegram** |
| :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------- |
| [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://communityinviter.com/apps/getxworkspace/getx) | [![Discord Shield](https://img.shields.io/discord/722900883784073290.svg?logo=discord)](https://discord.com/invite/9Hpt99N) | [![Telegram](https://img.shields.io/badge/chat-on%20Telegram-blue.svg)](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g) |
## 기여하는 방법
_프로젝트에 기여하고 싶으신가요? 우리는 귀하를 우리의 협력자 중 한 명으로 부각시켜 자랑스럽게 생각합니다. 다음은 Get(그리고 플러터)을 더욱 향상시키고 기여할 수 있는 몇 가지 사항입니다._
- readme을 다른 언어로 번역하는 데 도움이 됩니다.
- readme에 문서를 추가합니다(Get의 많은 기능이 아직 문서화되지 않았습니다).
- Get 사용법을 설명하는 기사를 쓰거나 비디오를 만듭니다(읽기 및 향후 위키에 삽입될 예정).
- 코드/테스트에 대한 PR을 제공합니다.
- 새로운 기능을 포함합니다.
어떤 기여도 환영합니다!
## 기사 및 비디오
- [Dynamic Themes in 3 lines using GetX™](https://medium.com/swlh/flutter-dynamic-themes-in-3-lines-c3b375f292e3) - Tutorial by [Rod Brown](https://github.com/RodBr).
- [Complete GetX™ Navigation](https://www.youtube.com/watch?v=RaqPIoJSTtI) - Route management video by Amateur Coder.
- [Complete GetX State Management](https://www.youtube.com/watch?v=CNpXbeI_slw) - State management video by Amateur Coder.
- [GetX™ Other Features](https://youtu.be/ttQtlX_Q0eU) - Utils, storage, bindings and other features video by Amateur Coder.
- [Firestore User with GetX | Todo App](https://www.youtube.com/watch?v=BiV0DcXgk58) - Video by Amateur Coder.
- [Firebase Auth with GetX | Todo App](https://www.youtube.com/watch?v=-H-T_BSgfOE) - Video by Amateur Coder.
- [The Flutter GetX™ Ecosystem ~ State Management](https://medium.com/flutter-community/the-flutter-getx-ecosystem-state-management-881c7235511d) - State management by [Aachman Garg](https://github.com/imaachman).
- [The Flutter GetX™ Ecosystem ~ Dependency Injection](https://medium.com/flutter-community/the-flutter-getx-ecosystem-dependency-injection-8e763d0ec6b9) - Dependency Injection by [Aachman Garg](https://github.com/imaachman).
- [GetX, the all-in-one Flutter package](https://www.youtube.com/watch?v=IYQgtu9TM74) - A brief tutorial covering State Management and Navigation by Thad Carnevalli.
- [Build a To-do List App from scratch using Flutter and GetX](https://www.youtube.com/watch?v=EcnqFasHf18) - UI + State Management + Storage video by Thad Carnevalli.
- [GetX Flutter Firebase Auth Example](https://medium.com/@jeffmcmorris/getx-flutter-firebase-auth-example-b383c1dd1de2) - Article by Jeff McMorris.
- [Flutter State Management with GetX – Complete App](https://www.appwithflutter.com/flutter-state-management-with-getx/) - by App With Flutter.
- [Flutter Routing with Animation using Get Package](https://www.appwithflutter.com/flutter-routing-using-get-package/) - by App With Flutter.
... ...
![](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)._
**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,9 @@ _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)
- [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 +126,13 @@ class Controller extends GetxController{
```dart
class Home extends StatelessWidget {
@override
Widget build(context) {
// 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(
return Scaffold(
// Use Obx(()=> to update Text() whenever count is changed.
appBar: AppBar(title: Obx(() => Text("Clicks: ${c.count}"))),
... ... @@ -136,6 +141,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 {
... ... @@ -202,7 +208,7 @@ That's all. It's _that_ simple.
### More details about state management
**See an more in-depth explanation of state management [here](./documentation/en_US/state_management.md). There you will see more examples and also the difference between the simple stage manager and the reactive state manager**
**See an more in-depth explanation of state management [here](./documentation/en_US/state_management.md). There you will see more examples and also the difference between the simple state manager and the reactive state manager**
You will get a good idea of GetX power.
... ... @@ -377,6 +383,83 @@ 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() {
@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);
}
```
## Other Advanced APIs
```dart
... ... @@ -389,7 +472,7 @@ Get.previousRoute
// give the raw route to access for example, rawRoute.isFirst()
Get.rawRoute
// give access to Rounting API from GetObserver
// give access to Routing API from GetObserver
Get.routing
// check if snackbar is open
... ... @@ -731,7 +814,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`
);
}
}
... ... @@ -801,7 +884,7 @@ class SettingsService extends GetxService {
```
The only way to actually delete a `GetxService`, is with `Get.reset()` which is like a
"Hot Reboot" of your app. So remember, if you need absolute persistance of a class instance during the
"Hot Reboot" of your app. So remember, if you need absolute persistence of a class instance during the
lifetime of your app, use `GetxService`.
# Breaking changes from 2.0
... ...
![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/get.png)
*Languages: [English](README.md), [Język chiński](README.zh-cn.md), [Brazilian Portuguese](README.pt-br.md), [Spanish](README-es.md), [Russian](README.ru.md), Polish (Jesteś tu).*
*Languages: [English](README.md), [Język chiński](README.zh-cn.md), [Brazilian Portuguese](README.pt-br.md), [Spanish](README-es.md), [Russian](README.ru.md), Polish (Jesteś tu), [Koreański](README.ko-kr.md).*
[![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get)
![building](https://github.com/jonataslaw/get/workflows/build/badge.svg)
... ... @@ -102,11 +102,13 @@ Tworzymy View. Użyj StatelessWidget oszczędzajac przy tym RAM. Z Get nie będz
```dart
class Home extends StatelessWidget {
@override
Widget build(context) {
// 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(
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 {
... ...
![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/get.png)
*Idiomas: [Inglês](README.md), [Chinês](README.zh-cn.md), Português Brasileiro (este arquivo), [Espanhol](README-es.md), [Russo](README.ru.md), [Polonês](README.pl.md).*
*Idiomas: [Inglês](README.md), [Chinês](README.zh-cn.md), Português Brasileiro (este arquivo), [Espanhol](README-es.md), [Russo](README.ru.md), [Polonês](README.pl.md), [Coreana](README.ko-kr.md).*
[![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get)
![building](https://github.com/jonataslaw/get/workflows/build/badge.svg)
... ... @@ -124,16 +124,23 @@ Crie sua View usando StatelessWidget, já que, usando Get, você não precisa ma
```dart
class Home extends StatelessWidget {
@override
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());
@override
Widget build(context) => Scaffold(
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));
floatingActionButton:
FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment));
}
}
class Outra extends StatelessWidget {
... ...
![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/get.png)
_Языки: Русский (этот файл), [Английский](README.md), [Китайский](README.zh-cn.md), [Бразильский Португальский](README.pt-br.md), [Испанский](README-es.md), [Польский](README.pl.md)._
_Языки: Русский (этот файл), [Английский](README.md), [Китайский](README.zh-cn.md), [Бразильский Португальский](README.pt-br.md), [Испанский](README-es.md), [Польский](README.pl.md), [Kорейский](README.ko-kr.md)._
[![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get)
![building](https://github.com/jonataslaw/get/workflows/build/badge.svg)
... ... @@ -118,11 +118,13 @@ class Controller extends GetxController{
```dart
class Home extends StatelessWidget {
@override
Widget build(context) {
// 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(
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 {
... ...
![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/get.png)
_语言: 中文, [英文](README.md), [巴西葡萄牙语](README.pt-br.md), [俄语](README.ru.md), [西班牙语](README-es.md), [波兰语](README.pl.md)_
_语言: 中文, [英文](README.md), [巴西葡萄牙语](README.pt-br.md), [俄语](README.ru.md), [西班牙语](README-es.md), [波兰语](README.pl.md), [韩国语](README.ko-kr.md)._
[![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get)
![building](https://github.com/jonataslaw/get/workflows/build/badge.svg)
... ... @@ -122,11 +122,13 @@ class Controller extends GetxController{
```dart
class Home extends StatelessWidget {
@override
Widget build(context) {
// 使用Get.put()实例化你的类,使其对当下的所有子路由可用。
final Controller c = Get.put(Controller());
@override
Widget build(context) => Scaffold(
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 {
... ...
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>),
... ...
... ... @@ -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>> post<T>(
String url,
dynamic body, {
String contentType,
Map<String, String> headers,
Map<String, dynamic> query,
});
Future<Response<T>> put<T>(
String url,
Map<String, dynamic> body, {
String contentType,
Map<String, String> headers,
Map<String, dynamic> query,
});
Future<Response<T>> delete<T>(
String url, {
Map<String, String> headers,
String contentType,
Map<String, dynamic> query,
});
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,
headers: headers,
contentType: contentType,
query: query,
decoder: decoder,
);
}
@override
Future<Response<T>> put<T>(
String url,
Map<String, dynamic> body, {
String contentType,
Map<String, String> headers,
Map<String, dynamic> query,
Decoder<T> decoder,
}) {
_checkIfDisposed();
return httpClient.put(
url,
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 {
assert(method != null);
assert(body != null);
List<int> bodyBytes;
if (body is FormData) {
bodyBytes = await body.toBytes();
} else {
try {
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);
} on Exception catch (err) {
if (!errorSafety) {
throw UnexpectedFormat(err.toString());
} else {}
}
}
final bodyStream = BodyBytes.fromBytes(bodyBytes);
final headers = <String, String>{};
_setHeadersWithBody(contentType, headers, bodyBytes);
final uri = _createUri(url, query);
return Request(
method: method,
url: uri,
headers: headers,
bodyBytes: bodyStream,
followRedirects: followRedirects,
maxRedirects: maxRedirects,
);
}
void _setHeadersWithBody(
String contentType,
// String jsonString,
Map<String, String> headers,
List<int> bodyBytes,
// List<MultipartFile> files,
) {
// if (files != null) {
// headers['content-type'] = 'multipart/form-data';
// headers['x-requested-with'] = 'XMLHttpRequest';
// } else {
// headers['content-type'] = contentType ?? defaultContentType;
// }
headers['content-type'] =
contentType ?? defaultContentType; // verify if this is better location
headers['user-agent'] = userAgent;
headers['content-length'] = bodyBytes.length.toString();
}
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,
// List<MultipartFile> files,
}) {
assert(body != null);
return _requestWithBody<T>(
url,
contentType,
body,
'post',
query,
decoder,
);
}
Future<Request<T>> _put<T>(
String url, {
String contentType,
@required dynamic body,
@required Map<String, dynamic> query,
Decoder<T> decoder,
// List<MultipartFile> files,
}) {
assert(body != null);
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>> put<T>(
String url,
Map<String, 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');
... ...
... ... @@ -34,6 +34,7 @@ class GetPageRoute<T> extends PageRoute<T> {
assert(barrierDismissible != null),
assert(maintainState != null),
assert(fullscreenDialog != null),
reference = "$routeName: ${page.hashCode}",
super(settings: settings, fullscreenDialog: fullscreenDialog);
@override
... ... @@ -43,6 +44,8 @@ class GetPageRoute<T> extends PageRoute<T> {
final String routeName;
final String reference;
final CustomTransition customTransition;
final Bindings binding;
... ... @@ -110,7 +113,7 @@ class GetPageRoute<T> extends PageRoute<T> {
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
Get.reference = settings.name ?? routeName;
// Get.reference = settings.name ?? routeName;
binding?.dependencies();
if (bindings != null) {
for (final binding in bindings) {
... ... @@ -372,11 +375,15 @@ 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();
}
}
... ...
... ... @@ -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;
});
... ... @@ -205,7 +208,8 @@ class GetObserver extends NavigatorObserver {
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;
});
... ...
... ... @@ -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;
}
... ...
... ... @@ -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.20.0
homepage: https://github.com/jonataslaw/getx
environment:
... ...