new change to use intaleq_map sdk 04-16-4

This commit is contained in:
Hamza-Ayed
2026-04-16 19:45:03 +03:00
parent 0aa1f15f25
commit a54a7a4189
850 changed files with 83282 additions and 3075 deletions

1433
packages/get/CHANGELOG.md Normal file

File diff suppressed because it is too large Load Diff

21
packages/get/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Jonny Borges
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

1307
packages/get/README-ar.md Normal file

File diff suppressed because it is too large Load Diff

578
packages/get/README-es.md Normal file
View File

@@ -0,0 +1,578 @@
![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/get.png)
*Idiomas: Español (este archivo), [Vietnamita](README-vi.md), [Indonesio](README.id-ID.md), [Urdu](README.ur-PK.md), [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), [Francés](README-fr.md).*
[![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get)
[![popularity](https://badges.bar/get/popularity)](https://pub.dev/packages/sentry/score)
[![likes](https://badges.bar/get/likes)](https://pub.dev/packages/get/score)
[![pub points](https://badges.bar/get/pub%20points)](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)
<h3>Lamentamos la inconsistencia en la traducción. El paquete GetX se actualiza con bastante frecuencia y es posible que las traducciones a documentos no sean tan rápidas. Entonces, para que esta documentación aún tenga todo el contenido, dejaré aquí todos los textos nuevos sin traducir (considero que es mejor tener los documentos en inglés que no tenerlos), por lo que si alguien quiere traducir, sería de gran ayuda 😁</h3>
- [Sobre GetX](#sobre-getx)
- [Instalación](#instalación)
- [Proyecto contador con GetX](#proyecto-contador-con-getx)
- [Los tres pilares](#los-tres-pilares)
- [Gestión del Estado](#gestión-del-estado)
- [Reactivo STATE_MANAGER](#reactivo-state_manager)
- [Más detalles sobre la gestión del estado.](#más-detalles-sobre-la-gestión-del-estado)
- [Explicación en video sobre state management](#explicación-en-video-sobre-state-management)
- [Gestión de Rutas](#gestión-de-rutas)
- [Más detalles sobre la gestión de rutas.](#más-detalles-sobre-la-gestión-de-rutas)
- [Explicación del video](#explicación-del-video)
- [Gestión de dependencias](#gestión-de-dependencias)
- [Más detalles sobre la gestión de dependencias.](#más-detalles-sobre-la-gestión-de-dependencias)
- [Utilidades](#utilidades)
- [Cambiar de tema](#cambiar-de-tema)
- [Otras API avanzadas y configuraciones manuales](#otras-api-avanzadas-y-configuraciones-manuales)
- [Configuraciones globales opcionales](#configuraciones-globales-opcionales)
- [Video explanation of Other GetX Features](#video-explanation-of-other-getx-features)
- [Cambios importantes desde 2.0](#cambios-importantes-desde-20)
- [¿Por qué Getx?](#por-qué-getx)
- [Comunidad](#comunidad)
- [Canales de la comunidad](#canales-de-la-comunidad)
- [Cómo contribuir](#cómo-contribuir)
- [Artículos y vídeos](#artículos-y-vídeos-inglés)
# Sobre GetX
- GetX es una solución extra ligera y potente para Flutter. Combina gestión de estádo de alto rendimiento, inyección de dependencia inteligente y gestión de rutas de forma rápida y práctica.
- GetX tiene 3 principios básicos, esto significa que esta es la prioridad para todos los recursos de la biblioteca.
- **RENDIMIENTO:** GetX se centra en el rendimiento y el consumo mínimo de recursos. Los puntos de referencia casi siempre no son importantes en el mundo real, pero si lo desea, aquí hay un indicador de consumo.([benchmarks](https://github.com/jonataslaw/benchmarks)), donde GetX lo hace mejor que otros enfoques de gestión estatal, por ejemplo. La diferencia no es grande, pero muestra nuestra preocupación por no desperdiciar sus recursos.
- **PRODUCTIVIDAD:** GetX utiliza una sintaxis fácil y agradable.
- **ORGANIZACIÓN:** GetX permite el desacoplamiento total de la vista de la lógica de negocio.
* GetX ahorrará horas de desarrollo y extraerá el máximo rendimiento que su aplicación puede ofrecer, siendo fácil para los principiantes y precisa para los expertos. Navega sin contexto, abre diálogos, snackbars o bottomsheets desde cualquier lugar de tu código, gestiona estados e inyecta dependencias de forma fácil y práctica. Get es seguro, estable, actualizado y ofrece una amplia gama de API que no están presentes en el marco predeterminado.
- GetX no es bloated. Tiene una multitud de características que le permiten comenzar a programar sin preocuparse por nada, pero cada una de estas características se encuentran en contenedores separados y solo se inician después de su uso. Si solo usa State Management, solo se compilará State Management. Si solo usa rutas, no se compilará nada de la administración estatal. Puede compilar el repositorio de referencia y verá que al usar solo la administración de estado de Get, la aplicación compilada con Get se ha vuelto más pequeña que todas las demás aplicaciones que solo tienen la administración de estado de otros paquetes, porque nada que no se use se compilará en su código, y cada solución GetX fue diseñada para ser muy liviana. El mérito aquí también proviene del movimiento del árbol de Flutter, que es increíble y logra eliminar los recursos no utilizados como ningún otro marco lo hace.
**GetX hace que su desarrollo sea productivo, pero ¿quiere hacerlo aún más productivo? [Agregue la extensión a su VSCode](https://marketplace.visualstudio.com/items?itemName=get-snippets.get-snippets)**
# Instalación
Añada la librería Get en tu archivo pubspec.yaml:
```yaml
dependencies:
get:
```
Importe Get en los archivos en los que se utilizará:
```dart
import 'package:get/get.dart';
```
# Proyecto Contador con GetX
Vea una explicación más detallada de la administración del estado [aquí](./documentation/es_ES/state_management.md). Allí verá más ejemplos y también la diferencia entre el Gestión del Estado simple y el Gestión del Estado reactivo
El proyecto "contador" creado por defecto en un nuevo proyecto en Flutter tiene más de 100 líneas (con comentarios). Para mostrar el poder de GetX, demostraré cómo hacer un "contador" cambiando el estado con cada clic, cambiando de página y compartiendo el estado entre pantallas, todo de manera organizada, separando la vista de la lógica de negocio, SOLO 26 LÍNEAS DE CÓDIGO INCLUIDOS COMENTARIOS.
- Paso 1: Agregue "Get" antes de su materialApp, convirtiéndolo en GetMaterialApp
```dart
void main() => runApp(GetMaterialApp(home: Home()));
```
**Nota**: esto no modifica el MaterialApp del Flutter, GetMaterialApp no es una MaterialApp modificado, es solo un Widget preconfigurado que tiene como child un MaterialApp por defecto. Puede configurar esto manualmente, pero definitivamente no es necesario. GetMaterialApp creará rutas, las inyectará, inyectará traducciones, inyectará todo lo que necesita para la navegación de rutas. Si usa Get solo para la gestión de estado o dependencias, no es necesario usar GetMaterialApp. GetMaterialApp es necesario para rutas, snackbars, internacionalización, bottomSheets, diálogos y APIs de alto nivel relacionadas con rutas y ausencia de contexto.
**Nota²:** Este paso solo es necesario si va a usar route management (`Get.to()`, `Get.back()` y así). Si no lo va a usar, no es necesario que realice el paso 1
- Paso 2: Cree su clase con la lógica de negocio colocando todas las variables, métodos y controladores dentro de ella. Puede hacer que cualquier variable sea observable usando un simple ".obs".
```dart
class Controller extends GetxController {
var count = 0.obs;
increment() => count.value++;
}
```
- Paso 3: Cree su vista, use StatelessWidget y ahorre algo de RAM, con GetX ya no necesitará usar StatefulWidget.
```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());
return Scaffold(
// Utilice Obx(()=> para actualizar Text() siempre que se cambie el recuento.
appBar: AppBar(title: Obx(() => Text("Clicks: " + c.count.string))),
// Reemplace el Navigator.push de 8 líneas por un simple Get.to(). No necesitas contexto
body: Center(child: ElevatedButton(
child: Text("Go to Other"), onPressed: () => Get.to(Other()))),
floatingActionButton:
FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment));
}
}
class Other extends StatelessWidget {
// Puede pedirle a Get que busque un controlador que está siendo utilizado por otra página y le redirija a él.
final Controller c = Get.find();
@override
Widget build(context){
// Acceder a la variable de recuento actualizada
return Scaffold(body: Center(child: Text(c.count.string)));
}
}
```
Resultado:
![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/counter-app-gif.gif)
Este es un proyecto simple pero ya deja en claro cuán poderoso es GetX. A medida que su proyecto crezca, esta diferencia se volverá más significativa. GetX fue diseñado para trabajar con equipos, pero también simplifica el trabajo de un desarrollador individual. Mejore sus plazos, entregue todo a tiempo, sin perder rendimiento. GetX no es para todos, pero si te identificaste con esa frase, ¡GET es para ti!
# Los tres pilares
## Gestión del Estado
Actualmente hay varios State Managers para Flutter. Sin embargo, con la mayoría de ellos implica utilizar ChangeNotifier para actualizar widgets y este es un enfoque malo y muy malo para el rendimiento de aplicaciones medianas o grandes. Puede verificar en la documentación oficial de Flutter que [ChangeNotifier debe usarse con 1 o un máximo de 2 listeners](https://api.Flutter.dev/Flutter/foundation/ChangeNotifier-class.html), por lo que es prácticamente inutilizable para cualquier aplicación mediana o grande.
GetX no es mejor ni peor que cualquier otro gestor de estado, pero debe analizar estos puntos, así como los puntos que se mencionan a continuación, para elegir entre usar GetX en forma pura (vanilla) o usarlo junto con otro gestor de estado.
Definitivamente, GetX no es enemigo de ningún otro gestor de estado, porque GetX es más bien un microframework, no solo un gestor de estado, y se puede usar solo o en combinación con ellos.
### Reactivo STATE_MANAGER
La programación reactiva puede alienar a muchas personas porque se dice que es complicada. GetX convierte la programación reactiva en algo tan simple que puede ser aprendido y utilizado por aquellos que comenzaron en ese mismo momento en Flutter. No, no necesitará crear StreamControllers. Tampoco necesitará crear un StreamBuilder para cada variable. No necesitará crear una clase para cada estado. No necesitará crear un get para un valor inicial. La programación reactiva con GetX es tan fácil como usar setState (¡o incluso más fácil!).
Imaginemos que tiene una variable "name" y desea que cada vez que la modifique, todos los widgets que la usan cambien automáticamente.
Ej. esta es tu variable "name":
```dart
var name = 'Jonatas Borges';
```
Para que sea observable, solo necesita agregar ".obs" al final:
```dart
var name = 'Jonatas Borges'.obs;
```
¿StreamBuilder? ¿initialValue? ¿builder? No, solo necesitas jugar con esta variable dentro de un widget Obx.
```dart
Obx(() => Text (controller.name));
```
### Más detalles sobre la gestión del estado.
**Vea una explicación más detallada de la administración del estado [aquí](./documentation/es_ES/state_management.md). Allí verá más ejemplos y también la diferencia entre el Gestión del Estado simple y el Gestión del Estado reactivo**
### Explicación en video sobre state management
Darwin Morocho hizo una increíble serie de videos sobre state management! Link: [Complete GetX State Management](https://www.youtube.com/watch?v=PTjj0DFK8BA&list=PLV0nOzdUS5XtParoZLgKoVwNSK9zROwuO)
Obtendrá una buena idea de la potencia de GetX.
## Gestión de Rutas
Para navegar a una nueva pantalla:
```dart
Get.to(NextScreen());
```
Para cerrar snackbars, dialogs, bottomsheets o cualquier cosa que normalmente cierre con Navigator.pop(contexto);
```dart
Get.back();
```
Para ir a la siguiente pantalla, sin opción a volver (util por ejemplo en SplashScreens, LoginScreen, etc.)
```dart
Get.off(NextScreen());
```
Para ir a la siguiente pantalla y cancelar todas las rutas anteriores (útil en carritos de compras, encuestas y exámenes)
```dart
Get.offAll(NextScreen());
```
Para navegar a la siguiente ruta y recibir o actualizar datos tan pronto como se regrese de ella:
```dart
var data = await Get.to(Payment());
```
### Más detalles sobre la gestión de rutas.
**Vea una explicación más detallada de la Gestión de Rutas [aquí](./documentation/es_ES/route_management.md).**
### Explicación del video
Amateur Coder hizo un excelente video que cubre route management con Get! aquí esta el link: [Complete Getx Navigation](https://www.youtube.com/watch?v=RaqPIoJSTtI)
## Gestión de dependencias
- Nota: si está utilizando el gestor de estado de GetX, no tiene que preocuparse por esto, solo lea para obtener información, pero preste más atención a la API de bindings, que hará todo esto automáticamente por usted.
¿Ya estás utilizando GetX y quieres que tu proyecto sea lo más ágil posible? GetX tiene un gestor de dependencias simple y poderoso que le permite recuperar la misma clase que su BLoC o Controller con solo una líneas de código, sin contexto de Provider, sin inheritedWidget:
```dart
Controller controller = Get.put(Controller()); // Rather Controller controller = Controller();
```
En lugar de crear una instancia de su clase dentro de la clase que está utilizando, la está creando dentro de la instancia GetX, que la hará disponible en toda su aplicación. Entonces puede usar su Controller (o BLoC) normalmente.
```dart
controller.fetchApi();
```
Imagine que ha navegado a través de numerosas rutas y necesita datos que quedaron en su controlador, necesitaría un gestor de estado combinado con Provider o Get_it, ¿correcto? No con GetX. Solo necesita pedirle a GetX que "encuentre" su controlador, no necesita dependencias adicionales:
```dart
Controller controller = Get.find();
//Sí, parece que es magia, Get encontrará su controlador y se lo entregará. Puede tener 1 millón de controladores instanciados, Get siempre le dará el controlador correcto.
```
Y luego podrá recuperar los datos de su controlador que se obtuvieron allí:
```dart
Text(controller.textFromApi);
```
¿Buscando lazy loading? Puede declarar todos sus controladores, y se llamará solo cuando alguien lo necesite. Puedes hacer esto con:
```dart
Get.lazyPut<Service>(()=> ApiMock());
/// ApiMock solo se llamará cuando alguien use Get.find<Service> por primera vez
```
### Más detalles sobre la gestión de dependencias.
**Vea una explicación más detallada de la Gestión de dependencias [aquí](./documentation/es_ES/dependency_management.md).**
# Utilidades
## Cambiar de tema
No utilice ningún widget de nivel superior que GetMaterialApp para actualizarlo. Esto puede activar claves duplicadas. Mucha gente está acostumbrada al enfoque prehistórico de crear un widget "ThemeProvider" solo para cambiar el tema de su aplicación, y esto definitivamente NO es necesario con GetX.
Puede crear su tema personalizado y simplemente agregarlo dentro de Get.changeTheme sin ningún boilerplate para eso:
```dart
Get.changeTheme(ThemeData.light());
```
Si desea crear algo así como un botón que cambia el tema con onTap, puede combinar dos APIs GetX para eso, la API que verifica si se está utilizando el tema oscuro y la API de cambio de tema, simplemente puede poner esto dentro de un onPressed:
```dart
Get.changeTheme(Get.isDarkMode? ThemeData.light(): ThemeData.dark());
```
Cuando el modo oscuro está activado, cambiará al tema claro, y cuando el tema claro esté activado, cambiará a oscuro.
Si quieres saber en profundidad cómo cambiar el tema, puedes seguir este tutorial en Medium que incluso enseña la persistencia del tema usando GetX:
- [Temas dinámicos en 3 líneas usando GetX](https://medium.com/swlh/flutter-dynamic-themes-in-3-lines-c3b375f292e3) - Tutorial de [Rod Brown](https://github.com/RodBr).
## Otras API avanzadas y configuraciones manuales
GetMaterialApp configura todo para usted, pero si desea configurar GetX manualmente utilizando APIs avanzadas.
```dart
MaterialApp(
navigatorKey: Get.key,
navigatorObservers: [GetObserver()],
);
```
También podrá usar su propio Middleware dentro de GetObserver, esto no influirá en nada.
```dart
MaterialApp(
navigatorKey: Get.key,
navigatorObservers: [GetObserver(MiddleWare.observer)], // Here
);
```
```dart
// dar los argumentos actuales de currentScreen
Get.arguments
// dar el nombre de la ruta anterior
Get.previousRoute
// dar la ruta sin procesar para acceder, por ejemplo, rawRoute.isFirst()
Get.rawRoute
// dar acceso a Routing API desde GetObserver
Get.routing
// comprobar si la cafetería está abierta
Get.isSnackbarOpen
// comprobar si el diálogo está abierto
Get.isDialogOpen
// comprobar si bottomsheet está abierto
Get.isBottomSheetOpen
// eliminar una ruta.
Get.removeRoute()
// volver repetidamente hasta que predicate devuelva verdadero.
Get.until()
//ir a la siguiente ruta y eliminar todas las rutas anteriores hasta que predicate devuelva verdadero.
Get.offUntil()
// ir a la siguiente ruta con nombre y eliminar todas las rutas anteriores hasta que predicate devuelve verdadero.
Get.offNamedUntil()
//Verifique en qué plataforma se ejecuta la aplicación
GetPlatform.isAndroid
GetPlatform.isIOS
GetPlatform.isWeb
// Equivalente al método: MediaQuery.of(context).size.height, pero son inmutables.
Get.height
Get.width
// Da el contexto de la pantalla en primer plano en cualquier parte de su código.
Get.context
// Da el contexto de la barra de bocadillos / diálogo / hoja inferior en primer plano en cualquier parte de su código.
Get.contextOverlay
// Note: los siguientes métodos son extensiones de context. Desde que tu
// tiene acceso al contexto en cualquier lugar de su interfaz de usuario, puede usarlo en cualquier lugar del código de la interfaz de usuario
// Si necesita un cambiable height/width (como las ventanas del navegador que se pueden escalar) necesitará usar context.
context.width
context.height
// le da el poder de definir la mitad de la pantalla ahora, un tercio y así sucesivamente.
// Útil para aplicaciones receptivas.
// param dividedBy (double) optional - default: 1
// param reducedBy (double) optional - default: 0
context.heightTransformer()
context.widthTransformer()
/// Similar a MediaQuery.of(context).size
context.mediaQuerySize()
/// similar a MediaQuery.of(context).padding
context.mediaQueryPadding()
/// similar a MediaQuery.of(context).viewPadding
context.mediaQueryViewPadding()
/// similar a MediaQuery.of(context).viewInsets;
context.mediaQueryViewInsets()
/// similar a MediaQuery.of(context).orientation;
context.orientation()
/// comprobar si el dispositivo esta en landscape mode
context.isLandscape()
/// comprobar si el dispositivo esta en portrait mode
context.isPortrait()
/// similar a MediaQuery.of(context).devicePixelRatio;
context.devicePixelRatio()
/// similar a MediaQuery.of(context).textScaleFactor;
context.textScaleFactor()
/// obtener el lado más corto de la pantalla
context.mediaQueryShortestSide()
/// Verdadero si el ancho es mayor que 800
context.showNavbar()
/// Verdadero si el lado más corto es menor que 600p
context.isPhone()
/// Verdadero si el lado más corto es más grande que 600p
context.isSmallTablet()
/// Verdadero si el lado más corto es mayor que 720p
context.isLargeTablet()
/// Verdadero si el dispositivo actual es una tableta
context.isTablet()
```
### Configuraciones globales opcionales
Puede crear configuraciones globales para GetX. Simplemente agregue Get.config a su código antes de insertar cualquier ruta o hágalo directamente en su 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
)
```
Opcionalmente, puede redirigir todos los mensajes de registro de Get. Si desea utilizar su propio paquete de registro favorito y desea capturar los registros allí.
```dart
GetMaterialApp(
enableLog: true,
logWriterCallback: localLogWriter,
);
void localLogWriter(String text, {bool isError = false}) {
// pase el mensaje a su paquete de registro favorito aquí
//Nota: incluso si los mensajes de registro están desactivados
// con el comando "enableLog: false", los mensajes seguirán pasando por aquí
// Debe verificar esta configuración manualmente aquí si desea respetarla
}
```
## Video explanation of Other GetX Features
Amateur Coder hizo un video asombroso sobre utilidades, almacenamiento, enlaces y otras características! Link: [GetX Other Features](https://youtu.be/ttQtlX_Q0eU)
# Cambios importantes desde 2.0
1- Rx types:
| Antes | Ahora |
| ------- | ---------- |
| StringX | `RxString` |
| IntX | `RxInt` |
| MapX | `RxMap` |
| ListX | `RxList` |
| NumX | `RxNum` |
| DoubleX | `RxDouble` |
RxController y GetBuilder ahora se han fusionado, ya no necesita memorizar qué controlador desea usar, solo use GetXController, funcionará para gestión de estádo simple y también para reactivo.
2- Rutas Nombradas
Antes:
```dart
GetMaterialApp(
namedRoutes: {
'/': GetRoute(page: Home()),
}
)
```
Ahora:
```dart
GetMaterialApp(
getPages: [
GetPage(name: '/', page: () => Home()),
]
)
```
¿Por qué este cambio?
A menudo, puede ser necesario decidir qué página se mostrará desde un parámetro o un token de inicio de sesión, el enfoque anterior era inflexible, ya que no permitía esto.
Insertar la página en una función ha reducido significativamente el consumo de RAM, ya que las rutas no se asignarán en la memoria desde que se inició la aplicación, y también permitió hacer este tipo de enfoque:
```dart
GetStorage box = GetStorage();
GetMaterialApp(
getPages: [
GetPage(name: '/', page:(){
return box.hasData('token') ? Home() : Login();
})
]
)
```
# ¿Por qué Getx?
1- Después de una actualización de Flutter, muchos paquetes suelen romperse. A veces se producen errores de compilación, errores de los que aún no hay respuestas y el desarrollador necesita saber el origen del error, poder rastrear, y solo entonces intentar abrir un issue en el repositorio correspondiente, para finalmente ver su problema resuelto. Getx centraliza los principales recursos para el desarrollo (gestión de estado, dependencia y rutas), lo que le permite agregar un único paquete a su pubspec y comenzar a trabajar. Después de una actualización de Flutter, lo único que debe hacer es actualizar la dependencia Get y ponerse a trabajar. Get también resuelve problemas de compatibilidad. ¿Cuántas veces una versión de un paquete no es compatible con la versión de otro, porque una usa una dependencia en una versión y la otra en otra? Tampoco es una preocupación usando Get, ya que todo estará en el mismo paquete y será totalmente compatible.
2- Flutter es fácil, Flutter es increíble, pero todavía tiene algo repetitivo que puede ser no deseado para la mayoría de los desarrolladores, como `Navigator.of(context).push (context, builder [...]`. Get simplifica el desarrollo. En lugar de escribir 8 líneas de código para simplemente llamar a una ruta, simplemente puede hacerlo: `Get.to(Home())` y listo, irá a la página siguiente. Algo doloroso de hacer con Flutter actualmente, mientras que con GetX es estúpidamente simple. Gestionar estados en Flutter y dependencias también es algo que genera mucho debate, ya que hay cientos de patrones en el pub. Pero no hay nada tan fácil como agregar un ".obs" al final de su variable, y colocar su widget dentro de un Obx, y eso es todo, todas las actualizaciones de esa variable se actualizarán automáticamente en la pantalla.
3- Facilidad sin preocuparse por el rendimiento. El rendimiento de Flutter ya es sorprendente, pero imagine que usa un gestor de estado y un localizador para distribuir sus clases de bloc/stores/controllers/ etc. Tendrá que llamar manualmente a la exclusión de esa dependencia cuando no la necesite. Pero, ¿alguna vez pensó en simplemente usar el controlador, y cuando ya no sea necesario, simplemente se elimine de la memoria? Eso es lo que hace GetX. Con SmartManagement, todo lo que no se está utilizando se elimina de la memoria, y no debería tener que preocuparse por nada más que la programación. Se le garantiza el consumo mínimo de recursos, sin siquiera haber creado una lógica para esto.
4- Desacoplamiento real. Es posible que haya escuchado la idea de "separar la vista de la lógica de negocio". Esta no es una peculiaridad de BLoC, MVC, MVVM, cualquier otro estándar en el mercado tiene este concepto. Sin embargo, a menudo se puede mitigar en Flutter debido al uso del contexto.
Si necesita contexto para encontrar un InheritedWidget, lo necesita en la vista o pasado por parámetro. En particular, encuentro esta solución muy fea, y para trabajar en equipo siempre tendremos una dependencia de la lógica de negocios de la vista. Getx no es ortodoxo con el enfoque estándar, y aunque no prohíbe completamente el uso de StatefulWidgets, InitState, etc., siempre tiene un enfoque similar que puede ser más limpio. Los controladores tienen ciclos de vida, y cuando necesita hacer una solicitud API REST, por ejemplo, no depende de nada en la vista. Puede usar onInit para iniciar la llamada http, y cuando lleguen los datos, se rellenarán las variables. Como GetX es completamente reactivo (realmente, y funciona bajo streams), una vez que se llenan los elementos, todos los widgets que usan esa variable se actualizarán automáticamente en la vista. Esto permite que las personas con experiencia en IU trabajen solo con widgets y no tengan que enviar nada a la lógica de negocios que no sean eventos de usuario (como hacer clic en un botón), mientras que las personas que trabajan con lógica de negocios podrán crearla y probarla por separado.
Esta librería siempre se actualizará e implementará nuevas características. Siéntase libre de ofrecer PRs y contribuir a ellas.
# Comunidad
## Canales de la comunidad
GetX tiene una comunidad muy activa e implicada. Si tiene dudas, o necesita cualquier tipo de asistencia sobre el uso de este framework, no dude en unirse a nuestr, tu duda será resuelta lo antes posible. Este repositorio es de uso exclusivo para abrir issues, pero siéntase libre de unirse a la Comunidad de GetX.
| **Slack (🇬🇧)** | **Discord (🇬🇧 y 🇵🇹)** | **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) |
# Cómo contribuir
_¿Quieres contribuir al proyecto? Estaremos orgullosos de destacarte como uno de nuestros colaboradores. Aquí hay algunos puntos en los que puede contribuir y hacer que GetX (y Flutter) sea aún mejor._
- Ayudando a traducir el archivo Léame a otros idiomas.
- Agregar documentación al archivo Léame (ni siquiera la mitad de las funciones de GetX han sido documentadas todavía).
- Escriba artículos o haga videos que enseñen cómo usar GetX (se insertarán en el archivo Léame y en el futuro en nuestro Wiki).
- Ofreciendo PRs para código/pruebas.
- Incluyendo nuevas funciones.
¡Cualquier contribución es bienvenida!
## Artículos y vídeos (inglés)
- [Flutter Getx EcoSystem package for arabic people](https://www.youtube.com/playlist?list=PLV1fXIAyjeuZ6M8m56zajMUwu4uE3-SL0) - Tutorial by [Pesa Coder](https://github.com/UsamaElgendy).
- [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.
- [A minimal example on dartpad](https://dartpad.dev/2b3d0d6f9d4e312c5fdbefc414c1727e?) - by [Roi Peker](https://github.com/roipeker)

1134
packages/get/README-fr.md Normal file

File diff suppressed because it is too large Load Diff

1176
packages/get/README-vi.md Normal file

File diff suppressed because it is too large Load Diff

1113
packages/get/README.id-ID.md Normal file

File diff suppressed because it is too large Load Diff

1256
packages/get/README.ja-JP.md Normal file

File diff suppressed because it is too large Load Diff

1266
packages/get/README.ko-kr.md Normal file

File diff suppressed because it is too large Load Diff

1276
packages/get/README.md Normal file

File diff suppressed because it is too large Load Diff

547
packages/get/README.pl.md Normal file
View File

@@ -0,0 +1,547 @@
![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/get.png)
*Languages: [English](README.md), [Wietnamski](README-vi.md), [Indonezyjski](README.id-ID.md), [Urdu](README.ur-PK.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), [French](README-fr.md)*
[![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get)
[![popularity](https://badges.bar/get/popularity)](https://pub.dev/packages/sentry/score)
[![likes](https://badges.bar/get/likes)](https://pub.dev/packages/get/score)
[![pub points](https://badges.bar/get/pub%20points)](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)
- [Kanały komunikacji i wsparcia:](#kanały-komunikacji-i-wsparcia)
- [Wprowadzenie](#wprowadzenie)
- [Instalacja](#instalacja)
- [Counter App z GetX](#counter-app-z-getx)
- [Trzy filary](#trzy-filary)
- [Menadżer stanu](#menadżer-stanu)
- [Reaktywny menadżer stanu](#reaktywny-menadżer-stanu)
- [Bardziej szczegółowo o menadżerze stanu](#bardziej-szczegółowo-o-menadżerze-stanu)
- [Video tłumaczące użycie menadżera stanu](#video-tłumaczące-użycie-menadżera-stanu)
- [Zarządzanie routami](#zarządzanie-routami)
- [Więcej o routach](#więcej-o-routach)
- [Video tłumaczące użycie](#video-tłumaczące-użycie)
- [Zarządzanie dependencies](#zarządzanie-dependencies)
- [Bardziej szczegółowo o menadżerze dependencies](#bardziej-szczegółowo-o-menadżerze-dependencies)
- [Jak włożyć coś od siebie](#jak-włożyć-coś-od-siebie)
- [Narzędzia](#narzędzia)
- [Zmiana motywu](#zmiana-motywu)
- [Inne zaawansowane API](#inne-zaawansowane-api)
- [Opcjonalne globalne ustawienia i manualna konfiguracja](#opcjonalne-globalne-ustawienia-i-manualna-konfiguracja)
- [Video tłumaczące inne funkcjonalności GetX](#video-tłumaczące-inne-funkcjonalności-getx)
- [Zmiany od 2.0](#zmiany-od-20)
# Kanały komunikacji i wsparcia:
[**Slack (English)**](https://communityinviter.com/apps/getxworkspace/getx)
[**Discord (English and Portuguese)**](https://discord.com/invite/9Hpt99N)
[**Telegram (Portuguese)**](https://t.me/joinchat/PhdbJRmsZNpAqSLJL6bH7g)
# Wprowadzenie
- GetX jest bardzo lekką, a zarazem potężną biblioteką do Flattera. Łączy wysoką wydajność menadżera stanu, inteligętne dodawanie dependencies i zarządzanie routami w szybki i praktyczny sposób.
- GetX nie jest dla wszystkich, skupia się na jak najmniejszej konsumpcji zasobów (wydajności) ([zobacz benchmarki](https://github.com/jonataslaw/benchmarks)), używaniu łatwej skłani (produktywności) i daniu możliwości pełnego rozbicia View na z logiki biznesowej (organizacja).
- GetX da Ci supermoce i zwiększy produktywność w tworzeniu projektu. Oszczędzi godziny zarówno początkującym jak i ekspertom.
- Nawiguj bez podawania `context`, używaj open `dialogs`, `snackbarów` oraz `bottomsheetów` z każdego miejsca w kodzie. Zarządzaj stanami i dodawaj dependencies w prosty i praktyczny sposób!
- Get jest bezpieczny, stabilny i aktualny. Oprócz tego oferuje szeroki zakres API, które nie są zawarte w standardowym frameworku.
- GetX nie jest przytłaczający. Ma wiele funkcjonalności pozwalajacych na rozpoczęcie programowania bez martwienia się zupełnie nic. Wszystkie funkcjonalności są w osobnych kontenerach, które dodawane są dopiero po ich użyciu. Jeśli tylko używasz menadżera stanu, tylko on będzie kompilowany. Jeśli używasz routów, lecz nic z menadżera stanu to nie będzie on kompilowany. Możesz skompilować repozytorium benchmark i zobaczysz że używa tylko menadżera stanu. Aplikacje używajace Get są mniejsze niz inne, ponieważ wszystkie rozwiązania GetX są projektowane z myślą o lekkości i wydajności. Jest to też zasługa Flutterowego AOT, które jest niesamowite i eliminuje nieużywane zasoby jak żaden inny framework.
**GetX zwiększa twoja produktywność, lecz możesz to jeszcze przyspieszyć instalując rozszerzenie [GetX extension](https://marketplace.visualstudio.com/items?itemName=get-snippets.get-snippets) do swojego VSCode**. Jeszcze nie dostępne w innych IDE.
# Instalacja
Dodaj Get do swojego pliku pubspec.yaml:
```yaml
dependencies:
get:
```
Zaimportuj Get do plików w których chcesz go użyć:
```dart
import 'package:get/get.dart';
```
# Counter App z GetX
Przykładowa aplikaja tworzona domyślnie podczas kreacji nowego projektu we Flaterze ma ponad 100 lini kodu (z komentarzami). By pokazać siłę Get pokażę jak zrobić "licznik" ze zmianą stanu przy każdym kliknięciu, zmianą stron i udostępniajac stan pomiędzy ekranami. Wszystko w zorganizowany sposób dzieląc bussines logic z view w zaledwie 26 LINI KODU WŁĄCZAJĄC W TO KOMENTARZE.
-Krok 1:
Dodaj "Get" przed MaterialApp, zamieniając je na GetMaterialApp
```dart
void main() => runApp(GetMaterialApp(home: Home()));
```
- Note: nie jest to modyfikaja MaterialApp, ponieważ GetMaterialApp nie jest zmodyfikowanym MaterialApp z Fluttera, jest tylko skonfigurowanym Widgetem mającym domyślnie MaterialApp jako dziecko. Możesz to konfigurować ręcznie, ale nie jest to konieczne. GetMaterialApp jest niezbędne dla działania routów, snackbarów, bootomsheetów, internacjonalizacji, dialogów i wysokopoziomowych api powiązanych z routami i nieobecnościa kontekstu. Nie jest to jednak wymagane do używania zarzadzania stanem i dependencies.
-Krok 2:
Tworzymy klasę business logic i umieszczmy w niej wszystkie zmienne, metody oraz kontrolery. Możesz zmienić zmiennaą na obserwowalną używajac prostego subfixu ".obs"
```dart
class Controller extends GetxController{
var count = 0.obs;
increment() => count.value++;
}
```
- Krok 3:
Tworzymy View. Użyj StatelessWidget oszczędzajac przy tym RAM. Z Get nie będzie Ci potrzebny StatefullWidget.
```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());
return Scaffold(
// Use Obx(()=> to update Text() whenever count is changed.
appBar: AppBar(title: Obx(() => Text("Clicks: " + c.count.string))),
// Replace the 8 lines Navigator.push by a simple Get.to(). You don't need context
body: Center(child: ElevatedButton(
child: Text("Go to Other"), onPressed: () => Get.to(Other()))),
floatingActionButton:
FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment));
}
}
class Other extends StatelessWidget {
// You can ask Get to find a Controller that is being used by another page and redirect you to it.
final Controller c = Get.find();
@override
Widget build(context){
// Access the updated count variable
return Scaffold(body: Center(child: Text(c.count.string)));
}
}
```
Wynik:
![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/counter-app-gif.gif)
Jest to prosty projekt, ale już na jego przykładzie widać potęgę Get. Wraz ze wzrostem rozmiaru aplikacji ta różnica tylko się powiększa.
Get był projektowany dla pracy z zespołem, ale równie dobrze sprawdza się w indywidualnych projektach.
Zawsze dotrzymuj deadlinów i dostarczaj swoje rozwiązania na czas bez straty na wydajności. Get nie jest dla wszystkich jak już wspominałem, ale jeśli identyfikujesz się z powyższym zdaniem Get jest właśnie dla Ciebie.
# Trzy filary
## Menadżer stanu
Obecnie istnieje kilka menadżeów dla Fluttera. Jednak większość z nich wymaga używania ChangeNotifier, po to aby zaktualizować widżety, co nie sprawdza się pod kątem wydajności w średnich i dużych aplikacach. Możesz sprawdzić w oficjalnej dokumentacji, że ChangeNotifier powinien być używany z maksimum dwoma listinerami (https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html), będąc praktycznie bezużytecznym w średnich i duzych projektach.
Get nie jest ani lepszy, ani gorszy od innych menadżerów stanów, ale powinieneś rozpatrzyć te punkty jak i poniższe, aby wybrać między użyciem Get w czystej formie (Vanilla), albo używaniem go wraz z innym menadżerem.
Definitywnie Get nie jest przeciwnikiem żadnego innego menadżera, ponieważ jest on mikroframeworkiem, nie tylko menadżerem stanu. Może być użyty samodzielnie, lub w koegzystencji.
Get ma bardzo lekki i prosty menadżer stanu (napisany w tylko 95 lini kodu), który nie używa ChangeNotifier. Sprosta on wymaganiom szczególnie nowych we Flutterze i nie sprawi problemu nawet w dużych aplikacjach.
### Reaktywny menadżer stanu
Reaktywne programowanie może odtrącać niektórych, ponieważ powszechnie jest uważane za skomplikowane. GetX zamienia to w coś prostego:
- Nie musisz tworzyć StreamControllerów,
- Nie musisz tworzyć StreamBuildera dla każdej zmiennej,
- Nie ma potrzeby tworzenia klasy dla każdego stanu,
- Nie musisz tworzyć Get dla inicjalnej zmiennej
Wyobraź sobie, że masz zmienną i za każdym razem jak zmienisz ją chcesz żeby wszystkie widżety używające jej automatycznie się zmieniły
Przykładowa zmienna:
```dart
var name = 'Jonatas Borges';
```
By zamienić ją na obserwowalną dodaj ".obx" na końcu:
```dart
var name = 'Jonatas Borges'.obs;
```
I w UI, kiedy chcesz go zaktualizować przy modyfikacji zmiennej po prostu dodaj to:
```dart
Obx (() => Text (controller.name));
```
To wszystko. *Proste*, co nie?
### Bardziej szczegółowo o menadżerze stanu
**Zobacz bardziej szczegółowe wytłumaczenie menadżera stanu [tutaj](./documentation/en_US/state_management.md). Znajdują się tam przykłady jak o różnice między prostym menadżerem stanu oraz reaktywnym**
### Video tłumaczące użycie menadżera stanu
Tadas Petra nagrał o tym niezwykły film:
Link: [Complete GetX State Management](https://www.youtube.com/watch?v=CNpXbeI_slw)
## Zarządzanie routami
Jeśli chcesz używać routes/snackbars/dialogs/bottomsheets z GetX możesz to robić bez contextu.
Zamień MaterialApp na GetMaterialApp
```dart
GetMaterialApp( // Before: MaterialApp(
home: MyHome(),
)
```
By nawigować do nowego ekranu:
```dart
Get.to(NextScreen());
```
By powrócić do poprzedniego ekranu
```dart
Get.back();
```
By przejść do następnego ekranu bez możliwości powrotu do poprzedniego (do zastosowania SplashScreenów, ekranów logowania itd.)
```dart
Get.off(NextScreen());
```
By przejść do następnego ekranu niszcząc poprzednie routy (użyteczne w koszykach, ankietach i testach)
```dart
Get.offAll(NextScreen());
```
By nawigować do następnego routa i otrzymać, lub uaktualnić dane zaraz po tym jak z niego wrócisz:
```dart
var data = await Get.to(Payment());
```
w innym ekranie wyślij dane z poprzedniego routa:featury
```dart
Get.back(result: 'sucess');
```
I użyj następujące np.:
```dart
if(data == 'sucess') madeAnything();
```
Zobacz, ze do żadnej z tych operacji nie potrzebowałeś contextu. Jest to jedna z głównych zalet GetX oszczędzającego na niepotrzebnej obudowie w kod i dającego możliwość używania tych metod w klasie kontrolera.
### Więcej o routach
**Get używa named routes oraz oferuje niskopoziomową obsługę routów! Zobacz bardziej szczegółową dokumentacje [tutaj](./documentation/en_US/route_management.md)**
### Video tłumaczące użycie
Tadas Petra nagrał o tym niezwykły film:
Link: [Complete GetX Navigation](https://www.youtube.com/watch?v=RaqPIoJSTtI)
## Zarządzanie dependencies
Get ma prosty, a zarazem potężny menadżer dependencies. Pozwala on na otrzymanie tych samych klas jak twoje Bloc lub Kontroler pisząc jedną linię kodu bez Provider context i inheritedWidget:
```dart
Controller controller = Get.put(Controller()); // Rather Controller controller = Controller();
```
- Note: Jeśli używasz menadżera stanu Get zwróć uwagę na binding api, które pozwoli Ci łatwiej połączyć twój widok z kontrolerem.
https://github.com/jonataslaw/get
**Tip:** Menadżer dependency Get jest oddzielony od innych części pakietu więc jeśli już używasz menadżera stanu(którego kolwiek, bez różnicy) nie musisz przepisywać tego wszystkiego na nowo. Możesz używać tego dodawania dependencies bez poroblemu.
```dart
controller.fetchApi();
```
Wyobraź sobie, że musisz nawigować pomiędzy wieloma routami i potrzebujesz dane z kontrolerów z poprzednich ekranów. Musiałbyś użyć menadżera stanu z dodatkiem Providera albo Get_it, prawda? Otóż nie z Fet. Musisz po prostu poprosić Get o znalezienie tego kontrolera i nie potrzebujesz przy tym dodatkowych dependencies.
```dart
Controller controller = Get.find();
//Tak, to wygląda jak Magia, Get znjadzie Twój kontroler i Ci go dostarczy. Możesz mieć nawet MILION kontrolerów, a Get zawsze da Ci prawidłowy kontroler.
```
I wtedy będziesz mógł otrzymać z niego dane bez żadnego problemu
```dart
Text(controller.textFromApi);
```
### Bardziej szczegółowo o menadżerze dependencies
**Zobzcz więcej w dokumentacji [tutaj](./documentation/en_US/dependency_management.md)**
# Jak włożyć coś od siebie
Możesz uczestniczyć w rozwoju projektu na różny sposób:
- Pomagając w tłumaczeniu readme na inne języki.
- Dodając dokumentację do readme (nawet połowa funkcji została jeszcze opisana).
- Pisząc artykuły i nagrywając filmy pokazujące użycie biblioteki Get (będą zamieszczone w readme, a w przyszłości na naszej Wiki).
- Oferując PR-y dla kodu i testów.
- Dodając nowe funkcje.
Każda współpraca jest mile widziana!
# Narzędzia
## Zmiana motywu
Nie powinno się używać innego widżetu niż GetMaterialApp by go zaktualizować. To może powodować duplikacje kluczy. Wiele osób nawykło do prehistorycznego podejścia tworzenia widżetu "ThemeProvider" tylko po to by zmienić motyw aplikacji. Z Get nie jest to wymagane.
Możesz stworzyć customowy motyw i łatwo go dodać z Get.changeTheme bez niepotrzebnego kodu.
```dart
Get.changeTheme(ThemeData.light());
```
Jeśli chcesz stworzyć coś jak przycisk zmieniający motyw aplikacji na onTap, możesz połączyć dwia Get API. Api sprawdzające czy ciemny motyw jest używany i Api zajmujące się zmianą motywu. Po prostu użyj tego w onPressed:
```dart
Get.changeTheme(Get.isDarkMode? ThemeData.light(): ThemeData.dark());featury
```
Kiedy ciemny motyw jest aktywny zmieni się on na jasny, w przeciwnym wypadku zmieni się na ciemny.
Jeśli interesuje Cię jak zmieniać motywy podążaj za samouczkiem na Medium pokazującum zmianę motywu przy użyciu Get:
- [Dynamic Themes in 3 lines using Get](https://medium.com/swlh/flutter-dynamic-themes-in-3-lines-c3b375f292e3) - Samouczek stworzony przez [Rod Brown](https://github.com/RodBr).
## Inne zaawansowane API
```dart
// give the current args from currentScreen
Get.arguments
// give name of previous route
Get.previousRoute
// give the raw route to access for example, rawRoute.isFirst()
Get.rawRoute
// give access to Rounting API from GetObserver
Get.routing
// check if snackbar is open
Get.isSnackbarOpen
// check if dialog is open
Get.isDialogOpen
// check if bottomsheet is opefeaturyn
Get.isBottomSheetOpen
// remove one route.
Get.removeRoute()
// back repeatedly until the predicate returns true.
Get.until()
// go to next route and remove all the previous routes until the predicate returns true.
Get.offUntil()
// go to next named route and remove all the previous routes until the predicate returns true.
Get.offNamedUntil()
//Check in what platform the app is running
GetPlatform.isAndroid
GetPlatform.isIOS
GetPlatform.isWeb
// Equivalent to the method: MediaQuery.of(context).size.height, but they are immutable.
Get.height
Get.width
// Gives the current context of navigator.
Get.context
// Gives the context of the snackbar/dialog/bottomsheet in the foreground anywhere in your code.
Get.contextOverlay
// Note: the following methods are extensions on context. Since you
// have access to context in any place of your UI, you can use it anywhere in the UI code
// If you need a changeable height/width (like browser windows that can be scfeaturyaled) you will need to use context.
context.width
context.height
// gives you the power to define half the screen now, a third of it and so on.
//Useful for responsive applications.
// param dividedBy (double) optional - default: 1
// param reducedBy (double) optional - default: 0
context.heightTransformer()
context.widthTransformer()
/// similar to MediaQuery.of(context).size
context.mediaQuerySize()
/// similar to MediaQuery.of(context).padding
context.mediaQueryPadding()
/// similar to MediaQuery.of(context).viewPadding
context.mediaQueryViewPadding()
/// similar to MediaQuery.of(context).viewInsets;
context.mediaQueryViewInsets()
/// similar to MediaQuery.of(context).orientation;
context.orientation()
/// check if device is on landscape mode
context.isLandscape()
/// check if device is on portrait mode
context.isPortrait()
/// similar to MediaQuery.of(context).devicePixelRatio;
context.devicePixelRatio()
/// similar to MediaQuery.of(context).textScaleFactor;
context.textScaleFactor()
/// get the shortestSide from screen
context.mediaQueryShortestSide()
/// True if width be larger thfeaturyan 800
context.showNavbar()
/// True if the shortestSide is smaller than 600p
context.isPhone()
/// True if the shortestSide is largest than 600p
context.isSmallTablet()
/// True if the shortestSide is largest than 720p
context.isLargeTablet()
/// True if the current device is Tablet
context.isTablet()
```
### Opcjonalne globalne ustawienia i manualna konfiguracja
GetMaterialApp konfiguruje wszystko za Ciebie, ale jeśli chcesz możesz konfigurować Get manualnie.
```dart
MaterialApp(
navigatorKey: Get.key,
navigatorObservers: [GetObserver()],
);
```
Będziesz mógł używać swojego Midware z GetObserver, nie wpływa to na nic.
```dart
MaterialApp(
navigatorKey: Get.key,
navigatorObservers: [
GetObserver(MiddleWare.observer) // Here
],
);
```
Mozesz stworzyć globalne ustawienia dla Get. Tylko dodaj Get.config do swojego kodu przed użyciem routów, lub bezpośrednio w GetMaterialApp
```dart
GetMaterialApp(
enableLog: true,
defaultTransition: Transition.fade,
opaqueRoute: Get.isOpaqueRouteDefault,
popGesture: Get.isPopGestureEnable,
transitionDuration: Get.defaultDurationTransition,
defaultGlobalState: Get.defaultGlobalState,https://github.com/jonataslaw/ge
);
Get.config(
enableLog = true,
defaultPopGesture = true,
defaultTransition = Transitions.cupertino
)
```
Opcjonalnie możesz przekierować wszystkie logi z Get by używać swojej ulubionej paczki i zbierać w niej logi.
```dart
GetMaterialApp(
enableLog: true,
logWriterCallback: localLogWriter,
);
void localLogWriter(String text, {bool isError = false}) {
// tutaj przekaż wiadomosci do ulubionej paczki
// pamiętaj że nawet jeśli "enableLog: false" logi i tak będą wysłane w tym callbacku
// Musisz sprawdzić konfiguracje flag jeśli chcesz przez GetConfig.isLogEnable
}
```
## Video tłumaczące inne funkcjonalności GetX
Tadas Petra nagrał niezwykły film tłumaczący powyższe zagadnienia!
Link: [GetX Other Features](https://youtu.be/ttQtlX_Q0eU)
# Zmiany od 2.0
1- Typy Rx:
| Przed | Po |
| ------- | ---------- |
| StringX | `RxString` |
| IntX | `RxInt` |
| MapX | `RxMax` |
| ListX | `RxList` |
| NumX | `RxNum` |
| DoubleX | `RxDouble` |
RXController i GetBuilder teraz zostały połączone. Nie musisz już pamiętać którego kontrolera chcesz użyć, po prostu korzystaj z GetxController, będzie działać zarówno dla prostego jak i reaktywnego menadżera stanów.
2- NamedRoutes
Wcześniej:
```dart
GetMaterialApp(
namedRoutes: {
'/': GetRoute(page: Home()),
}
)
```
Teraz:
```dart
GetMaterialApp(
getPages: [
GetPage(name: '/', page: () => Home()),
]
)
```
Po co ta zmiana?
Często może być niezbędnym decydowanie która strona będzie wyświetlana w zależności od parametru, lub tokenu logowania. Wczesniejsze podejście było nieelastyczne, ponieważ na to nie pozwalało. Zawarcie strony w funkcji zmniejszyło sporzycie RAM-u, ze względu na niealokowanie routów od początku działania aplikacji. Pozwoliło to także na takie podejscie:
```dart
GetStorage box = GetStorage();
GetMaterialApp(
getPages: [
GetPage(name: '/', page:(){
return box.hasData('token') ? Home() : Login();
})
]
)
```

1132
packages/get/README.pt-br.md Normal file

File diff suppressed because it is too large Load Diff

912
packages/get/README.ru.md Normal file
View File

@@ -0,0 +1,912 @@
![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/get.png)
_Языки: Русский (этот файл), [вьетнамский](README-vi.md), [индонезийский](README.id-ID.md), [урду](README.ur-PK.md), [Английский](README.md), [Китайский](README.zh-cn.md), [Бразильский Португальский](README.pt-br.md), [Испанский](README-es.md), [Польский](README.pl.md), [Kорейский](README.ko-kr.md), [French](README-fr.md)._
[![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get)
[![popularity](https://badges.bar/get/popularity)](https://pub.dev/packages/sentry/score)
[![likes](https://badges.bar/get/likes)](https://pub.dev/packages/get/score)
[![pub points](https://badges.bar/get/pub%20points)](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](#приложение-счётчик-с-getx)
- [Три столпа](#три-столпа)
- [Управление состоянием](#управление-состоянием)
- [Реактивное управление состоянием](#реактивное-управление-состоянием)
- [Подробнее об управлении состоянием](#подробнее-об-управлении-состоянием)
- [Управление маршрутами](#управление-маршрутами)
- [Подробнее об управлении маршрутами](#подробнее-об-управлении-маршрутами)
- [Внедрение зависимостей](#внедрение-зависимостей)
- [Подробнее о внедрении зависимостей](#подробнее-о-внедрении-зависимостей)
- [Утилиты](#утилиты)
- [Интернационализация](#интернационализация)
- [Переводы](#переводы)
- [Использование переводов](#использование-переводов)
- [Локализация](#локализация)
- [Изменение локализации](#изменение-локализации)
- [Системная локализация](#системная-локализация)
- [Изменение темы](#изменение-темы)
- [Другие API](#другие-api)
- [Дополнительные глобальные настройки и ручные настройки](#дополнительные-глобальные-настройки-и-ручные-настройки)
- [Локальные виджеты состояния](#локальные-виджеты-состояния)
- [ValueBuilder](#valuebuilder)
- [ObxValue](#obxvalue)
- [Полезные советы](#полезные-советы)
- [GetView](#getview)
- [GetWidget](#getwidget)
- [GetxService](#getxservice)
- [Критические изменения по сравнению с версией 2.0](#критические-изменения-по-сравнению-с-версией-20)
- [Почему Getx?](#почему-getx)
- [Сообщества](#сообщества)
- [Каналы сообщества](#каналы-сообщества)
- [Как внести свой вклад](#как-внести-свой-вклад)
- [Статьи и видео](#статьи-и-видео)
# Про Get
- GetX - это сверхлегкое и мощное решение для Flutter. Оно совмещает в себе высокопроизводительное управление состоянием, интеллектуальное внедрение зависимостей, управление маршрутами быстрым и практичным способом.
- GetX имеет 3 базовых принципа, являющихся приоритетом для всех ресурсов в библиотеке
- **Производительность:** GetX сфокусирован на производительности и минимальном потреблении ресурсов. Бенчмарки почти всегда не имеют значения в реальном мире, но, если Вам угодно, здесь ([бенчмарки](https://github.com/jonataslaw/benchmarks)) есть индикаторы потребления, где GetX работает лучше, чем другие подходы к управлению состоянием. Разница небольшая, но демонстрирует нашу заботу о ресурсах.
- **Продуктивность:** GetX использует простой и приятный синтаксис. Не имеет значения, что вы хотите сделать, всегда есть более легкий способ с GetX. Это сэкономит часы разработки и обеспечит максимальную производительность, которую может обеспечить ваше приложение.
- **Организация:** GetX позволяет полностью разделить представление, логику представления, бизнес-логику, внедрение зависимостей и навигацию. Вам не нужен контекст для навигации между маршрутами, поэтому вы не зависите от дерева виджетов. Вам не нужен контекст для доступа к вашим контроллерам / блокам через наследуемый виджет, поэтому вы полностью отделяете логику представления и бизнес-логику от уровня визуализации. Вам не нужно внедрять классы Controllers / Models / Blocs в дерево виджетов через мультипровайдеры, поскольку GetX использует собственную функцию внедрения зависимостей, полностью отделяя DI от его представления.
С GetX вы знаете, где найти каждую функцию вашего приложения, имея чистый код по умолчанию. Это, помимо упрощения обслуживания, делает возможным совместное использование модулей, что до того момента во Flutter было немыслимо.
BLoC был отправной точкой для организации кода во Flutter, он отделяет бизнес-логику от визуализации. Getx является естественным развитием этого, разделяя не только бизнес-логику, но и логику представления. Дополнительное внедрение зависимостей и маршрутов также разделено, и уровень данных не учитывается. Вы знаете, где все находится, и это проще, чем написать "Hello World".
GetX - это самый простой, практичный и масштабируемый способ создания высокопроизводительных приложений с помощью Flutter SDK с большой экосистемой вокруг него, которая отлично работает, прост для новичков и точен для экспертов. Он безопасен, стабилен, актуален и предлагает огромный набор встроенных API, которых нет в Flutter SDK по умолчанию.
- GetX не раздут. Он имеет множество функций, которые позволяют вам начать программировать, ни о чем не беспокоясь, но каждая из этих функций находится в отдельных контейнерах и запускается только после использования. Если вы используете только управление состоянием, то будет скомпилировано только управление состоянием. Если вы используете маршрутизацию, то ничего из управления состоянием не будет скомпилировано. Вы можете воспользоваться репозиторием бенчмарка, и вы увидите, что используя только управление состоянием Get, приложение, которое скомпилировано с помощью Get, имеет меньший размер, чем приложения использующие другие пакеты для управления состоянием, потому что всё, что не используется, не будет скомпилировано в Ваш код. Таким образом каждое решение 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).**
# Установка
Добавьте Get в файл pubspec.yaml:
```yaml
dependencies:
get:
```
Импортируйте Get в файлы, в которых планируете его использовать:
```dart
import 'package:get/get.dart';
```
# Приложение "Счётчик" с GetX
Проект "Счётчик", созданный по умолчанию для нового проекта на Flutter, имеет более 100 строк (с комментариями). Чтобы показать возможности Get, я продемонстрирую, как сделать "Счётчик", изменяющий состояние при каждом клике, переключении между страницами и передаче состояния между экранами. Всё это вместе с разделением бизнес логики от представления занимает ВСЕГО ЛИШЬ 26 СТРОК КОДА, ВКЛЮЧАЯ КОММЕНТАРИИ.
- Шаг 1:
Добавьте "Get" перед вашим MaterialApp, превращая его в GetMaterialApp
```dart
void main() => runApp(GetMaterialApp(home: Home()));
```
- Примечание: это не изменяет MaterialApp, GetMaterialApp не является модифицированным MaterialApp, это просто предварительно настроенный виджет, у которого в качестве дочернего по умолчанию используется MaterialApp. Вы можете настроить это вручную, но это не обязательно. GetMaterialApp будет создавать маршруты, вводить их, вводить переводы, вводить всё, что вам нужно для навигации. Если вы используете Get только для управления состоянием или зависимостями, нет необходимости использовать GetMaterialApp. GetMaterialApp необходим для навигации, снекбаров, интернационализации, bottomSheets, диалогов и API, связанных с маршрутами и отсутствием контекста.
- Примечание²: Этот шаг необходим только в том случае, если вы собираетесь использовать управление маршрутами (`Get.to()`, `Get.back()` и так далее). Если вы не собираетесь его использовать, то шаг 1 выполнять необязательно.
- Шаг 2:
Создайте свой класс бизнес-логики и поместите в него все переменные, методы и контроллеры.
Вы можете сделать любую переменную наблюдаемой, используя простой ".obs".
```dart
class Controller extends GetxController{
var count = 0.obs;
increment() => count++;
}
```
- Шаг 3:
Создайте свой View, используйте StatelessWidget и сэкономьте немного оперативной памяти, с Get вам больше не нужно использовать StatefulWidget.
```dart
class Home extends StatelessWidget {
@override
Widget build(context) {
// Создайте экземпляр вашего класса с помощью Get.put(), чтобы сделать его доступным для всех "дочерних" маршрутов.
final Controller c = Get.put(Controller());
return Scaffold(
// Используйте Obx(()=> чтобы обновить Text() как только count изменится.
appBar: AppBar(title: Obx(() => Text("Кликов: ${c.count}"))),
// Замените 8 строк Navigator.push простым Get.to(). Вам не нужен context!
body: Center(child: ElevatedButton(
child: Text("Перейти к Other"), onPressed: () => Get.to(Other()))),
floatingActionButton:
FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment));
}
}
class Other extends StatelessWidget {
// "Попросите" Get найти и предоставить вам ваш Controller, используемый на другой странице.
final Controller c = Get.find();
@override
Widget build(context){
// Получите доступ к обновленной переменной count
return Scaffold(body: Center(child: Text("${c.count}")));
}
}
```
Результат:
![](counter-app-gif.gif)
Это простой проект, но он уже дает понять, насколько мощным является Get. По мере роста вашего проекта эта разница будет становиться все более значительной.
Get был разработан для работы с командами, но он упрощает работу отдельного разработчика.
Оптимизируйте ваши сроки, доставляйте всё вовремя без потери производительности. Get не для всех, но, если вы идентифицировали себя с предыщим предложением, Get для вас!
# Три столпа
## Управление состоянием
В настоящее время для Flutter есть несколько менеджеров состояний. Однако большинство из них связано с использованием ChangeNotifier для обновления виджетов, и это плохой и очень плохой подход к производительности для средних или больших приложений. Вы можете проверить в официальной документации Flutter, что [ChangeNotifier следует использовать с 1 или максимум 2 слушателями](https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html), что делает его практически непригодным для любого приложения среднего или большого размера.
Get не лучше и не хуже, чем любой другой менеджер состояний, но вам следует проанализировать его, а также пункты ниже, чтобы выбрать между использованием Get в чистой форме (Vanilla), либо совместно с другим менеджером состояний.
Определенно, Get не враг любого другого менеджера состояний, потому что Get - это микрофреймворк, а не просто менеджер состояний, и его можно использовать отдельно или вместе с ними.
Get имеет два разных менеджера состояний: простой менеджер состояний (мы назовем его GetBuilder) и реактивный менеджер состояний (который называется GetX).
### Реактивное управление состоянием
Реактивное программирование может оттолкнуть многих людей, потому что считается сложным. GetX превращает реактивное программирование в нечто довольно простое:
- Вам не нужно создавать StreamControllers.
- Вам не нужно создавать StreamBuilder для каждой переменной.
- Вам не нужно создавать класс для каждого состояния.
- Вам не нужно создавать геттер начального значения.
Реактивное программирование с Get так же просто, как использование setState.
Представим, что у вас есть переменная name и вы хотите, чтобы каждый раз, когда вы её изменяете, все виджеты, которые её используют, менялись автоматически.
Это ваша переменная:
```dart
var name = 'Джонатас Борхес';
```
Чтобы сделать его наблюдаемым, вам просто нужно добавить в конец ".obs":
```dart
var name = 'Джонатас Борхес'.obs;
```
А в пользовательском интерфейсе, если вы хотите отображать это значение и обновлять экран при изменении значений, просто сделайте следующее:
```dart
Obx(() => Text("${controller.name}"));
```
Вот и всё. Это _так_ просто.
### Подробнее об управлении состоянием
**Более подробное объяснение управления состоянием [здесь](./documentation/ru_RU/state_management.md). Там вы увидите больше примеров, а также разницу между простым менеджером состояния и реактивным менеджером состояния.**
Вы получите хорошее представление о мощности GetX.
## Управление маршрутами
Если вы собираетесь использовать маршруты / снекбары / диалоги / bottomsheets без контекста, GetX отлично подойдёт вам, просто посмотрите:
Добавьте "Get" перед MaterialApp, превратив его в GetMaterialApp.
```dart
GetMaterialApp( // Ранее: MaterialApp(
home: MyHome(),
)
```
Перейдите на новый экран:
```dart
Get.to(NextScreen());
```
Перейдите на новый экран с именем. Более подробную информацию об именованных маршрутах смотрите [здесь](./documentation/ru_RU/route_management.md#navigation-with-named-routes)
```dart
Get.toNamed('/details');
```
Закрыть снекбар, диалог, bottomsheets, или что-то иное, что вы обычно закрывали с помощью Navigator.pop(context);
```dart
Get.back();
```
Для перехода к следующему экрану без возможности вернуться к предыдущему экрану (для использования в SplashScreens, экранах входа и т. д.)
```dart
Get.off(NextScreen());
```
Для перехода к следующему экрану и отмены всех предыдущих маршрутов (полезно в корзинах для покупок, опросах и тестах)
```dart
Get.offAll(NextScreen());
```
Заметили, что вам не нужно было использовать контекст, чтобы делать что-либо из этого? Это одно из самых больших преимуществ использования Get. Благодаря этому вы можете без проблем выполнять все эти методы из класса контроллера.
### Подробнее об управлении маршрутами
**Get работает с именованными маршрутами, а также предлагает более низкий уровень контроля над вашими маршрутами! [Здесь](./documentation/ru_RU/route_management.md) есть подробная документация.**
## Внедрение зависимостей
Get имеет простой и мощный менеджер зависимостей, который позволяет вам получить тот же класс, что и ваш BLoC или контроллер, всего одной строкой кода, без Provider context, без InheritedWidget:
```dart
Controller controller = Get.put(Controller()); // Вместо Controller controller = Controller();
```
- Примечание: Если вы используете Get State Manager, обратите больше внимания на API привязок, который упростит подключение вашего представления к контроллеру.
Вместо того, чтобы создавать экземпляр вашего класса внутри класса, который вы используете, вы создаете его в экземпляре Get, что сделает его доступным во всем приложении. Таким образом, вы можете использовать свой контроллер (или BLoC) в обычном режиме.
**Совет:** Управление зависимостями Get не связано с другими частями пакета, поэтому, если, например, ваше приложение уже использует менеджер состояний (любой, не имеет значения), вам не нужно все это переписывать, вы можете использовать это внедрение зависимостей без проблем.
```dart
controller.fetchApi();
```
Представьте, что вы прошли через множество маршрутов и вам нужны данные, которые остались в вашем контроллере, вам понадобится менеджер состояний в сочетании с Provider или Get_it, верно? Только не с Get. Вам просто нужно попросить Get «найти» ваш контроллер, никаких дополнительных зависимостей вам не потребуется:
```dart
Controller controller = Get.find();
// Да, это выглядит как Магия! Get найдет ваш controller и доставит его вам. У вас может быть миллион созданных контроллеров, и Get всегда найдет нужный.
```
И тогда вы сможете восстановить данные вашего контроллера, которые были там получены:
```dart
Text(controller.textFromApi);
```
### Подробнее о внедрении зависимостей
**Более подробное объяснение управления зависимостями [здесь](./documentation/ru_RU/dependency_management.md)**
# Утилиты
## Интернационализация
### Переводы
Переводы хранятся в виде карты пар "ключ-значение". Чтобы добавить собственные переводы, создайте класс и расширьте `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);
```
#### Системная локализация
Чтобы узнать системную локализацию, вам следует использовать `window.locale`.
```dart
import 'dart:ui' as ui;
return GetMaterialApp(
locale: ui.window.locale,
);
```
## Изменение темы
Пожалуйста, не используйте виджет более высокого уровня, чем `GetMaterialApp`, для его обновления. Это может вызвать повторяющиеся ключи. Многие люди привыкли к старому подходу к созданию виджета «ThemeProvider» только для того, чтобы изменить тему вашего приложения, а это НЕ требуется с GetX ™.
Вы можете создать свою собственную тему и просто добавить ее в `Get.changeTheme` без повторяющегося кода:
```dart
Get.changeTheme(ThemeData.light());
```
Если вы хотите создать что-то вроде кнопки, которая изменяет тему, вы можете объединить для этого два API **GetX ™**:
- API, который проверяет, используется ли темная тема.
- И API смены темы.
Вы можете просто поместить это в `onPressed`:
```dart
Get.changeTheme(Get.isDarkMode? ThemeData.light(): ThemeData.dark());
```
Когда `.darkmode` активен, он переключится на _light theme_, и когда _light theme_ станет активной, он изменится на _dark theme_.
## Другие API
```dart
// получить текущие аргументы текущего экрана
Get.arguments
// получить аргументы предыдущего маршрута
Get.previousArguments
// получить имя предыдущего маршрута
Get.previousRoute
// получить чистый маршрут, например, чтобы узнать: rawRoute.isFirst()
Get.rawRoute
// получить доступ к Rounting API из GetObserver
Get.routing
// проверить, открыт ли снекбар
Get.isSnackbarOpen
// открыт ли диалог
Get.isDialogOpen
// открыт ли bottomsheets
Get.isBottomSheetOpen
// удалить один маршрут
Get.removeRoute()
// возвращаться назад, пока данный предикат не выполнится
Get.until()
// идти вперед, удалив предыдущие маршруты, пока данный предикат не выполнится
Get.offUntil()
// перейти к следующему именованному маршруту, удалив предыдущие маршруты, пока данный предикат не выполнится
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
// Текущий контекст навигации
Get.context
// Получить контекст показанного снекбара/диалога/bottomsheets в любом месте вызова.
Get.contextOverlay
// Внимание: методы ниже являются расширениями класса BuildContext.
// Поскольку доступ к контексту есть в любом месте из вашего UI,
// вы можете использовать расширения в любом месте UI кода
// Если вам нужна изменяемая высота/ширина (например, настольное или браузерное окно, размер которого можно изменить), вам нужно использовать context
context.width
context.height
// Дает возможность определить половину экрана, треть и так далее.
// Полезно для отзывчивых приложений.
// 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()
/// Схоже с to MediaQuery.of(context).devicePixelRatio;
context.devicePixelRatio()
/// Схоже с MediaQuery.of(context).textScaleFactor;
context.textScaleFactor()
/// Получить shortestSide экрана
context.mediaQueryShortestSide()
/// Вернет True, если ширина больше 800
context.showNavbar()
/// Вернет True, если меньшая сторона меньше 600p
context.isPhone()
/// Вернет True, если меньшая сторона больше 600p
context.isSmallTablet()
/// Вернет True, если меньшая сторона больше 720p
context.isLargeTablet()
/// Вернет True, если текущее устройство — Планшет
context.isTablet()
/// Возвращает value<T> в зависимости от размера экрана
/// Можно устанавливать значения для:
/// watch: Если меньшая сторона меньше 300
/// mobile: Если меньшая сторона меньше 600
/// tablet: Если меньшая сторона меньше 1200
/// desktop: Если ширина больше 1200
context.responsiveValue<T>()
```
### Дополнительные глобальные настройки и ручные настройки
GetMaterialApp настраивает все за вас, но если вы хотите настроить Get вручную.
```dart
MaterialApp(
navigatorKey: Get.key,
navigatorObservers: [GetObserver()],
);
```
Вы также сможете использовать собственное промежуточное ПО в `GetObserver`, это ни на что не повлияет.
```dart
MaterialApp(
navigatorKey: Get.key,
navigatorObservers: [
GetObserver(MiddleWare.observer) // Here
],
);
```
Вы можете создать _Глобальные Настройки_ Для `Get`. Просто добавьте `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}) {
// передайте сообщение вашей любимой log-библиотеке
// но учитывайте, что даже если enableLog: false, сообщения все равно будут передаваться сюда
// узнать значение этого флага можно с помощью GetConfig.isLogEnable
}
```
### Локальные виджеты состояния
Эти виджеты позволяют управлять одним значением, сохраняя состояние эфемерным и локальным.
У нас есть варианты для Reactive и Simple.
Например, вы можете использовать их для переключения obscureText в `TextField`, возможно, для создания кастомного ExpandablePanel или, возможно, для изменения текущего индекса в `BottomNavigationBar` при изменении содержимого body в `Scaffold`.
#### ValueBuilder
Упрощение `StatefulWidget` который работает с вызовом `.setState` принимающим обновленное значение.
```dart
ValueBuilder<bool>(
initialValue: false,
builder: (value, updateFn) => Switch(
value: value,
onChanged: updateFn, // такая же сигнатура! Вы можете использовать ( newValue ) => updateFn( newValue )
),
// Если нужно вызвать что-то вне метода builder
onUpdate: (value) => print("Значение обновлено: $value"),
onDispose: () => print("Виджет удален"),
),
```
#### ObxValue
Похож на [`ValueBuilder`](#valuebuilder), но это реактивная версия, вы передаёте Rx экземпляр (помните волшебный .obs?) и
автоматически обновляетесь... разве это не великолепно?
```dart
ObxValue((data) => Switch(
value: data.value,
onChanged: data, // У Rx есть _callable_ функция! Вы можете использовать (flag) => data.value = flag,
),
false.obs,
),
```
## Полезные советы
`.obs`ervables (наблюдатели) (также известные как Rx-типы) имеют широкий спектр внутренних методов и операторов
> Очень распространено _мнение_, что свойство с `.obs` **ЯВЛЯЕТСЯ** действительным значением... но не ошибайтесь!
> Мы избегаем объявления типа переменной, потому что компилятор Dart достаточно умен, и
> код выглядит чище, но:
```dart
var message = 'Привет, мир'.obs;
print( 'Тип "$message" — ${message.runtimeType}');
```
Даже если `message` _выводит_ значение String, его тип - **RxString**!
Итак, вы не сможете сделать `message.substring( 0, 4 )`.
Вы должны получить доступ к реальному `value` внутри _observable_:
Самый "используемый способ" это `.value`, но, знаете ли вы, что вы также можете использовать ...
```dart
final name = 'GetX'.obs;
// "обновляет" поток только если значение отличается от текущего.
name.value = 'Хей';
// Все свойства Rx являются "вызываемыми" и возвращают новые значения.
// но это не работает с `null`: UI не будет перестроен.
name('Привет');
// как геттер, напечатает 'Привет'.
name() ;
/// числа:
final count = 0.obs;
// Вы можете использовать все неизменяемые операции с числами!
count + 1;
// Осторожно! Это можно использовать только если `count` не final, а var
count += 1;
// Сравнения так же работают:
count > 2;
/// логические:
final flag = false.obs;
// переключает значение между true/false
flag.toggle();
/// все типы:
// обнуляет значение переменной `value`.
flag.nil();
// Все toString(), toJson() операции применяются к `value`
print( count ); // вызывает `toString()` внутри RxInt
final abc = [0,1,2].obs;
// Преобразует значение в json массив, выводит RxList
// Json поддерживается всеми Rx типами!
print('json: ${jsonEncode(abc)}, тип: ${abc.runtimeType}');
// RxMap, RxList и RxSet являются особенными Rx типами: они расширяют нативные типы.
// Но вы можете работать со списком как и с обычным списком, хоть и реактивным!
abc.add(12); // добавлеет 12 в список, and ОБНОВЛЯЕТ поток.
abc[3]; // как списки, возвращает значение с индексом 3.
// Сравнение равенства работает с Rx и с его value, но хэш код всегда берется у value
final number = 12.obs;
print( number == 12 ); // печатает 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';
}
final user = User(name: 'Джон', last: 'Доу', age: 33).obs;
// `user` "реактивный", но его свойства НЕТ!
// Так что если мы обновим что-либо внутри user...
user.value.name = 'Рой';
// Виджет перестроен не будет!
// `Rx` не знает, изменили ли вы что-то у user.
// Так что для кастомных классов вам нужно явно "уведомлять" об изменении.
user.refresh();
// или мы можем использовать метод `update()`!
user.update((value){
value.name='Рой';
});
print( user );
```
#### GetView
Я люблю этот виджет, он такой простой, но такой полезный!
Это `const Stateless` виджет, который имеет геттер `controller` для зарегистрированного `Controller`, вот и всё.
```dart
class AwesomeController extends GetxController {
final String title = 'Моя Удивительная View';
}
// ВСЕГДА передавайте `Тип` вашего контроллера!
class AwesomeView extends GetView<AwesomeController> {
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(20),
child: Text( controller.title ), // просто вызовите `controller.что-то`
);
}
}
```
#### GetWidget
Большинство людей понятия не имеют об этом виджете или путаются при его применении.
Вариант его использования редок, но конкретен: он кэширует Controller.
Поэтому из-за _cache_, он не может быть `const Stateless`.
> Итак, когда вам нужно «кэшировать» контроллер?
В случаях использования другой "не распространённой" фичи **GetX**: `Get.create()`.
`Get.create(()=>Controller())` будет создавать новый `Controller` каждый раз при вызове
`Get.find<Controller>()`,
Это тот самый случай, когда `GetWidget` блистает... поскольку вы можете использовать его, например, для хранения списка элементов Todo. Итак, если виджет будет «перестроен», он сохранит тот же экземпляр контроллера.
#### GetxService
Этот класс похож на `GetxController`, у него такой же жизненный цикл ( `onInit()`, `onReady()`, `onClose()`).
Но внутри нет никакой «логики». Он просто уведомляет систему **GetX** Dependency Injection о том, что этот подкласс **нельзя** удалить из памяти.
Так что очень полезно держать ваши «Сервисы» всегда доступными и активными с помощью `Get.find()`. Например:
`ApiService`, `StorageService`, `CacheService`.
```dart
Future<void> main() async {
await initServices(); /// ПОДОЖДИТЕ ИНИЦИАЛИЗАЦИЮ СЕРВИСОВ.
runApp(SomeApp());
}
/// Умным решением будет проинициализировать сервисы перед вызовом runApp,
/// поскольку вы можете контроллировать процесс инициализации
/// (может, вам нужно загрузить конфигурацию Темы, ключи API, язык, определенный пользователем...
/// Загружайте SettingService прежде чем запускать ApiService.
/// Таким образом GetMaterialApp() принимает параметры напрямую, и ему не нужно будет перезагружаться
void initServices() async {
print('запуск сервисов ...');
/// Здесь вы инициализируете get_storage, hive, shared_pref,
/// или что-либо другое асинхронное.
await Get.putAsync(() => DbService().init());
await Get.putAsync(SettingsService()).init();
print('Все сервисы запущены...');
}
class DbService extends GetxService {
Future<DbService> init() async {
print('$runtimeType задержка 2 секунды');
await 2.delay();
print('$runtimeType готов!');
return this;
}
}
class SettingsService extends GetxService {
void init() async {
print('$runtimeType задержка 1 секунду');
await 1.delay();
print('$runtimeType готов!');
}
}
```
Единственный способ удалить `GetxService` - использовать `Get.reset()`, который похож на «горячую перезагрузку» вашего приложения. Так что помните, если вам нужен постоянный экземпляр класса в течение всего жизненного цикла вашего приложения, используйте `GetxService`.
# Критические изменения по сравнению с версией 2.0
1- Rx типы:
| До | После |
| ------- | ---------- |
| StringX | `RxString` |
| IntX | `RxInt` |
| MapX | `RxMap` |
| ListX | `RxList` |
| NumX | `RxNum` |
| DoubleX | `RxDouble` |
RxController и GetBuilder теперь объединены, вам больше не нужно запоминать, какой контроллер вы хотите использовать, просто используйте GetxController, он будет работать как для простого управления состоянием, так и для реактивного.
2- NamedRoutes
До:
```dart
GetMaterialApp(
namedRoutes: {
'/': GetRoute(page: Home()),
}
)
```
Сейчас:
```dart
GetMaterialApp(
getPages: [
GetPage(name: '/', page: () => Home()),
]
)
```
Для чего это изменение?
Часто может потребоваться решить, какая страница будет отображаться с помощью параметра или токена входа, предыдущий подход был негибким, так как он не позволял этого.
Вставка страницы в функцию значительно снизила потребление оперативной памяти, поскольку маршруты не будут выделяться в памяти с момента запуска приложения, а также позволил использовать такой подход:
```dart
GetStorage box = GetStorage();
GetMaterialApp(
getPages: [
GetPage(name: '/', page:(){
return box.hasData('token') ? Home() : Login();
})
]
)
```
# Почему Getx?
1- Много раз после обновления Flutter многие из ваших пакетов ломались. Иногда случаются ошибки компиляции, часто возникают ошибки, на которые до сих пор нет ответов, и разработчику необходимо знать, откуда возникла ошибка, отслеживать ошибку, только затем попытаться открыть проблему в соответствующем репозитории и увидеть, как проблема решена. Get централизует основные ресурсы для разработки (управление состоянием, зависимостями и маршрутами), позволяя вам добавить один пакет в свой pubspec и начать работу. После обновления Flutter единственное, что вам нужно сделать, это обновить зависимость Get и приступить к работе. Get также решает проблемы совместимости. Как часто бывало, что одна версия пакета несовместима с другой, потому что одна использует зависимость в одной версии, а другая - в другой? Это не проблема при использовании Get, поскольку все находится в одном пакете и полностью совместимо.
2- Flutter - это просто, Flutter - это невероятно, но у Flutter все еще некоторый шаблонный код, который может быть нежелательным для большинства разработчиков, например `Navigator.of(context).push (context, builder [...]`. Get упрощает разработку. Вместо того, чтобы писать 8 строк кода для вызова маршрута, вы можете просто сделать это: `Get.to(Home())` и всё готово, вы перейдёте на следующую страницу. Динамические URL-адреса - это действительно болезненная вещь, которую нужно решать во Flutter в настоящее время, а с GetX это элементарно. Управление состояниями во Flutter и управление зависимостями также вызывает много споров, поскольку в pub есть сотни паттернов. Но нет ничего проще, чем добавить «.obs» в конец вашей переменной и поместить ваш виджет внутри Obx, и всё, все обновления этой переменной будут автоматически обновляться на экране.
3- Лёгкость, не беспокоясь о производительности. Производительность Flutter уже потрясающая, но представьте, что вы используете диспетчер состояний и локатор для распределения классов блоков / хранилищ / контроллеров / и других классов. Вам придётся вручную вызывать исключение этой зависимости, когда она вам не нужна. Но вы когда-нибудь думали о том, чтобы просто использовать свой контроллер, и когда он больше никем не использовался, он просто был бы удален из памяти? Это то, что делает GetX. Благодаря SmartManagement всё, что не используется, удаляется из памяти, и вам не нужно беспокоиться ни о чем, кроме программирования. Вы будете уверены, что потребляете минимум необходимых ресурсов, даже не создав для этого логики.
4- Действительное разделение. Вы могли слышать о концепции разделения представления от бизнес логики. Это не исключительная особенность BLoC, MVC, MVVM и тд, любой стандарт реализует эту концепцию. Однако во Flutter возможно ослабление этой концепции из-за необходимости использования контекста.
Если вам нужен контекст для поиска InheritedWidget, он вам нужен в представлении, либо нужно передать контекст как параметр. Мы считаем это решение очень уродливым, и для работы в команде мы всегда будем зависеть от логики представления (View). Getx - необычный подход со стандартным доступом, который хоть и не запрещает использование StatefulWidgets, InitState, и т.д., всегда имеет более чистый аналог. У контроллеров есть жизненные циклы, и когда вам нужно сделать, например, запрос APIREST, вы не зависите ни от чего в представлении. Вы можете использовать onInit для инициирования http-вызова, и когда данные поступят, переменные будут заполнены. Поскольку GetX полностью реактивен (действительно реактивен и работает с потоками), после заполнения элементов все виджеты, использующие эту переменную, будут автоматически обновлены в представлении. Это позволяет людям с опытом работы с пользовательским интерфейсом работать только с виджетами и не отправлять в бизнес-логику ничего, кроме пользовательских событий (например, нажатия кнопки), в то время как люди, работающие с бизнес-логикой, смогут создавать и тестировать бизнес-логику отдельно.
Эта библиотека всегда будет обновляться и реализовывать новые функции. Не стесняйтесь предлагать 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 (и Flutter) лучше._
- Помощь в переводе readme на другие языки.
- Добавление документации в readme (многие функции Get еще не задокументированы).
- Напишите статью или сделайте видео, обучающие использованию Get (они будут вставлены в Readme и в будущем в нашу Wiki).
- Предложите PRs для кода/тестов.
- Новые фичи.
Приветствуется любой вклад!
## Статьи и видео
- [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).
- [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.

1054
packages/get/README.ur-PK.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,919 @@
![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/get.png)
_语言: 中文, [英文](README.md), [越南文](README-vi.md), [印度尼西亚](README.id-ID.md), [乌尔都语](README.ur-PK.md), [巴西葡萄牙语](README.pt-br.md), [俄语](README.ru.md), [西班牙语](README-es.md), [波兰语](README.pl.md), [韩国语](README.ko-kr.md), [法语](README-fr.md), [French](README-fr.md)._
[![pub package](https://img.shields.io/pub/v/get.svg?label=get&color=blue)](https://pub.dev/packages/get)
[![popularity](https://badges.bar/get/popularity)](https://pub.dev/packages/sentry/score)
[![likes](https://badges.bar/get/likes)](https://pub.dev/packages/get/score)
[![pub points](https://badges.bar/get/pub%20points)](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的计数器示例](#getx的计数器示例)
- [三大功能](#三大功能)
- [状态管理](#状态管理)
- [响应式状态管理器](#响应式状态管理器)
- [关于状态管理的更多细节](#关于状态管理的更多细节)
- [路由管理](#路由管理)
- [关于路由管理的更多细节](#关于路由管理的更多细节)
- [依赖管理](#依赖管理)
- [关于依赖管理的更多细节](#关于依赖管理的更多细节)
- [实用工具](#实用工具)
- [国际化](#国际化)
- [翻译](#翻译)
- [使用翻译](#使用翻译)
- [语言](#语言)
- [改变语言](#改变语言)
- [系统语言](#系统语言)
- [改变主题](#改变主题)
- [其他高级API](#其他高级api)
- [可选的全局设置和手动配置](#可选的全局设置和手动配置)
- [局部状态组件](#局部状态组件)
- [ValueBuilder](#valuebuilder)
- [ObxValue](#obxvalue)
- [有用的提示](#有用的提示)
- [GetView](#getview)
- [GetWidget](#getwidget)
- [GetxService](#getxservice)
- [从2.0开始的兼容性变化](#从20开始的兼容性变化)
- [为什么选择Getx](#为什么选择getx)
- [社区](#社区)
- [社区渠道](#社区渠道)
- [如何做贡献](#如何做贡献)
- [文章和视频](#文章和视频)
# 关于Get
- GetX 是 Flutter 上的一个轻量且强大的解决方案:高性能的状态管理、智能的依赖注入和便捷的路由管理。
- GetX 有3个基本原则
- **性能:** GetX 专注于性能和最小资源消耗。GetX 打包后的apk占用大小和运行时的内存占用与其他状态管理插件不相上下。如果你感兴趣这里有一个[性能测试](https://github.com/jonataslaw/benchmarks)。
- **效率:** GetX 的语法非常简捷,并保持了极高的性能,能极大缩短你的开发时长。
- **结构:** GetX 可以将界面、逻辑、依赖和路由完全解耦,用起来更清爽,逻辑更清晰,代码更容易维护。
- GetX 并不臃肿,却很轻量。如果你只使用状态管理,只有状态管理模块会被编译,其他没用到的东西都不会被编译到你的代码中。它拥有众多的功能,但这些功能都在独立的容器中,只有在使用后才会启动。
- Getx有一个庞大的生态系统能够在Android、iOS、Web、Mac、Linux、Windows和你的服务器上用同样的代码运行。
**通过[Get Server](https://github.com/jonataslaw/get_server)** 可以在你的后端完全重用你在前端写的代码。
**此外,通过[Get CLI](https://github.com/jonataslaw/get_cli)**,无论是在服务器上还是在前端,整个开发过程都可以完全自动化。
**此外,为了进一步提高您的生产效率,我们还为您准备了一些插件**
- **getx_template**:一键生成每个页面必需的文件夹、文件、模板代码等等
- [Android Studio/Intellij插件](https://plugins.jetbrains.com/plugin/15919-getx)
- **GetX Snippets**:输入少量字母,自动提示选择后,可生成常用的模板代码
- [Android Studio/Intellij扩展](https://plugins.jetbrains.com/plugin/14975-getx-snippets)
- [VSCode扩展](https://marketplace.visualstudio.com/items?itemName=get-snippets.get-snippets)
# 安装
将 Get 添加到你的 pubspec.yaml 文件中。
```yaml
dependencies:
get:
```
在需要用到的文件中导入,它将被使用。
```dart
import 'package:get/get.dart';
```
# GetX的计数器示例
Flutter默认创建的 "计数器 "项目有100多行含注释为了展示Get的强大功能我将使用 GetX 重写一个"计数器 Plus版",实现:
- 每次点击都能改变状态
- 在不同页面之间切换
- 在不同页面之间共享状态
- 将业务逻辑与界面分离
而完成这一切只需 **26 行代码(含注释)**
- 步骤1.
在你的MaterialApp前添加 "Get"将其变成GetMaterialApp。
```dart
void main() => runApp(GetMaterialApp(home: Home()));
```
- 注意这并不能修改Flutter的MaterialAppGetMaterialApp并不是修改后的MaterialApp它只是一个预先配置的Widget它的子组件是默认的MaterialApp。你可以手动配置但绝对没有必要。GetMaterialApp会创建路由注入它们注入翻译注入你需要的一切路由导航。如果你只用Get来进行状态管理或依赖管理就没有必要使用GetMaterialApp。GetMaterialApp对于路由、snackbar、国际化、bottomSheet、对话框以及与路由相关的高级apis和没有上下文context的情况下是必要的。
- 注2: 只有当你要使用路由管理(`Get.to()`, `Get.back()`等)时才需要这一步。如果你不打算使用它那么就不需要做第1步。
- 第二步:
创建你的业务逻辑类,并将所有的变量,方法和控制器放在里面。
你可以使用一个简单的".obs "使任何变量成为可观察的。
```dart
class Controller extends GetxController{
var count = 0.obs;
increment() => count++;
}
```
- 第三步:
创建你的界面使用StatelessWidget节省一些内存使用Get你可能不再需要使用StatefulWidget。
```dart
class Home extends StatelessWidget {
@override
Widget build(context) {
// 使用Get.put()实例化你的类,使其对当下的所有子路由可用。
final Controller c = Get.put(Controller());
return Scaffold(
// 使用Obx(()=>每当改变计数时就更新Text()。
appBar: AppBar(title: Obx(() => Text("Clicks: ${c.count}"))),
// 用一个简单的Get.to()即可代替Navigator.push那8行无需上下文
body: Center(child: ElevatedButton(
child: Text("Go to Other"), onPressed: () => Get.to(Other()))),
floatingActionButton:
FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment));
}
}
class Other extends StatelessWidget {
// 你可以让Get找到一个正在被其他页面使用的Controller并将它返回给你。
final Controller c = Get.find();
@override
Widget build(context){
// 访问更新后的计数变量
return Scaffold(body: Center(child: Text("${c.count}")));
}
}
```
结果:
![](https://raw.githubusercontent.com/jonataslaw/getx-community/master/counter-app-gif.gif)
这是一个简单的项目但它已经让人明白Get的强大。随着项目的发展这种差异将变得更加显著。
Get的设计是为了与团队合作但它也可以让个人开发者的工作变得更简单。
加快开发速率在不损失性能的情况下按时交付一切。Get并不适合每一个人但如果你认同这句话Get就是为你准备的!
# 三大功能
## 状态管理
目前Flutter有几种状态管理器。但是它们中的大多数都涉及到使用ChangeNotifier来更新widget这对于中大型应用的性能来说是一个很糟糕的方法。你可以在Flutter的官方文档中查看到[ChangeNotifier应该使用1个或最多2个监听器](https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html),这使得它们实际上无法用于任何中等或大型应用。
Get 并不是比任何其他状态管理器更好或更差而是说你应该分析这些要点以及下面的要点来选择只用Get还是与其他状态管理器结合使用。
Get不是其他状态管理器的敌人因为Get是一个微框架而不仅仅是一个状态管理器既可以单独使用也可以与其他状态管理器结合使用。
Get有两个不同的状态管理器简单的状态管理器GetBuilder和响应式状态管理器GetX
### 响应式状态管理器
响应式编程可能会让很多人感到陌生因为觉得它很复杂但是GetX将响应式编程变得非常简单。
- 你不需要创建StreamControllers.
- 你不需要为每个变量创建一个StreamBuilder。
- 你不需要为每个状态创建一个类。
- 你不需要为一个初始值创建一个get。
使用 Get 的响应式编程就像使用 setState 一样简单。
让我们想象一下,你有一个名称变量,并且希望每次你改变它时,所有使用它的小组件都会自动刷新。
这就是你的计数变量。
```dart
var name = 'Jonatas Borges';
```
要想让它变得可观察,你只需要在它的末尾加上".obs"。
```dart
var name = 'Jonatas Borges'.obs;
```
而在UI中当你想显示该值并在值变化时更新页面只需这样做。
```dart
Obx(() => Text("${controller.name}"));
```
这就是全部,就这么简单。
### 关于状态管理的更多细节
**关于状态管理更深入的解释请查看[这里](./documentation/zh_CN/state_management.md)。在那里你将看到更多的例子,以及简单的状态管理器和响应式状态管理器之间的区别**
你会对GetX的能力有一个很好的了解。
## 路由管理
如果你想免上下文context使用路由/snackbars/dialogs/bottomsheetsGetX对你来说也是极好的来吧展示
在你的MaterialApp前加上 "Get"把它变成GetMaterialApp。
```dart
GetMaterialApp( // Before: MaterialApp(
home: MyHome(),
)
```
导航到新页面
```dart
Get.to(NextScreen());
```
用别名导航到新页面。查看更多关于命名路由的详细信息[这里](./documentation/zh_CN/route_management.md#navigation-with-named-routes)
```dart
Get.toNamed('/details');
```
要关闭snackbars, dialogs, bottomsheets或任何你通常会用Navigator.pop(context)关闭的东西。
```dart
Get.back();
```
进入下一个页面,但没有返回上一个页面的选项(用于闪屏页,登录页面等)。
```dart
Get.off(NextScreen());
```
进入下一个页面并取消之前的所有路由(在购物车、投票和测试中很有用)。
```dart
Get.offAll(NextScreen());
```
注意到你不需要使用context来做这些事情吗这就是使用Get路由管理的最大优势之一。有了它你可以在你的控制器类中执行所有这些方法而不用担心context在哪里。
### 关于路由管理的更多细节
**关于别名路由,和对路由的低级控制,请看[这里](./documentation/zh_CN/route_management.md)**
## 依赖管理
Get有一个简单而强大的依赖管理器它允许你只用1行代码就能检索到与你的Bloc或Controller相同的类无需Provider context无需inheritedWidget。
```dart
Controller controller = Get.put(Controller()); // 而不是 Controller controller = Controller();
```
- 注意如果你使用的是Get的状态管理器请多注意绑定api这将使你的界面更容易连接到你的控制器。
你是在Get实例中实例化它而不是在你使用的类中实例化你的类这将使它在整个App中可用。
所以你可以正常使用你的控制器或类Bloc
**提示:** Get依赖管理与包的其他部分是解耦的所以如果你的应用已经使用了一个状态管理器任何一个都没关系你不需要全部重写你可以使用这个依赖注入。
```dart
controller.fetchApi();
```
想象一下你已经浏览了无数条路由现在你需要拿到一个被遗留在控制器中的数据那你需要一个状态管理器与Provider或Get_it一起使用来拿到它对吗用Get则不然Get会自动为你的控制器找到你想要的数据而你甚至不需要任何额外的依赖关系。
```dart
Controller controller = Get.find();
//是的它看起来像魔术Get会找到你的控制器并将其提供给你。你可以实例化100万个控制器Get总会给你正确的控制器。
```
然后你就可以恢复你在后面获得的控制器数据。
```dart
Text(controller.textFromApi);
```
### 关于依赖管理的更多细节
**关于依赖管理的更深入解释请看[此处](./documentation/zh_CN/dependency_management.md)**
# 实用工具
## 国际化
### 翻译
翻译被保存为一个简单的键值字典映射。
要添加自定义翻译,请创建一个类并扩展`翻译`
```dart
import 'package:get/get.dart';
class Messages extends Translations {
@override
Map<String, Map<String, String>> get keys => {
'zh_CN': {
'hello': '你好 世界',
},
'de_DE': {
'hello': 'Hallo Welt',
}
};
}
```
#### 使用翻译
只要将`.tr`追加到指定的键上,就会使用`Get.locale``Get.fallbackLocale`的当前值进行翻译。
```dart
Text('title'.tr);
```
### 语言
传递参数给`GetMaterialApp`来定义语言和翻译。
```dart
return GetMaterialApp(
translations: Messages(), // 你的翻译
locale: Locale('zh', 'CN'), // 将会按照此处指定的语言翻译
fallbackLocale: Locale('en', 'US'), // 添加一个回调语言选项,以备上面指定的语言翻译不存在
);
```
#### 改变语言
调用`Get.updateLocale(locale)`来更新语言环境。然后翻译会自动使用新的locale。
```dart
var locale = Locale('en', 'US');
Get.updateLocale(locale);
```
#### 系统语言
要读取系统语言,可以使用`window.locale`
```dart
import 'dart:ui' as ui;
return GetMaterialApp(
locale: ui.window.locale,
);
```
## 改变主题
请不要使用比`GetMaterialApp`更高级别的widget来更新主题这可能会造成键重复。很多人习惯于创建一个 "ThemeProvider "的widget来改变应用主题这在**GetX™**中是绝对没有必要的。
你可以创建你的自定义主题,并简单地将其添加到`Get.changeTheme`中,而无需任何模板。
```dart
Get.changeTheme(ThemeData.light());
```
如果你想在 "onTap "中创建类似于改变主题的按钮,你可以结合两个**GetX™** API来实现。
- 检查是否使用了深色的 "Theme "的API以及 "Theme "更改API。
-`Theme` Change API你可以把下面的代码放在`onPressed`里。
```dart
Get.changeTheme(Get.isDarkMode? ThemeData.light(): ThemeData.dark());
```
`.darkmode`被激活时它将切换到light主题当light主题被激活时它将切换到dark主题。
## 其他高级API
```dart
// 给出当前页面的args。
Get.arguments
//给出以前的路由名称
Get.previousRoute
// 给出要访问的原始路由例如rawRoute.isFirst()
Get.rawRoute
// 允许从GetObserver访问Rounting API。
Get.routing
// 检查 snackbar 是否打开
Get.isSnackbarOpen
// 检查 dialog 是否打开
Get.isDialogOpen
// 检查 bottomsheet 是否打开
Get.isBottomSheetOpen
// 删除一个路由。
Get.removeRoute()
//反复返回,直到表达式返回真。
Get.until()
// 转到下一条路由并删除所有之前的路由直到表达式返回true。
Get.offUntil()
// 转到下一个命名的路由并删除所有之前的路由直到表达式返回true。
Get.offNamedUntil()
//检查应用程序在哪个平台上运行。
GetPlatform.isAndroid
GetPlatform.isIOS
GetPlatform.isMacOS
GetPlatform.isWindows
GetPlatform.isLinux
GetPlatform.isFuchsia
//检查设备类型
GetPlatform.isMobile
GetPlatform.isDesktop
//所有平台都是独立支持web的!
//你可以知道你是否在浏览器内运行。
//在Windows、iOS、OSX、Android等系统上。
GetPlatform.isWeb
// 相当于.MediaQuery.of(context).size.height,
//但不可改变。
Get.height
Get.width
// 提供当前上下文。
Get.context
// 在你的代码中的任何地方,在前台提供 snackbar/dialog/bottomsheet 的上下文。
Get.contextOverlay
// 注意:以下方法是对上下文的扩展。
// 因为在你的UI的任何地方都可以访问上下文你可以在UI代码的任何地方使用它。
// 如果你需要一个可改变的高度/宽度(如桌面或浏览器窗口可以缩放),你将需要使用上下文。
context.width
context.height
// 让您可以定义一半的页面、三分之一的页面等。
// 对响应式应用很有用。
// 参数: dividedBy (double) 可选 - 默认值1
// 参数: reducedBy (double) 可选 - 默认值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()
///查询设备最短边。
context.mediaQueryShortestSide()
///如果宽度大于800则为真。
context.showNavbar()
///如果最短边小于600p则为真。
context.isPhone()
///如果最短边大于600p则为真。
context.isSmallTablet()
///如果最短边大于720p则为真。
context.isLargeTablet()
///如果当前设备是平板电脑,则为真
context.isTablet()
///根据页面大小返回一个值<T>。
///可以给值为:
///watch如果最短边小于300
///mobile如果最短边小于600
///tablet如果最短边shortestSide小于1200
///desktop如果宽度大于1200
context.responsiveValue<T>()
```
### 可选的全局设置和手动配置
GetMaterialApp为你配置了一切但如果你想手动配置Get。
```dart
MaterialApp(
navigatorKey: Get.key,
navigatorObservers: [GetObserver()],
);
```
你也可以在`GetObserver`中使用自己的中间件,这不会影响任何事情。
```dart
MaterialApp(
navigatorKey: Get.key,
navigatorObservers: [
GetObserver(MiddleWare.observer) // Here
],
);
```
你可以为 "Get "创建_全局设置。只需在推送任何路由之前将`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}) {
// 在这里把信息传递给你最喜欢的日志包。
// 请注意即使enableLog: false日志信息也会在这个回调中被推送。
// 如果你想的话可以通过GetConfig.isLogEnable来检查这个标志。
}
```
### 局部状态组件
这些Widgets允许您管理一个单一的值并保持状态的短暂性和本地性。
我们有Reactive和Simple两种风格。
例如,你可以用它们来切换`TextField`中的obscureText也许可以创建一个自定义的可扩展面板Expandable Panel或者在"Scaffold "的主体中改变内容的同时修改`BottomNavigationBar`中的当前索引。
#### ValueBuilder
`StatefulWidget`的简化,它与`.setState`回调一起工作,并接受更新的值。
```dart
ValueBuilder<bool>(
initialValue: false,
builder: (value, updateFn) => Switch(
value: value,
onChanged: updateFn, // 你可以用( newValue )=> updateFn( newValue )。
),
// 如果你需要调用 builder 方法之外的东西。
onUpdate: (value) => print("Value updated: $value"),
onDispose: () => print("Widget unmounted"),
),
```
#### ObxValue
类似于[`ValueBuilder`](#valuebuilder)但这是Reactive版本你需要传递一个Rx实例还记得神奇的.obs吗自动更新......是不是很厉害?)
```dart
ObxValue((data) => Switch(
value: data.value,
onChanged: data, // Rx 有一个 _callable_函数! 你可以使用 (flag) => data.value = flag,
),
false.obs,
),
```
## 有用的提示
`.obs`ervables (也称为_Rx_ Types)有各种各样的内部方法和操作符。
> `.obs`的属性**是**实际值,不要搞错了!
> 我们避免了变量的类型声明因为Dart的编译器足够聪明而且代码
> 看起来更干净,但:
```dart
var message = 'Hello world'.obs;
print( 'Message "$message" has Type ${message.runtimeType}');
```
即使`message` _prints_实际的字符串值类型也是**RxString**
所以,你不能做`message.substring( 0, 4 )`
你必须在_observable_里面访问真正的`value`
最常用的方法是".value", 但是你也可以用...
```dart
final name = 'GetX'.obs;
//只有在值与当前值不同的情况下,才会 "更新 "流。
name.value = 'Hey';
// 所有Rx属性都是 "可调用 "的,并返回新的值。
//但这种方法不接受 "null"UI将不会重建。
name('Hello');
// 就像一个getter打印'Hello'。
name() ;
///数字。
final count = 0.obs;
// 您可以使用num基元的所有不可变操作!
count + 1;
// 注意!只有当 "count "不是final时这才有效除了var
count += 1;
// 你也可以与数值进行比较。
count > 2;
/// booleans:
final flag = false.obs;
// 在真/假之间切换数值
flag.toggle();
/// 所有类型。
// 将 "value "设为空。
flag.nil();
// 所有的toString()、toJson()操作都会向下传递到`value`。
print( count ); // 在内部调用 "toString() "来GetRxInt
final abc = [0,1,2].obs;
// 将值转换为json数组打印RxList。
// 所有Rx类型都支持Json!
print('json: ${jsonEncode(abc)}, type: ${abc.runtimeType}');
// RxMap, RxList 和 RxSet 是特殊的 Rx 类型,扩展了它们的原生类型。
// 但你可以像使用普通列表一样使用列表,尽管它是响应式的。
abc.add(12); // 将12添加到列表中并更新流。
abc[3]; // 和Lists一样读取索引3。
// Rx和值是平等的但hashCode总是从值中提取。
final number = 12.obs;
print( number == 12 ); // prints > true
///自定义Rx模型。
// toJson(), toString()都是递延给子代的,所以你可以在它们上实现覆盖,并直接打印()可观察的内容。
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.refresh();
// 或者我们可以使用`update()`方法!
user.update((value){
value.name='Roi';
});
print( user );
```
#### GetView
我很喜欢这个Widget很简单很有用。
它是一个对已注册的`Controller`有一个名为`controller`的getter的`const Stateless`的Widget仅此而已。
```dart
class AwesomeController extends GetxController {
final String title = 'My Awesome View';
}
// 一定要记住传递你用来注册控制器的`Type`!
class AwesomeView extends GetView<AwesomeController> {
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(20),
child: Text( controller.title ), // 只需调用 "controller.something"。
);
}
}
```
#### GetWidget
大多数人都不知道这个Widget或者完全搞不清它的用法。
这个用例非常少见且特殊:它 "缓存 "了一个Controller由于_cache_不能成为一个 "const Stateless"因为_cache_所以不能成为一个`const Stateless`)。
> 那么,什么时候你需要 "缓存 "一个Controller
如果你使用了**GetX**的另一个 "不常见 "的特性 `Get.create()`
`Get.create(()=>Controller())` 会在每次调用时生成一个新的`Controller`
`Get.find<Controller>()`
你可以用它来保存Todo项目的列表如果小组件被 "重建",它将保持相同的控制器实例。
#### GetxService
这个类就像一个 "GetxController",它共享相同的生命周期("onInit()"、"onReady()"、"onClose()")。
但里面没有 "逻辑"。它只是通知**GetX**的依赖注入系统,这个子类**不能**从内存中删除。
所以这对保持你的 "服务 "总是可以被`Get.find()`获取到并保持运行是超级有用的。比如
`ApiService``StorageService``CacheService`
```dart
Future<void> main() async {
await initServices(); /// 等待服务初始化.
runApp(SomeApp());
}
/// 在你运行Flutter应用之前让你的服务初始化是一个明智之举。
////因为你可以控制执行流程也许你需要加载一些主题配置apiKey由用户自定义的语言等所以在运行ApiService之前加载SettingService。
///所以GetMaterialApp()不需要重建,可以直接取值。
void initServices() async {
print('starting services ...');
///这里是你放get_storage、hive、shared_pref初始化的地方。
///或者moor连接或者其他什么异步的东西。
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`的唯一方法是使用`Get.reset()`,它就像"热重启 "你的应用程序。
所以如果你需要在你的应用程序的生命周期内对一个类实例进行绝对的持久化,请使用`GetxService`
# 从2.0开始的兼容性变化
1- Rx类型。
| Before | After |
| ------- | ---------- |
| StringX | `RxString` |
| IntX | `RxInt` |
| MapX | `RxMap` |
| ListX | `RxList` |
| NumX | `RxNum` |
| DoubleX | `RxDouble` |
现在RxController和GetBuilder已经合并了你不再需要记住你要用哪个控制器只要用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- Flutter更新后很多时候你的很多包都会坏掉。有时会发生编译错误经常出现的错误至今仍没有答案开发者需要知道错误的来源跟踪错误才会尝试在相应的仓库中开一个问题并看到其问题的解决。Get集中了开发的主要资源状态、依赖和路由管理让你可以在pubspec中添加一个包然后开始工作。Flutter更新后你唯一需要做的就是更新Get依赖然后开始工作。Get还可以解决兼容性问题。有多少次一个包的版本与另一个包的版本不兼容因为一个包在一个版本中使用了依赖而另一个包在另一个版本中使用了依赖使用Get也不用担心这个问题因为所有的东西都在同一个包里是完全兼容的。
2- Flutter很简单Flutter很不可思议但是Flutter仍然有一些代码对于大多数开发者来说可能是不需要的比如`Navigator.of(context).push (context, builder [...]`你写了8行代码仅仅只为了调用一个路由。而使用Get只需`Get.to(Home())`就完成了你将进入下一个页面。动态网页URL是目前Flutter中非常痛苦的一件事而用GetX则非常简单。在Flutter中管理状态管理依赖关系也产生了很多讨论因为pub中的模式有上百种。但是没有什么比在你的变量末尾加一个".obs "更简单的了把你的widget放在一个Obx里面就这样所有对这个变量的更新都会在页面上自动更新。
3-轻松不用担心性能。Flutter的性能已经很惊人了但是想象一下你使用一个状态管理器和一个定位器来分布你的blocs/stores/controllers/等等类。当你不需要那个依赖的时候你必须手动调用排除它。但是你有没有想过简单地使用你的控制器当它不再被任何人使用时它会简单地从内存中删除这就是GetX所做的。有了SmartManagement所有不被使用的东西都会从内存中删除除了编程您不应该担心任何事情。GetX将保证您消耗的是最低限度的必要资源甚至没有为此创建一个逻辑。
4-实际解耦。你可能听说过 "将界面与业务逻辑分离 "的概念。这并不是BLoC、MVC、MVVM的特例市面上的其他标准都有这个概念。但是由于使用了上下文context这个概念在Flutter中往往可以得到缓解。
如果你需要上下文来寻找InheritedWidget你需要在界面中找到它或者通过参数传递上下文。我特别觉得这种解决方案非常丑陋要在团队中工作我们总会对View的业务逻辑产生依赖。Getx与标准的做法不一样虽然它并没有完全禁止使用StatefulWidgets、InitState等但它总有类似的方法可以更干净。控制器是有生命周期的例如当你需要进行APIREST请求时你不依赖于界面中的任何东西。你可以使用onInit来启动http调用当数据到达时变量将被填充。由于GetX是完全响应式的真的在流下工作一旦项目被填充所有使用该变量的widgets将在界面中自动更新。这使得具有UI专业知识的人只需要处理widget除了用户事件比如点击按钮之外不需要向业务逻辑发送任何东西而处理业务逻辑的人将可以自由地单独创建和测试业务逻辑。
这个库会一直更新和实现新的功能。欢迎提供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和Flutter变得更好的几点。
- 帮助将readme翻译成其他语言。
- 为readme添加文档Get的很多功能还没有被记录下来
- 撰写文章或制作视频教大家如何使用Get它们将被记录到readme和未来的Wiki中
- 提供代码/测试的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).
- [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.
- [Flutter GetX use --- simple charm!](https://github.com/CNAD666/getx_template/blob/main/docs/Use%20of%20Flutter%20GetX---simple%20charm!.md) - CNAD666
- [Flutter GetX使用---简洁的魅力!](https://juejin.cn/post/6924104248275763208)

1
packages/get/_config.yml Normal file
View File

@@ -0,0 +1 @@
theme: jekyll-theme-cayman

View File

@@ -0,0 +1,90 @@
# Include option is buggy:
# https://github.com/flutter/flutter/issues/62591
# In case the include issue gets fixed, lines below INCLUDE_FIX
# can be removed
# include: package:flutter_lints/flutter.yaml
# include: package:effective_dart/analysis_options.1.2.0.yaml
analyzer:
strong-mode:
implicit-casts: false
linter:
rules:
await_only_futures: true
# This one is desirable, but that's a lot of work for now
public_member_api_docs: false
# Desirable, but would be breaking changes:
avoid_positional_boolean_parameters: false
constant_identifier_names: false
include_file_not_found: false
# INCLUDE_FIX (copy of effective dart 1.2.0)
# STYLE
camel_case_types: true
close_sinks: true
unnecessary_statements: true
camel_case_extensions: true
library_names: true
file_names: true
library_prefixes: true
non_constant_identifier_names: true
directives_ordering: true
lines_longer_than_80_chars: true # avoid
curly_braces_in_flow_control_structures: true
# DOCUMENTATION
slash_for_doc_comments: true
package_api_docs: true # prefer
#- comment_references # Unused because https://github.com/dart-lang/sdk/issues/36974
# USAGE
implementation_imports: true
avoid_relative_lib_imports: true # prefer
prefer_relative_imports: true # prefer
prefer_adjacent_string_concatenation: true
prefer_interpolation_to_compose_strings: true # prefer
unnecessary_brace_in_string_interps: true # avoid
prefer_collection_literals: true
avoid_function_literals_in_foreach_calls: true # avoid
prefer_iterable_whereType: true
prefer_function_declarations_over_variables: true
unnecessary_lambdas: true
prefer_equal_for_default_values: true
avoid_init_to_null: true
unnecessary_getters_setters: true
annotate_overrides: true
#- unnecessary_getters # prefer # Disabled pending fix: https://github.com/dart-lang/linter/issues/23
#- prefer_expression_function_bodies # consider
unnecessary_this: true
prefer_initializing_formals: true
type_init_formals: true
empty_constructor_bodies: true
unnecessary_new: true
unnecessary_const: true
avoid_catches_without_on_clauses: true # avoid
avoid_catching_errors: true
use_rethrow_when_possible: true
unrelated_type_equality_checks: true
# DESIGN
use_to_and_as_if_applicable: true # prefer
one_member_abstracts: true # avoid
avoid_classes_with_only_static_members: true # avoid
prefer_mixin: true
prefer_final_fields: true # prefer
use_setters_to_change_properties: true
avoid_setters_without_getters: true
avoid_returning_null: true # avoid
avoid_returning_this: true # avoid
type_annotate_public_apis: true # prefer
#- prefer_typing_uninitialized_variables # consider
omit_local_variable_types: true # avoid
avoid_types_on_closure_parameters: true # avoid
avoid_return_types_on_setters: true # avoid
prefer_generic_function_type_aliases: true
avoid_private_typedef_functions: true # prefer
#- use_function_type_syntax_for_parameters # consider
hash_and_equals: true
avoid_equals_and_hash_code_on_mutable_classes: true # avoid
avoid_null_checks_in_equality_operators: true

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1 @@
[]

Binary file not shown.

View File

@@ -0,0 +1,410 @@
# Dependency Management
- [Dependency Management](#dependency-management)
- [Instancing methods](#instancing-methods)
- [Get.put()](#getput)
- [Get.lazyPut](#getlazyput)
- [Get.putAsync](#getputasync)
- [Get.create](#getcreate)
- [Using instantiated methods/classes](#using-instantiated-methodsclasses)
- [Specifying an alternate instance](#specifying-an-alternate-instance)
- [Differences between methods](#differences-between-methods)
- [Bindings](#bindings)
- [Bindings class](#bindings-class)
- [BindingsBuilder](#bindingsbuilder)
- [SmartManagement](#smartmanagement)
- [How to change](#how-to-change)
- [SmartManagement.full](#smartmanagementfull)
- [SmartManagement.onlyBuilders](#smartmanagementonlybuilders)
- [SmartManagement.keepFactory](#smartmanagementkeepfactory)
- [How bindings work under the hood](#how-bindings-work-under-the-hood)
- [Notes](#notes)
Get has a simple and powerful dependency manager that allows you to retrieve the same class as your Bloc or Controller with just 1 lines of code, no Provider context, no inheritedWidget:
```dart
Controller controller = Get.put(Controller()); // Rather Controller controller = Controller();
```
Instead of instantiating your class within the class you are using, you are instantiating it within the Get instance, which will make it available throughout your App.
So you can use your controller (or Bloc class) normally
- Note: If you are using Get's State Manager, pay more attention to the [Bindings](#bindings) api, which will make easier to connect your view to your controller.
- Note²: Get dependency management is decloupled from other parts of the package, so if for example your app is already using a state manager (any one, it doesn't matter), you don't need to change that, you can use this dependency injection manager with no problems at all
## Instancing methods
The methods and it's configurable parameters are:
### Get.put()
The most common way of inserting a dependency. Good for the controllers of your views for example.
```dart
Get.put<SomeClass>(SomeClass());
Get.put<LoginController>(LoginController(), permanent: true);
Get.put<ListItemController>(ListItemController, tag: "some unique string");
```
This is all options you can set when using put:
```dart
Get.put<S>(
// mandatory: the class that you want to get to save, like a controller or anything
// note: "S" means that it can be a class of any type
S dependency
// optional: this is for when you want multiple classess that are of the same type
// since you normally get a class by using Get.find<Controller>(),
// you need to use tag to tell which instance you need
// must be unique string
String tag,
// optional: by default, get will dispose instances after they are not used anymore (example,
// the controller of a view that is closed), but you might need that the instance
// to be kept there throughout the entire app, like an instance of sharedPreferences or something
// so you use this
// defaults to false
bool permanent = false,
// optional: allows you after using an abstract class in a test, replace it with another one and follow the test.
// defaults to false
bool overrideAbstract = false,
// optional: allows you to create the dependency using function instead of the dependency itself.
// this one is not commonly used
InstanceBuilderCallback<S> builder,
)
```
### Get.lazyPut
It is possible to lazyLoad a dependency so that it will be instantiated only when is used. Very useful for computational expensive classes or if you want to instantiate several classes in just one place (like in a Bindings class) and you know you will not gonna use that class at that time.
```dart
/// ApiMock will only be called when someone uses Get.find<ApiMock> for the first time
Get.lazyPut<ApiMock>(() => ApiMock());
Get.lazyPut<FirebaseAuth>(
() {
// ... some logic if needed
return FirebaseAuth();
},
tag: Math.random().toString(),
fenix: true
)
Get.lazyPut<Controller>( () => Controller() )
```
This is all options you can set when using lazyPut:
```dart
Get.lazyPut<S>(
// mandatory: a method that will be executed when your class is called for the first time
InstanceBuilderCallback builder,
// optional: same as Get.put(), it is used for when you want multiple different instance of a same class
// must be unique
String tag,
// optional: It is similar to "permanent", the difference is that the instance is discarded when
// is not being used, but when it's use is needed again, Get will recreate the instance
// just the same as "SmartManagement.keepFactory" in the bindings api
// defaults to false
bool fenix = false
)
```
### Get.putAsync
If you want to register an asynchronous instance, you can use `Get.putAsync`:
```dart
Get.putAsync<SharedPreferences>(() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setInt('counter', 12345);
return prefs;
});
Get.putAsync<YourAsyncClass>( () async => await YourAsyncClass() )
```
This is all options you can set when using putAsync:
```dart
Get.putAsync<S>(
// mandatory: an async method that will be executed to instantiate your class
AsyncInstanceBuilderCallback<S> builder,
// optional: same as Get.put(), it is used for when you want multiple different instance of a same class
// must be unique
String tag,
// optional: same as in Get.put(), used when you need to maintain that instance alive in the entire app
// defaults to false
bool permanent = false
)
```
### Get.create
This one is tricky. A detailed explanation of what this is and the differences between the other one can be found on [Differences between methods:](#differences-between-methods) section
```dart
Get.Create<SomeClass>(() => SomeClass());
Get.Create<LoginController>(() => LoginController());
```
This is all options you can set when using create:
```dart
Get.create<S>(
// required: a function that returns a class that will be "fabricated" every
// time `Get.find()` is called
// Example: Get.create<YourClass>(() => YourClass())
FcBuilderFunc<S> builder,
// optional: just like Get.put(), but it is used when you need multiple instances
// of a of a same class
// Useful in case you have a list that each item need it's own controller
// needs to be a unique string. Just change from tag to name
String name,
// optional: just like int`Get.put()`, it is for when you need to keep the
// instance alive thoughout the entire app. The difference is in Get.create
// permanent is true by default
bool permanent = true
```
## Using instantiated methods/classes
Imagine that you have navigated through numerous routes, and you need a data that was left behind in your controller, you would need a state manager combined with the Provider or Get_it, correct? Not with Get. You just need to ask Get to "find" for your controller, you don't need any additional dependencies:
```dart
final controller = Get.find<Controller>();
// OR
Controller controller = Get.find();
// Yes, it looks like Magic, Get will find your controller, and will deliver it to you.
// You can have 1 million controllers instantiated, Get will always give you the right controller.
```
And then you will be able to recover your controller data that was obtained back there:
```dart
Text(controller.textFromApi);
```
Since the returned value is a normal class, you can do anything you want:
```dart
int count = Get.find<SharedPreferences>().getInt('counter');
print(count); // out: 12345
```
To remove an instance of Get:
```dart
Get.delete<Controller>(); //usually you don't need to do this because GetX already delete unused controllers
```
## Specifying an alternate instance
A currently inserted instance can be replaced with a similar or extended class instance by using the `replace` or `lazyReplace` method. This can then be retrieved by using the original class.
```dart
abstract class BaseClass {}
class ParentClass extends BaseClass {}
class ChildClass extends ParentClass {
bool isChild = true;
}
Get.put<BaseClass>(ParentClass());
Get.replace<BaseClass>(ChildClass());
final instance = Get.find<BaseClass>();
print(instance is ChildClass); //true
class OtherClass extends BaseClass {}
Get.lazyReplace<BaseClass>(() => OtherClass());
final instance = Get.find<BaseClass>();
print(instance is ChildClass); // false
print(instance is OtherClass); //true
```
## Differences between methods
First, let's of the `fenix` of Get.lazyPut and the `permanent` of the other methods.
The fundamental difference between `permanent` and `fenix` is how you want to store your instances.
Reinforcing: by default, GetX deletes instances when they are not in use.
It means that: If screen 1 has controller 1 and screen 2 has controller 2 and you remove the first route from stack, (like if you use `Get.off()` or `Get.offNamed()`) the controller 1 lost its use so it will be erased.
But if you want to opt for using `permanent:true`, then the controller will not be lost in this transition - which is very useful for services that you want to keep alive throughout the entire application.
`fenix` in the other hand is for services that you don't worry in losing between screen changes, but when you need that service, you expect that it is alive. So basically, it will dispose the unused controller/service/class, but when you need it, it will "recreate from the ashes" a new instance.
Proceeding with the differences between methods:
- Get.put and Get.putAsync follows the same creation order, with the difference that the second uses an asynchronous method: those two methods creates and initializes the instance. That one is inserted directly in the memory, using the internal method `insert` with the parameters `permanent: false` and `isSingleton: true` (this isSingleton parameter only purpose is to tell if it is to use the dependency on `dependency` or if it is to use the dependency on `FcBuilderFunc`). After that, `Get.find()` is called that immediately initialize the instances that are on memory.
- Get.create: As the name implies, it will "create" your dependency! Similar to `Get.put()`, it also calls the internal method `insert` to instancing. But `permanent` became true and `isSingleton` became false (since we are "creating" our dependency, there is no way for it to be a singleton instace, that's why is false). And because it has `permanent: true`, we have by default the benefit of not losing it between screens! Also, `Get.find()` is not called immediately, it wait to be used in the screen to be called. It is created this way to make use of the parameter `permanent`, since then, worth noticing, `Get.create()` was made with the goal of create not shared instances, but don't get disposed, like for example a button in a listView, that you want a unique instance for that list - because of that, Get.create must be used together with GetWidget.
- Get.lazyPut: As the name implies, it is a lazy proccess. The instance is create, but it is not called to be used immediately, it remains waiting to be called. Contrary to the other methods, `insert` is not called here. Instead, the instance is inserted in another part of the memory, a part responsible to tell if the instance can be recreated or not, let's call it "factory". If we want to create something to be used later, it will not be mix with things been used right now. And here is where `fenix` magic enters: if you opt to leaving `fenix: false`, and your `smartManagement` are not `keepFactory`, then when using `Get.find` the instance will change the place in the memory from the "factory" to common instance memory area. Right after that, by default it is removed from the "factory". Now, if you opt for `fenix: true`, the instance continues to exist in this dedicated part, even going to the common area, to be called again in the future.
## Bindings
One of the great differentials of this package, perhaps, is the possibility of full integration of the routes, state manager and dependency manager.
When a route is removed from the Stack, all controllers, variables, and instances of objects related to it are removed from memory. If you are using streams or timers, they will be closed automatically, and you don't have to worry about any of that.
In version 2.10 Get completely implemented the Bindings API.
Now you no longer need to use the init method. You don't even have to type your controllers if you don't want to. You can start your controllers and services in the appropriate place for that.
The Binding class is a class that will decouple dependency injection, while "binding" routes to the state manager and dependency manager.
This allows Get to know which screen is being displayed when a particular controller is used and to know where and how to dispose of it.
In addition, the Binding class will allow you to have SmartManager configuration control. You can configure the dependencies to be arranged when removing a route from the stack, or when the widget that used it is laid out, or neither. You will have intelligent dependency management working for you, but even so, you can configure it as you wish.
### Bindings class
- Create a class and implements Binding
```dart
class HomeBinding implements Bindings {}
```
Your IDE will automatically ask you to override the "dependencies" method, and you just need to click on the lamp, override the method, and insert all the classes you are going to use on that route:
```dart
class HomeBinding implements Bindings {
@override
void dependencies() {
Get.lazyPut<HomeController>(() => HomeController());
Get.put<Service>(()=> Api());
}
}
class DetailsBinding implements Bindings {
@override
void dependencies() {
Get.lazyPut<DetailsController>(() => DetailsController());
}
}
```
Now you just need to inform your route, that you will use that binding to make the connection between route manager, dependencies and states.
- Using named routes:
```dart
getPages: [
GetPage(
name: '/',
page: () => HomeView(),
binding: HomeBinding(),
),
GetPage(
name: '/details',
page: () => DetailsView(),
binding: DetailsBinding(),
),
];
```
- Using normal routes:
```dart
Get.to(Home(), binding: HomeBinding());
Get.to(DetailsView(), binding: DetailsBinding())
```
There, you don't have to worry about memory management of your application anymore, Get will do it for you.
The Binding class is called when a route is called, you can create an "initialBinding in your GetMaterialApp to insert all the dependencies that will be created.
```dart
GetMaterialApp(
initialBinding: SampleBind(),
home: Home(),
);
```
### BindingsBuilder
The default way of creating a binding is by creating a class that implements Bindings.
But alternatively, you can use `BindingsBuilder` callback so that you can simply use a function to instantiate whatever you desire.
Example:
```dart
getPages: [
GetPage(
name: '/',
page: () => HomeView(),
binding: BindingsBuilder(() {
Get.lazyPut<ControllerX>(() => ControllerX());
Get.put<Service>(()=> Api());
}),
),
GetPage(
name: '/details',
page: () => DetailsView(),
binding: BindingsBuilder(() {
Get.lazyPut<DetailsController>(() => DetailsController());
}),
),
];
```
That way you can avoid to create one Binding class for each route making this even simpler.
Both ways of doing work perfectly fine and we want you to use what most suit your tastes.
### SmartManagement
GetX by default disposes unused controllers from memory, even if a failure occurs and a widget that uses it is not properly disposed.
This is what is called the `full` mode of dependency management.
But if you want to change the way GetX controls the disposal of classes, you have `SmartManagement` class that you can set different behaviors.
#### How to change
If you want to change this config (which you usually don't need) this is the way:
```dart
void main () {
runApp(
GetMaterialApp(
smartManagement: SmartManagement.onlyBuilders //here
home: Home(),
)
)
}
```
#### SmartManagement.full
It is the default one. Dispose classes that are not being used and were not set to be permanent. In the majority of the cases you will want to keep this config untouched. If you new to GetX then don't change this.
#### SmartManagement.onlyBuilders
With this option, only controllers started in `init:` or loaded into a Binding with `Get.lazyPut()` will be disposed.
If you use `Get.put()` or `Get.putAsync()` or any other approach, SmartManagement will not have permissions to exclude this dependency.
With the default behavior, even widgets instantiated with "Get.put" will be removed, unlike SmartManagement.onlyBuilders.
#### SmartManagement.keepFactory
Just like SmartManagement.full, it will remove it's dependencies when it's not being used anymore. However, it will keep their factory, which means it will recreate the dependency if you need that instance again.
### How bindings work under the hood
Bindings creates transitory factories, which are created the moment you click to go to another screen, and will be destroyed as soon as the screen-changing animation happens.
This happens so fast that the analyzer will not even be able to register it.
When you navigate to this screen again, a new temporary factory will be called, so this is preferable to using SmartManagement.keepFactory, but if you don't want to create Bindings, or want to keep all your dependencies on the same Binding, it will certainly help you.
Factories take up little memory, they don't hold instances, but a function with the "shape" of that class you want.
This has a very low cost in memory, but since the purpose of this lib is to get the maximum performance possible using the minimum resources, Get removes even the factories by default.
Use whichever is most convenient for you.
## Notes
- DO NOT USE SmartManagement.keepFactory if you are using multiple Bindings. It was designed to be used without Bindings, or with a single Binding linked in the GetMaterialApp's initialBinding.
- Using Bindings is completely optional, if you want you can use `Get.put()` and `Get.find()` on classes that use a given controller without any problem.
However, if you work with Services or any other abstraction, I recommend using Bindings for a better organization.

View File

@@ -0,0 +1,569 @@
- [Route Management](#route-management)
- [How to use](#how-to-use)
- [Navigation without named routes](#navigation-without-named-routes)
- [Navigation with named routes](#navigation-with-named-routes)
- [Send data to named Routes](#send-data-to-named-routes)
- [Dynamic urls links](#dynamic-urls-links)
- [Middleware](#middleware)
- [Navigation without context](#navigation-without-context)
- [SnackBars](#snackbars)
- [Dialogs](#dialogs)
- [BottomSheets](#bottomsheets)
- [Nested Navigation](#nested-navigation)
# Route Management
This is the complete explanation of all there is to Getx when the matter is route management.
## How to use
Add this to your pubspec.yaml file:
```yaml
dependencies:
get:
```
If you are going to use routes/snackbars/dialogs/bottomsheets without context, or use the high-level Get APIs, you need to simply add "Get" before your MaterialApp, turning it into GetMaterialApp and enjoy!
```dart
GetMaterialApp( // Before: MaterialApp(
home: MyHome(),
)
```
## Navigation without named routes
To navigate to a new screen:
```dart
Get.to(NextScreen());
```
To close snackbars, dialogs, bottomsheets, or anything you would normally close with Navigator.pop(context);
```dart
Get.back();
```
To go to the next screen and no option to go back to the previous screen (for use in SplashScreens, login screens and etc.)
```dart
Get.off(NextScreen());
```
To go to the next screen and cancel all previous routes (useful in shopping carts, polls, and tests)
```dart
Get.offAll(NextScreen());
```
To navigate to the next route, and receive or update data as soon as you return from it:
```dart
var data = await Get.to(Payment());
```
on other screen, send a data for previous route:
```dart
Get.back(result: 'success');
```
And use it:
ex:
```dart
if(data == 'success') madeAnything();
```
Don't you want to learn our syntax?
Just change the Navigator (uppercase) to navigator (lowercase), and you will have all the functions of the standard navigation, without having to use context
Example:
```dart
// Default Flutter navigator
Navigator.of(context).push(
context,
MaterialPageRoute(
builder: (BuildContext context) {
return HomePage();
},
),
);
// Get using Flutter syntax without needing context
navigator.push(
MaterialPageRoute(
builder: (_) {
return HomePage();
},
),
);
// Get syntax (It is much better, but you have the right to disagree)
Get.to(HomePage());
```
## Navigation with named routes
- If you prefer to navigate by namedRoutes, Get also supports this.
To navigate to nextScreen
```dart
Get.toNamed("/NextScreen");
```
To navigate and remove the previous screen from the tree.
```dart
Get.offNamed("/NextScreen");
```
To navigate and remove all previous screens from the tree.
```dart
Get.offAllNamed("/NextScreen");
```
To define routes, use GetMaterialApp:
```dart
void main() {
runApp(
GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => MyHomePage()),
GetPage(name: '/second', page: () => Second()),
GetPage(
name: '/third',
page: () => Third(),
transition: Transition.zoom
),
],
)
);
}
```
To handle navigation to non-defined routes (404 error), you can define an unknownRoute page in GetMaterialApp.
```dart
void main() {
runApp(
GetMaterialApp(
unknownRoute: GetPage(name: '/notfound', page: () => UnknownRoutePage()),
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => MyHomePage()),
GetPage(name: '/second', page: () => Second()),
],
)
);
}
```
### Send data to named Routes
Just send what you want for arguments. Get accepts anything here, whether it is a String, a Map, a List, or even a class instance.
```dart
Get.toNamed("/NextScreen", arguments: 'Get is the best');
```
on your class or controller:
```dart
print(Get.arguments);
//print out: Get is the best
```
### Dynamic urls links
Get offer advanced dynamic urls just like on the Web. Web developers have probably already wanted this feature on Flutter, and most likely have seen a package promise this feature and deliver a totally different syntax than a URL would have on web, but Get also solves that.
```dart
Get.offAllNamed("/NextScreen?device=phone&id=354&name=Enzo");
```
on your controller/bloc/stateful/stateless class:
```dart
print(Get.parameters['id']);
// out: 354
print(Get.parameters['name']);
// out: Enzo
```
You can also receive NamedParameters with Get easily:
```dart
void main() {
runApp(
GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(
name: '/',
page: () => MyHomePage(),
),
GetPage(
name: '/profile/',
page: () => MyProfile(),
),
//You can define a different page for routes with arguments, and another without arguments, but for that you must use the slash '/' on the route that will not receive arguments as above.
GetPage(
name: '/profile/:user',
page: () => UserProfile(),
),
GetPage(
name: '/third',
page: () => Third(),
transition: Transition.cupertino
),
],
)
);
}
```
Send data on route name
```dart
Get.toNamed("/profile/34954");
```
On second screen take the data by parameter
```dart
print(Get.parameters['user']);
// out: 34954
```
or send multiple parameters like this
```dart
Get.toNamed("/profile/34954?flag=true&country=italy");
```
or
```dart
var parameters = <String, String>{"flag": "true","country": "italy",};
Get.toNamed("/profile/34954", parameters: parameters);
```
On second screen take the data by parameters as usually
```dart
print(Get.parameters['user']);
print(Get.parameters['flag']);
print(Get.parameters['country']);
// out: 34954 true italy
```
And now, all you need to do is use Get.toNamed() to navigate your named routes, without any context (you can call your routes directly from your BLoC or Controller class), and when your app is compiled to the web, your routes will appear in the url <3
### Middleware
If you want to listen Get events to trigger actions, you can to use routingCallback to it
```dart
GetMaterialApp(
routingCallback: (routing) {
if(routing.current == '/second'){
openAds();
}
}
)
```
If you are not using GetMaterialApp, you can use the manual API to attach Middleware observer.
```dart
void main() {
runApp(
MaterialApp(
onGenerateRoute: Router.generateRoute,
initialRoute: "/",
navigatorKey: Get.key,
navigatorObservers: [
GetObserver(MiddleWare.observer), // HERE !!!
],
),
);
}
```
Create a MiddleWare class
```dart
class MiddleWare {
static observer(Routing routing) {
/// You can listen in addition to the routes, the snackbars, dialogs and bottomsheets on each screen.
///If you need to enter any of these 3 events directly here,
///you must specify that the event is != Than you are trying to do.
if (routing.current == '/second' && !routing.isSnackbar) {
Get.snackbar("Hi", "You are on second route");
} else if (routing.current =='/third'){
print('last route called');
}
}
}
```
Now, use Get on your code:
```dart
class First extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.add),
onPressed: () {
Get.snackbar("hi", "i am a modern snackbar");
},
),
title: Text('First Route'),
),
body: Center(
child: ElevatedButton(
child: Text('Open route'),
onPressed: () {
Get.toNamed("/second");
},
),
),
);
}
}
class Second extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.add),
onPressed: () {
Get.snackbar("hi", "i am a modern snackbar");
},
),
title: Text('second Route'),
),
body: Center(
child: ElevatedButton(
child: Text('Open route'),
onPressed: () {
Get.toNamed("/third");
},
),
),
);
}
}
class Third extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Third Route"),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Get.back();
},
child: Text('Go back!'),
),
),
);
}
}
```
## Navigation without context
### SnackBars
To have a simple SnackBar with Flutter, you must get the context of Scaffold, or you must use a GlobalKey attached to your Scaffold
```dart
final snackBar = SnackBar(
content: Text('Hi!'),
action: SnackBarAction(
label: 'I am a old and ugly snackbar :(',
onPressed: (){}
),
);
// Find the Scaffold in the widget tree and use
// it to show a SnackBar.
Scaffold.of(context).showSnackBar(snackBar);
```
With Get:
```dart
Get.snackbar('Hi', 'i am a modern snackbar');
```
With Get, all you have to do is call your Get.snackbar from anywhere in your code or customize it however you want!
```dart
Get.snackbar(
"Hey i'm a Get SnackBar!", // title
"It's unbelievable! I'm using SnackBar without context, without boilerplate, without Scaffold, it is something truly amazing!", // message
icon: Icon(Icons.alarm),
shouldIconPulse: true,
onTap:(){},
barBlur: 20,
isDismissible: true,
duration: Duration(seconds: 3),
);
////////// ALL FEATURES //////////
// Color colorText,
// Duration duration,
// SnackPosition snackPosition,
// Widget titleText,
// Widget messageText,
// bool instantInit,
// Widget icon,
// bool shouldIconPulse,
// double maxWidth,
// EdgeInsets margin,
// EdgeInsets padding,
// double borderRadius,
// Color borderColor,
// double borderWidth,
// Color backgroundColor,
// Color leftBarIndicatorColor,
// List<BoxShadow> boxShadows,
// Gradient backgroundGradient,
// TextButton mainButton,
// OnTap onTap,
// bool isDismissible,
// bool showProgressIndicator,
// AnimationController progressIndicatorController,
// Color progressIndicatorBackgroundColor,
// Animation<Color> progressIndicatorValueColor,
// SnackStyle snackStyle,
// Curve forwardAnimationCurve,
// Curve reverseAnimationCurve,
// Duration animationDuration,
// double barBlur,
// double overlayBlur,
// Color overlayColor,
// Form userInputForm
///////////////////////////////////
```
If you prefer the traditional snackbar, or want to customize it from scratch, including adding just one line (Get.snackbar makes use of a mandatory title and message), you can use
`Get.rawSnackbar();` which provides the RAW API on which Get.snackbar was built.
### Dialogs
To open dialog:
```dart
Get.dialog(YourDialogWidget());
```
To open default dialog:
```dart
Get.defaultDialog(
onConfirm: () => print("Ok"),
middleText: "Dialog made in 3 lines of code"
);
```
You can also use Get.generalDialog instead of showGeneralDialog.
For all other Flutter dialog widgets, including cupertinos, you can use Get.overlayContext instead of context, and open it anywhere in your code.
For widgets that don't use Overlay, you can use Get.context.
These two contexts will work in 99% of cases to replace the context of your UI, except for cases where inheritedWidget is used without a navigation context.
### BottomSheets
Get.bottomSheet is like showModalBottomSheet, but don't need of context.
```dart
Get.bottomSheet(
Container(
child: Wrap(
children: <Widget>[
ListTile(
leading: Icon(Icons.music_note),
title: Text('Music'),
onTap: () {}
),
ListTile(
leading: Icon(Icons.videocam),
title: Text('Video'),
onTap: () {},
),
],
),
)
);
```
## Nested Navigation
Get made Flutter's nested navigation even easier.
You don't need the context, and you will find your navigation stack by Id.
- NOTE: Creating parallel navigation stacks can be dangerous. The ideal is not to use NestedNavigators, or to use sparingly. If your project requires it, go ahead, but keep in mind that keeping multiple navigation stacks in memory may not be a good idea for RAM consumption.
See how simple it is:
```dart
Navigator(
key: Get.nestedKey(1), // create a key by index
initialRoute: '/',
onGenerateRoute: (settings) {
if (settings.name == '/') {
return GetPageRoute(
page: () => Scaffold(
appBar: AppBar(
title: Text("Main"),
),
body: Center(
child: TextButton(
color: Colors.blue,
onPressed: () {
Get.toNamed('/second', id:1); // navigate by your nested route by index
},
child: Text("Go to second"),
),
),
),
);
} else if (settings.name == '/second') {
return GetPageRoute(
page: () => Center(
child: Scaffold(
appBar: AppBar(
title: Text("Main"),
),
body: Center(
child: Text("second")
),
),
),
);
}
}
),
```

View File

@@ -0,0 +1,789 @@
* [State Management](#state-management)
+ [Reactive State Manager](#reactive-state-manager)
- [Advantages](#advantages)
- [Maximum performance:](#maximum-performance)
- [Declaring a reactive variable](#declaring-a-reactive-variable)
- [Having a reactive state, is easy.](#having-a-reactive-state-is-easy)
- [Using the values in the view](#using-the-values-in-the-view)
- [Conditions to rebuild](#conditions-to-rebuild)
- [Where .obs can be used](#where-obs-can-be-used)
- [Note about Lists](#note-about-lists)
- [Why i have to use .value](#why-i-have-to-use-value)
- [Obx()](#obx)
- [Workers](#workers)
+ [Simple State Manager](#simple-state-manager)
- [Advantages](#advantages-1)
- [Usage](#usage)
- [How it handles controllers](#how-it-handles-controllers)
- [You won't need StatefulWidgets anymore](#you-wont-need-statefulwidgets-anymore)
- [Why it exists](#why-it-exists)
- [Other ways of using it](#other-ways-of-using-it)
- [Unique IDs](#unique-ids)
+ [Mixing the two state managers](#mixing-the-two-state-managers)
+ [GetBuilder vs GetX vs Obx vs MixinBuilder](#getbuilder-vs-getx-vs-obx-vs-mixinbuilder)
# State Management
GetX does not use Streams or ChangeNotifier like other state managers. Why? In addition to building applications for android, iOS, web, linux, macos and linux, with GetX you can build server applications with the same syntax as Flutter/GetX. In order to improve response time and reduce RAM consumption, we created GetValue and GetStream, which are low latency solutions that deliver a lot of performance, at a low operating cost. We use this base to build all of our resources, including state management.
* _Complexity_: Some state managers are complex and have a lot of boilerplate. With GetX you don't have to define a class for each event, the code is highly clean and clear, and you do a lot more by writing less. Many people have given up on Flutter because of this topic, and they now finally have a stupidly simple solution for managing states.
* _No code generators_: You spend half your development time writing your application logic. Some state managers rely on code generators to have minimally readable code. Changing a variable and having to run build_runner can be unproductive, and often the waiting time after a flutter clean will be long, and you will have to drink a lot of coffee.
With GetX everything is reactive, and nothing depends on code generators, increasing your productivity in all aspects of your development.
* _It does not depend on context_: You probably already needed to send the context of your view to a controller, making the View's coupling with your business logic high. You have probably had to use a dependency for a place that has no context, and had to pass the context through various classes and functions. This just doesn't exist with GetX. You have access to your controllers from within your controllers without any context. You don't need to send the context by parameter for literally nothing.
* _Granular control_: most state managers are based on ChangeNotifier. ChangeNotifier will notify all widgets that depend on it when notifyListeners is called. If you have 40 widgets on one screen, which have a variable of your ChangeNotifier class, when you update one, all of them will be rebuilt.
With GetX, even nested widgets are respected. If you have Obx watching your ListView, and another watching a checkbox inside the ListView, when changing the CheckBox value, only it will be updated, when changing the List value, only the ListView will be updated.
* _It only reconstructs if its variable REALLY changes_: GetX has flow control, that means if you display a Text with 'Paola', if you change the observable variable to 'Paola' again, the widget will not be reconstructed. That's because GetX knows that 'Paola' is already being displayed in Text, and will not do unnecessary reconstructions.
Most (if not all) current state managers will rebuild on the screen.
## Reactive State Manager
Reactive programming can alienate many people because it is said to be complicated. GetX turns reactive programming into something quite simple:
* You won't need to create StreamControllers.
* You won't need to create a StreamBuilder for each variable
* You will not need to create a class for each state.
* You will not need to create a get for an initial value.
Reactive programming with Get is as easy as using setState.
Let's imagine that you have a name variable and want that every time you change it, all widgets that use it are automatically changed.
This is your count variable:
``` dart
var name = 'Jonatas Borges';
```
To make it observable, you just need to add ".obs" to the end of it:
``` dart
var name = 'Jonatas Borges'.obs;
```
That's all. It's *that* simple.
From now on, we might refer to this reactive-".obs"(ervables) variables as _Rx_.
What did we do under the hood? We created a `Stream` of `String` s, assigned the initial value `"Jonatas Borges"` , we notified all widgets that use `"Jonatas Borges"` that they now "belong" to this variable, and when the _Rx_ value changes, they will have to change as well.
This is the **magic of GetX**, thanks to Dart's capabilities.
But, as we know, a `Widget` can only be changed if it is inside a function, because static classes do not have the power to "auto-change".
You will need to create a `StreamBuilder` , subscribe to this variable to listen for changes, and create a "cascade" of nested `StreamBuilder` if you want to change several variables in the same scope, right?
No, you don't need a `StreamBuilder` , but you are right about static classes.
Well, in the view, we usually have a lot of boilerplate when we want to change a specific Widget, that's the Flutter way.
With **GetX** you can also forget about this boilerplate code.
`StreamBuilder( … )` ? `initialValue: …` ? `builder: …` ? Nope, you just need to place this variable inside an `Obx()` Widget.
``` dart
Obx (() => Text (controller.name));
```
_What do you need to memorize?_ Only `Obx(() =>` .
You are just passing that Widget through an arrow-function into an `Obx()` (the "Observer" of the _Rx_).
`Obx` is pretty smart, and will only change if the value of `controller.name` changes.
If `name` is `"John"` , and you change it to `"John"` ( `name.value = "John"` ), as it's the same `value` as before, nothing will change on the screen, and `Obx` , to save resources, will simply ignore the new value and not rebuild the Widget. **Isn't that amazing?**
> So, what if I have 5 _Rx_ (observable) variables within an `Obx` ?
It will just update when **any** of them changes.
> And if I have 30 variables in a class, when I update one, will it update **all** the variables that are in that class?
Nope, just the **specific Widget** that uses that _Rx_ variable.
So, **GetX** only updates the screen, when the _Rx_ variable changes it's value.
```
final isOpen = false.obs;
// NOTHING will happen... same value.
void onButtonTap() => isOpen.value=false;
```
### Advantages
**GetX()** helps you when you need **granular** control over what's being updated.
If you do not need `unique IDs` , because all your variables will be modified when you perform an action, then use `GetBuilder` ,
because it's a Simple State Updater (in blocks, like `setState()` ), made in just a few lines of code.
It was made simple, to have the least CPU impact, and just to fulfill a single purpose (a _State_ rebuild) and spend the minimum resources possible.
If you need a **powerful** State Manager, you can't go wrong with **GetX**.
It doesn't work with variables, but __flows__, everything in it are `Streams` under the hood.
You can use _rxDart_ in conjunction with it, because everything are `Streams`,
you can listen to the `event` of each "_Rx_ variable",
because everything in it are `Streams`.
It is literally a _BLoC_ approach, easier than _MobX_, and without code generators or decorations.
You can turn **anything** into an _"Observable"_ with just a `.obs` .
### Maximum performance:
In addition to having a smart algorithm for minimal rebuilds, **GetX** uses comparators
to make sure the State has changed.
If you experience any errors in your app, and send a duplicate change of State,
**GetX** will ensure it will not crash.
With **GetX** the State only changes if the `value` change.
That's the main difference between **GetX**, and using _ `computed` from MobX_.
When joining two __observables__, and one changes; the listener of that _observable_ will change as well.
With **GetX**, if you join two variables, `GetX()` (similar to `Observer()` ) will only rebuild if it implies a real change of State.
### Declaring a reactive variable
You have 3 ways to turn a variable into an "observable".
1 - The first is using **`Rx{Type}`**.
``` dart
// initial value is recommended, but not mandatory
final name = RxString('');
final isLogged = RxBool(false);
final count = RxInt(0);
final balance = RxDouble(0.0);
final items = RxList<String>([]);
final myMap = RxMap<String, int>({});
```
2 - The second is to use **`Rx`** and use Darts Generics, `Rx<Type>`
``` dart
final name = Rx<String>('');
final isLogged = Rx<Bool>(false);
final count = Rx<Int>(0);
final balance = Rx<Double>(0.0);
final number = Rx<Num>(0);
final items = Rx<List<String>>([]);
final myMap = Rx<Map<String, int>>({});
// Custom classes - it can be any class, literally
final user = Rx<User>();
```
3 - The third, more practical, easier and preferred approach, just add **`.obs`** as a property of your `value` :
``` dart
final name = ''.obs;
final isLogged = false.obs;
final count = 0.obs;
final balance = 0.0.obs;
final number = 0.obs;
final items = <String>[].obs;
final myMap = <String, int>{}.obs;
// Custom classes - it can be any class, literally
final user = User().obs;
```
##### Having a reactive state, is easy.
As we know, _Dart_ is now heading towards _null safety_.
To be prepared, from now on, you should always start your _Rx_ variables with an **initial value**.
> Transforming a variable into an _observable_ + _initial value_ with **GetX** is the simplest, and most practical approach.
You will literally add a " `.obs` " to the end of your variable, and **thats it**, youve made it observable,
and its `.value` , well, will be the _initial value_).
### Using the values in the view
``` dart
// controller file
final count1 = 0.obs;
final count2 = 0.obs;
int get sum => count1.value + count2.value;
```
``` dart
// view file
GetX<Controller>(
builder: (controller) {
print("count 1 rebuild");
return Text('${controller.count1.value}');
},
),
GetX<Controller>(
builder: (controller) {
print("count 2 rebuild");
return Text('${controller.count2.value}');
},
),
GetX<Controller>(
builder: (controller) {
print("count 3 rebuild");
return Text('${controller.sum}');
},
),
```
If we increment `count1.value++` , it will print:
* `count 1 rebuild`
* `count 3 rebuild`
because `count1` has a value of `1` , and `1 + 0 = 1` , changing the `sum` getter value.
If we change `count2.value++` , it will print:
* `count 2 rebuild`
* `count 3 rebuild`
because `count2.value` changed, and the result of the `sum` is now `2` .
* NOTE: By default, the very first event will rebuild the widget, even if it is the same `value`.
This behavior exists due to Boolean variables.
Imagine you did this:
``` dart
var isLogged = false.obs;
```
And then, you checked if a user is "logged in" to trigger an event in `ever` .
``` dart
@override
onInit() async {
ever(isLogged, fireRoute);
isLogged.value = await Preferences.hasToken();
}
fireRoute(logged) {
if (logged) {
Get.off(Home());
} else {
Get.off(Login());
}
}
```
if `hasToken` was `false` , there would be no change to `isLogged` , so `ever()` would never be called.
To avoid this type of behavior, the first change to an _observable_ will always trigger an event,
even if it contains the same `.value` .
You can remove this behavior if you want, using:
`isLogged.firstRebuild = false;`
### Conditions to rebuild
In addition, Get provides refined state control. You can condition an event (such as adding an object to a list), on a certain condition.
``` dart
// First parameter: condition, must return true or false.
// Second parameter: the new value to apply if the condition is true.
list.addIf(item < limit, item);
```
Without decorations, without a code generator, without complications :smile:
Do you know Flutter's counter app? Your Controller class might look like this:
``` dart
class CountController extends GetxController {
final count = 0.obs;
}
```
With a simple:
``` dart
controller.count.value++
```
You could update the counter variable in your UI, regardless of where it is stored.
### Where .obs can be used
You can transform anything on obs. Here are two ways of doing it:
* You can convert your class values to obs
``` dart
class RxUser {
final name = "Camila".obs;
final age = 18.obs;
}
```
* or you can convert the entire class to be an observable
``` dart
class User {
User({String name, int age});
var name;
var age;
}
// when instantianting:
final user = User(name: "Camila", age: 18).obs;
```
### Note about Lists
Lists are completely observable as are the objects within it. That way, if you add a value to a list, it will automatically rebuild the widgets that use it.
You also don't need to use ".value" with lists, the amazing dart api allowed us to remove that.
Unfortunaly primitive types like String and int cannot be extended, making the use of .value mandatory, but that won't be a problem if you work with gets and setters for these.
``` dart
// On the controller
final String title = 'User Info:'.obs
final list = List<User>().obs;
// on the view
Text(controller.title.value), // String need to have .value in front of it
ListView.builder (
itemCount: controller.list.length // lists don't need it
)
```
When you are making your own classes observable, there is a different way to update them:
``` dart
// on the model file
// we are going to make the entire class observable instead of each attribute
class User() {
User({this.name = '', this.age = 0});
String name;
int age;
}
// on the controller file
final user = User().obs;
// when you need to update the user variable:
user.update( (user) { // this parameter is the class itself that you want to update
user.name = 'Jonny';
user.age = 18;
});
// an alternative way of update the user variable:
user(User(name: 'João', age: 35));
// on view:
Obx(()=> Text("Name ${user.value.name}: Age: ${user.value.age}"))
// you can also access the model values without the .value:
user().name; // notice that is the user variable, not the class (variable has lowercase u)
```
You don't have to work with sets if you don't want to. you can use the "assign 'and" assignAll "api.
The "assign" api will clear your list, and add a single object that you want to start there.
The "assignAll" api will clear the existing list and add any iterable objects that you inject into it.
### Why i have to use .value
We could remove the obligation to use 'value' to `String` and `int` with a simple decoration and code generator, but the purpose of this library is precisely avoid external dependencies. We want to offer an environment ready for programming, involving the essentials (management of routes, dependencies and states), in a simple, lightweight and performant way, without a need of an external package.
You can literally add 3 letters to your pubspec (get) and a colon and start programming. All solutions included by default, from route management to state management, aim at ease, productivity and performance.
The total weight of this library is less than that of a single state manager, even though it is a complete solution, and that is what you must understand.
If you are bothered by `.value` , and like a code generator, MobX is a great alternative, and you can use it in conjunction with Get. For those who want to add a single dependency in pubspec and start programming without worrying about the version of a package being incompatible with another, or if the error of a state update is coming from the state manager or dependency, or still, do not want to worrying about the availability of controllers, whether literally "just programming", get is just perfect.
If you have no problem with the MobX code generator, or have no problem with the BLoC boilerplate, you can simply use Get for routes, and forget that it has state manager. Get SEM and RSM were born out of necessity, my company had a project with more than 90 controllers, and the code generator simply took more than 30 minutes to complete its tasks after a Flutter Clean on a reasonably good machine, if your project it has 5, 10, 15 controllers, any state manager will supply you well. If you have an absurdly large project, and code generator is a problem for you, you have been awarded this solution.
Obviously, if someone wants to contribute to the project and create a code generator, or something similar, I will link in this readme as an alternative, my need is not the need for all devs, but for now I say, there are good solutions that already do that, like MobX.
### Obx()
Typing in Get using Bindings is unnecessary. you can use the Obx widget instead of GetX which only receives the anonymous function that creates a widget.
Obviously, if you don't use a type, you will need to have an instance of your controller to use the variables, or use `Get.find<Controller>()` .value or Controller.to.value to retrieve the value.
### Workers
Workers will assist you, triggering specific callbacks when an event occurs.
``` dart
/// Called every time `count1` changes.
ever(count1, (_) => print("$_ has been changed"));
/// Called only first time the variable $_ is changed
once(count1, (_) => print("$_ was changed once"));
/// Anti DDos - Called every time the user stops typing for 1 second, for example.
debounce(count1, (_) => print("debouce$_"), time: Duration(seconds: 1));
/// Ignore all changes within 1 second.
interval(count1, (_) => print("interval $_"), time: Duration(seconds: 1));
```
All workers (except `debounce` ) have a `condition` named parameter, which can be a `bool` or a callback that returns a `bool` .
This `condition` defines when the `callback` function executes.
All workers returns a `Worker` instance, that you can use to cancel ( via `dispose()` ) the worker.
* **`ever`**
is called every time the _Rx_ variable emits a new value.
* **`everAll`**
Much like `ever` , but it takes a `List` of _Rx_ values Called every time its variable is changed. That's it.
* **`once`**
'once' is called only the first time the variable has been changed.
* **`debounce`**
'debounce' is very useful in search functions, where you only want the API to be called when the user finishes typing. If the user types "Jonny", you will have 5 searches in the APIs, by the letter J, o, n, n, and y. With Get this does not happen, because you will have a "debounce" Worker that will only be triggered at the end of typing.
* **`interval`**
'interval' is different from the debouce. debouce if the user makes 1000 changes to a variable within 1 second, he will send only the last one after the stipulated timer (the default is 800 milliseconds). Interval will instead ignore all user actions for the stipulated period. If you send events for 1 minute, 1000 per second, debounce will only send you the last one, when the user stops strafing events. interval will deliver events every second, and if set to 3 seconds, it will deliver 20 events that minute. This is recommended to avoid abuse, in functions where the user can quickly click on something and get some advantage (imagine that the user can earn coins by clicking on something, if he clicked 300 times in the same minute, he would have 300 coins, using interval, you you can set a time frame for 3 seconds, and even then clicking 300 or a thousand times, the maximum he would get in 1 minute would be 20 coins, clicking 300 or 1 million times). The debounce is suitable for anti-DDos, for functions like search where each change to onChange would cause a query to your api. Debounce will wait for the user to stop typing the name, to make the request. If it were used in the coin scenario mentioned above, the user would only win 1 coin, because it is only executed, when the user "pauses" for the established time.
* NOTE: Workers should always be used when starting a Controller or Class, so it should always be on onInit (recommended), Class constructor, or the initState of a StatefulWidget (this practice is not recommended in most cases, but it shouldn't have any side effects).
## Simple State Manager
Get has a state manager that is extremely light and easy, which does not use ChangeNotifier, will meet the need especially for those new to Flutter, and will not cause problems for large applications.
GetBuilder is aimed precisely at multiple state control. Imagine that you added 30 products to a cart, you click delete one, at the same time that the list is updated, the price is updated and the badge in the shopping cart is updated to a smaller number. This type of approach makes GetBuilder killer, because it groups states and changes them all at once without any "computational logic" for that. GetBuilder was created with this type of situation in mind, since for ephemeral change of state, you can use setState and you would not need a state manager for this.
That way, if you want an individual controller, you can assign IDs for that, or use GetX. This is up to you, remembering that the more "individual" widgets you have, the more the performance of GetX will stand out, while the performance of GetBuilder should be superior, when there is multiple change of state.
### Advantages
1. Update only the required widgets.
2. Does not use changeNotifier, it is the state manager that uses less memory (close to 0mb).
3. Forget StatefulWidget! With Get you will never need it. With the other state managers, you will probably have to use a StatefulWidget to get the instance of your Provider, BLoC, MobX Controller, etc. But have you ever stopped to think that your appBar, your scaffold, and most of the widgets that are in your class are stateless? So why save the state of an entire class, if you can only save the state of the Widget that is stateful? Get solves that, too. Create a Stateless class, make everything stateless. If you need to update a single component, wrap it with GetBuilder, and its state will be maintained.
4. Organize your project for real! Controllers must not be in your UI, place your TextEditController, or any controller you use within your Controller class.
5. Do you need to trigger an event to update a widget as soon as it is rendered? GetBuilder has the property "initState", just like StatefulWidget, and you can call events from your controller, directly from it, no more events being placed in your initState.
6. Do you need to trigger an action like closing streams, timers and etc? GetBuilder also has the dispose property, where you can call events as soon as that widget is destroyed.
7. Use streams only if necessary. You can use your StreamControllers inside your controller normally, and use StreamBuilder also normally, but remember, a stream reasonably consumes memory, reactive programming is beautiful, but you shouldn't abuse it. 30 streams open simultaneously can be worse than changeNotifier (and changeNotifier is very bad).
8. Update widgets without spending ram for that. Get stores only the GetBuilder creator ID, and updates that GetBuilder when necessary. The memory consumption of the get ID storage in memory is very low even for thousands of GetBuilders. When you create a new GetBuilder, you are actually sharing the state of GetBuilder that has a creator ID. A new state is not created for each GetBuilder, which saves A LOT OF ram for large applications. Basically your application will be entirely Stateless, and the few Widgets that will be Stateful (within GetBuilder) will have a single state, and therefore updating one will update them all. The state is just one.
9. Get is omniscient and in most cases it knows exactly the time to take a controller out of memory. You should not worry about when to dispose of a controller, Get knows the best time to do this.
### Usage
``` dart
// Create controller class and extends GetxController
class Controller extends GetxController {
int counter = 0;
void increment() {
counter++;
update(); // use update() to update counter variable on UI when increment be called
}
}
// On your Stateless/Stateful class, use GetBuilder to update Text when increment be called
GetBuilder<Controller>(
init: Controller(), // INIT IT ONLY THE FIRST TIME
builder: (_) => Text(
'${_.counter}',
),
)
//Initialize your controller only the first time. The second time you are using ReBuilder for the same controller, do not use it again. Your controller will be automatically removed from memory as soon as the widget that marked it as 'init' is deployed. You don't have to worry about that, Get will do it automatically, just make sure you don't start the same controller twice.
```
**Done!**
* You have already learned how to manage states with Get.
* Note: You may want a larger organization, and not use the init property. For that, you can create a class and extends Bindings class, and within it mention the controllers that will be created within that route. Controllers will not be created at that time, on the contrary, this is just a statement, so that the first time you use a Controller, Get will know where to look. Get will remain lazyLoad, and will continue to dispose Controllers when they are no longer needed. See the pub.dev example to see how it works.
If you navigate many routes and need data that was in your previously used controller, you just need to use GetBuilder Again (with no init):
``` dart
class OtherClass extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: GetBuilder<Controller>(
builder: (s) => Text('${s.counter}'),
),
),
);
}
```
If you need to use your controller in many other places, and outside of GetBuilder, just create a get in your controller and have it easily. (or use `Get.find<Controller>()` )
``` dart
class Controller extends GetxController {
/// You do not need that. I recommend using it just for ease of syntax.
/// with static method: Controller.to.increment();
/// with no static method: Get.find<Controller>().increment();
/// There is no difference in performance, nor any side effect of using either syntax. Only one does not need the type, and the other the IDE will autocomplete it.
static Controller get to => Get.find(); // add this line
int counter = 0;
void increment() {
counter++;
update();
}
}
```
And then you can access your controller directly, that way:
``` dart
FloatingActionButton(
onPressed: () {
Controller.to.increment(),
} // This is incredibly simple!
child: Text("${Controller.to.counter}"),
),
```
When you press FloatingActionButton, all widgets that are listening to the 'counter' variable will be updated automatically.
### How it handles controllers
Let's say we have this:
`Class a => Class B (has controller X) => Class C (has controller X)`
In class A the controller is not yet in memory, because you have not used it yet (Get is lazyLoad). In class B you used the controller, and it entered memory. In class C you used the same controller as in class B, Get will share the state of controller B with controller C, and the same controller is still in memory. If you close screen C and screen B, Get will automatically take controller X out of memory and free up resources, because Class a is not using the controller. If you navigate to B again, controller X will enter memory again, if instead of going to class C, you return to class A again, Get will take the controller out of memory in the same way. If class C didn't use the controller, and you took class B out of memory, no class would be using controller X and likewise it would be disposed of. The only exception that can mess with Get, is if you remove B from the route unexpectedly, and try to use the controller in C. In this case, the creator ID of the controller that was in B was deleted, and Get was programmed to remove it from memory every controller that has no creator ID. If you intend to do this, add the "autoRemove: false" flag to class B's GetBuilder and use adoptID = true; in class C's GetBuilder.
### You won't need StatefulWidgets anymore
Using StatefulWidgets means storing the state of entire screens unnecessarily, even because if you need to minimally rebuild a widget, you will embed it in a Consumer/Observer/BlocProvider/GetBuilder/GetX/Obx, which will be another StatefulWidget.
The StatefulWidget class is a class larger than StatelessWidget, which will allocate more RAM, and this may not make a significant difference between one or two classes, but it will most certainly do when you have 100 of them!
Unless you need to use a mixin, like TickerProviderStateMixin, it will be totally unnecessary to use a StatefulWidget with Get.
You can call all methods of a StatefulWidget directly from a GetBuilder.
If you need to call initState() or dispose() method for example, you can call them directly;
``` dart
GetBuilder<Controller>(
initState: (_) => Controller.to.fetchApi(),
dispose: (_) => Controller.to.closeStreams(),
builder: (s) => Text('${s.username}'),
),
```
A much better approach than this is to use the onInit() and onClose() method directly from your controller.
``` dart
@override
void onInit() {
fetchApi();
super.onInit();
}
```
* NOTE: If you want to start a method at the moment the controller is called for the first time, you DON'T NEED to use constructors for this, in fact, using a performance-oriented package like Get, this borders on bad practice, because it deviates from the logic in which the controllers are created or allocated (if you create an instance of this controller, the constructor will be called immediately, you will be populating a controller before it is even used, you are allocating memory without it being in use, this definitely hurts the principles of this library). The onInit() methods; and onClose(); were created for this, they will be called when the Controller is created, or used for the first time, depending on whether you are using Get.lazyPut or not. If you want, for example, to make a call to your API to populate data, you can forget about the old-fashioned method of initState/dispose, just start your call to the api in onInit, and if you need to execute any command like closing streams, use the onClose() for that.
### Why it exists
The purpose of this package is precisely to give you a complete solution for navigation of routes, management of dependencies and states, using the least possible dependencies, with a high degree of decoupling. Get engages all high and low level Flutter APIs within itself, to ensure that you work with the least possible coupling. We centralize everything in a single package, to ensure that you don't have any kind of coupling in your project. That way, you can put only widgets in your view, and leave the part of your team that works with the business logic free, to work with the business logic without depending on any element of the View. This provides a much cleaner working environment, so that part of your team works only with widgets, without worrying about sending data to your controller, and part of your team works only with the business logic in its breadth, without depending on no element of the view.
So to simplify this:
You don't need to call methods in initState and send them by parameter to your controller, nor use your controller constructor for that, you have the onInit() method that is called at the right time for you to start your services.
You do not need to call the device, you have the onClose() method that will be called at the exact moment when your controller is no longer needed and will be removed from memory. That way, leave views for widgets only, refrain from any kind of business logic from it.
Do not call a dispose method inside GetxController, it will not do anything, remember that the controller is not a Widget, you should not "dispose" it, and it will be automatically and intelligently removed from memory by Get. If you used any stream on it and want to close it, just insert it into the close method. Example:
``` dart
class Controller extends GetxController {
StreamController<User> user = StreamController<User>();
StreamController<String> name = StreamController<String>();
/// close stream = onClose method, not dispose.
@override
void onClose() {
user.close();
name.close();
super.onClose();
}
}
```
Controller life cycle:
* onInit() where it is created.
* onClose() where it is closed to make any changes in preparation for the delete method
* deleted: you do not have access to this API because it is literally removing the controller from memory. It is literally deleted, without leaving any trace.
### Other ways of using it
You can use Controller instance directly on GetBuilder value:
``` dart
GetBuilder<Controller>(
init: Controller(),
builder: (value) => Text(
'${value.counter}', //here
),
),
```
You may also need an instance of your controller outside of your GetBuilder, and you can use these approaches to achieve this:
``` dart
class Controller extends GetxController {
static Controller get to => Get.find();
[...]
}
// on you view:
GetBuilder<Controller>(
init: Controller(), // use it only first time on each controller
builder: (_) => Text(
'${Controller.to.counter}', //here
)
),
```
or
``` dart
class Controller extends GetxController {
// static Controller get to => Get.find(); // with no static get
[...]
}
// on stateful/stateless class
GetBuilder<Controller>(
init: Controller(), // use it only first time on each controller
builder: (_) => Text(
'${Get.find<Controller>().counter}', //here
),
),
```
* You can use "non-canonical" approaches to do this. If you are using some other dependency manager, like get_it, modular, etc., and just want to deliver the controller instance, you can do this:
``` dart
Controller controller = Controller();
[...]
GetBuilder<Controller>(
init: controller, //here
builder: (_) => Text(
'${controller.counter}', // here
),
),
```
### Unique IDs
If you want to refine a widget's update control with GetBuilder, you can assign them unique IDs:
``` dart
GetBuilder<Controller>(
id: 'text'
init: Controller(), // use it only first time on each controller
builder: (_) => Text(
'${Get.find<Controller>().counter}', //here
),
),
```
And update it this form:
``` dart
update(['text']);
```
You can also impose conditions for the update:
``` dart
update(['text'], counter < 10);
```
GetX does this automatically and only reconstructs the widget that uses the exact variable that was changed, if you change a variable to the same as the previous one and that does not imply a change of state , GetX will not rebuild the widget to save memory and CPU cycles (3 is being displayed on the screen, and you change the variable to 3 again. In most state managers, this will cause a new rebuild, but with GetX the widget will only is rebuilt again, if in fact his state has changed).
## Mixing the two state managers
Some people opened a feature request, as they wanted to use only one type of reactive variable, and the other mechanics, and needed to insert an Obx into a GetBuilder for this. Thinking about it MixinBuilder was created. It allows both reactive changes by changing ".obs" variables, and mechanical updates via update(). However, of the 4 widgets he is the one that consumes the most resources, since in addition to having a Subscription to receive change events from his children, he subscribes to the update method of his controller.
Extending GetxController is important, as they have life cycles, and can "start" and "end" events in their onInit() and onClose() methods. You can use any class for this, but I strongly recommend you use the GetxController class to place your variables, whether they are observable or not.
## StateMixin
Another way to handle your `UI` state is use the `StateMixin<T>` .
To implement it, use the `with` to add the `StateMixin<T>`
to your controller which allows a T model.
``` dart
class Controller extends GetController with StateMixin<User>{}
```
The `change()` method change the State whenever we want.
Just pass the data and the status in this way:
```dart
change(data, status: RxStatus.success());
```
RxStatus allow these status:
``` dart
RxStatus.loading();
RxStatus.success();
RxStatus.empty();
RxStatus.error('message');
```
To represent it in the UI, use:
```dart
class OtherClass extends GetView<Controller> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: controller.obx(
(state)=>Text(state.name),
// here you can put your custom loading indicator, but
// by default would be Center(child:CircularProgressIndicator())
onLoading: CustomLoadingIndicator(),
onEmpty: Text('No data found'),
// here also you can set your own error widget, but by
// default will be an Center(child:Text(error))
onError: (error)=>Text(error),
),
);
}
```
## GetBuilder vs GetX vs Obx vs MixinBuilder
In a decade working with programming I was able to learn some valuable lessons.
My first contact with reactive programming was so "wow, this is incredible" and in fact reactive programming is incredible.
However, it is not suitable for all situations. Often all you need is to change the state of 2 or 3 widgets at the same time, or an ephemeral change of state, in which case reactive programming is not bad, but it is not appropriate.
Reactive programming has a higher RAM consumption that can be compensated for by the individual workflow, which will ensure that only one widget is rebuilt and when necessary, but creating a list with 80 objects, each with several streams is not a good one idea. Open the dart inspect and check how much a StreamBuilder consumes, and you'll understand what I'm trying to tell you.
With that in mind, I created the simple state manager. It is simple, and that is exactly what you should demand from it: updating state in blocks in a simple way, and in the most economical way.
GetBuilder is very economical in RAM, and there is hardly a more economical approach than him (at least I can't imagine one, if it exists, please let us know).
However, GetBuilder is still a mechanical state manager, you need to call update() just like you would need to call Provider's notifyListeners().
There are other situations where reactive programming is really interesting, and not working with it is the same as reinventing the wheel. With that in mind, GetX was created to provide everything that is most modern and advanced in a state manager. It updates only what is necessary and when necessary, if you have an error and send 300 state changes simultaneously, GetX will filter and update the screen only if the state actually changes.
GetX is still more economical than any other reactive state manager, but it consumes a little more RAM than GetBuilder. Thinking about it and aiming to maximize the consumption of resources that Obx was created. Unlike GetX and GetBuilder, you will not be able to initialize a controller inside an Obx, it is just a Widget with a StreamSubscription that receives change events from your children, that's all. It is more economical than GetX, but loses to GetBuilder, which was to be expected, since it is reactive, and GetBuilder has the most simplistic approach that exists, of storing a widget's hashcode and its StateSetter. With Obx you don't need to write your controller type, and you can hear the change from multiple different controllers, but it needs to be initialized before, either using the example approach at the beginning of this readme, or using the Bindings class.

View File

@@ -0,0 +1,410 @@
# Dependency Management
- [Dependency Management](#dependency-management)
- [Instancing methods](#instancing-methods)
- [Get.put()](#getput)
- [Get.lazyPut](#getlazyput)
- [Get.putAsync](#getputasync)
- [Get.create](#getcreate)
- [Using instantiated methods/classes](#using-instantiated-methodsclasses)
- [Specifying an alternate instance](#specifying-an-alternate-instance)
- [Differences between methods](#differences-between-methods)
- [Bindings](#bindings)
- [Bindings class](#bindings-class)
- [BindingsBuilder](#bindingsbuilder)
- [SmartManagement](#smartmanagement)
- [How to change](#how-to-change)
- [SmartManagement.full](#smartmanagementfull)
- [SmartManagement.onlyBuilder](#smartmanagementonlybuilder)
- [SmartManagement.keepFactory](#smartmanagementkeepfactory)
- [How bindings work under the hood](#how-bindings-work-under-the-hood)
- [Notes](#notes)
Get has a simple and powerful dependency manager that allows you to retrieve the same class as your Bloc or Controller with just 1 lines of code, no Provider context, no inheritedWidget:
```dart
Controller controller = Get.put(Controller()); // Rather Controller controller = Controller();
```
Instead of instantiating your class within the class you are using, you are instantiating it within the Get instance, which will make it available throughout your App.
So you can use your controller (or Bloc class) normally
- Note: If you are using Get's State Manager, pay more attention to the [Bindings](#bindings) api, which will make easier to connect your view to your controller.
- Note²: Get dependency management is decloupled from other parts of the package, so if for example your app is already using a state manager (any one, it doesn't matter), you don't need to change that, you can use this dependency injection manager with no problems at all
## Instancing methods
The methods and it's configurable parameters are:
### Get.put()
The most common way of inserting a dependency. Good for the controllers of your views for example.
```dart
Get.put<SomeClass>(SomeClass());
Get.put<LoginController>(LoginController(), permanent: true);
Get.put<ListItemController>(ListItemController, tag: "some unique string");
```
This is all options you can set when using put:
```dart
Get.put<S>(
// mandatory: the class that you want to get to save, like a controller or anything
// note: "S" means that it can be a class of any type
S dependency
// optional: this is for when you want multiple classess that are of the same type
// since you normally get a class by using Get.find<Controller>(),
// you need to use tag to tell which instance you need
// must be unique string
String tag,
// optional: by default, get will dispose instances after they are not used anymore (example,
// the controller of a view that is closed), but you might need that the instance
// to be kept there throughout the entire app, like an instance of sharedPreferences or something
// so you use this
// defaults to false
bool permanent = false,
// optional: allows you after using an abstract class in a test, replace it with another one and follow the test.
// defaults to false
bool overrideAbstract = false,
// optional: allows you to create the dependency using function instead of the dependency itself.
// this one is not commonly used
InstanceBuilderCallback<S> builder,
)
```
### Get.lazyPut
It is possible to lazyLoad a dependency so that it will be instantiated only when is used. Very useful for computational expensive classes or if you want to instantiate several classes in just one place (like in a Bindings class) and you know you will not gonna use that class at that time.
```dart
/// ApiMock will only be called when someone uses Get.find<ApiMock> for the first time
Get.lazyPut<ApiMock>(() => ApiMock());
Get.lazyPut<FirebaseAuth>(
() {
// ... some logic if needed
return FirebaseAuth();
},
tag: Math.random().toString(),
fenix: true
)
Get.lazyPut<Controller>( () => Controller() )
```
This is all options you can set when using lazyPut:
```dart
Get.lazyPut<S>(
// mandatory: a method that will be executed when your class is called for the first time
InstanceBuilderCallback builder,
// optional: same as Get.put(), it is used for when you want multiple different instance of a same class
// must be unique
String tag,
// optional: It is similar to "permanent", the difference is that the instance is discarded when
// is not being used, but when it's use is needed again, Get will recreate the instance
// just the same as "SmartManagement.keepFactory" in the bindings api
// defaults to false
bool fenix = false
)
```
### Get.putAsync
If you want to register an asynchronous instance, you can use `Get.putAsync`:
```dart
Get.putAsync<SharedPreferences>(() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setInt('counter', 12345);
return prefs;
});
Get.putAsync<YourAsyncClass>( () async => await YourAsyncClass() )
```
This is all options you can set when using putAsync:
```dart
Get.putAsync<S>(
// mandatory: an async method that will be executed to instantiate your class
AsyncInstanceBuilderCallback<S> builder,
// optional: same as Get.put(), it is used for when you want multiple different instance of a same class
// must be unique
String tag,
// optional: same as in Get.put(), used when you need to maintain that instance alive in the entire app
// defaults to false
bool permanent = false
)
```
### Get.create
This one is tricky. A detailed explanation of what this is and the differences between the other one can be found on [Differences between methods:](#differences-between-methods) section
```dart
Get.Create<SomeClass>(() => SomeClass());
Get.Create<LoginController>(() => LoginController());
```
This is all options you can set when using create:
```dart
Get.create<S>(
// required: a function that returns a class that will be "fabricated" every
// time `Get.find()` is called
// Example: Get.create<YourClass>(() => YourClass())
FcBuilderFunc<S> builder,
// optional: just like Get.put(), but it is used when you need multiple instances
// of a of a same class
// Useful in case you have a list that each item need it's own controller
// needs to be a unique string. Just change from tag to name
String name,
// optional: just like int`Get.put()`, it is for when you need to keep the
// instance alive thoughout the entire app. The difference is in Get.create
// permanent is true by default
bool permanent = true
```
## Using instantiated methods/classes
Imagine that you have navigated through numerous routes, and you need a data that was left behind in your controller, you would need a state manager combined with the Provider or Get_it, correct? Not with Get. You just need to ask Get to "find" for your controller, you don't need any additional dependencies:
```dart
final controller = Get.find<Controller>();
// OR
Controller controller = Get.find();
// Yes, it looks like Magic, Get will find your controller, and will deliver it to you.
// You can have 1 million controllers instantiated, Get will always give you the right controller.
```
And then you will be able to recover your controller data that was obtained back there:
```dart
Text(controller.textFromApi);
```
Since the returned value is a normal class, you can do anything you want:
```dart
int count = Get.find<SharedPreferences>().getInt('counter');
print(count); // out: 12345
```
To remove an instance of Get:
```dart
Get.delete<Controller>(); //usually you don't need to do this because GetX already delete unused controllers
```
## Specifying an alternate instance
A currently inserted instance can be replaced with a similar or extended class instance by using the `replace` or `lazyReplace` method. This can then be retrieved by using the original class.
```dart
abstract class BaseClass {}
class ParentClass extends BaseClass {}
class ChildClass extends ParentClass {
bool isChild = true;
}
Get.put<BaseClass>(ParentClass());
Get.replace<BaseClass>(ChildClass());
final instance = Get.find<BaseClass>();
print(instance is ChildClass); //true
class OtherClass extends BaseClass {}
Get.lazyReplace<BaseClass>(() => OtherClass());
final instance = Get.find<BaseClass>();
print(instance is ChildClass); // false
print(instance is OtherClass); //true
```
## Differences between methods
First, let's of the `fenix` of Get.lazyPut and the `permanent` of the other methods.
The fundamental difference between `permanent` and `fenix` is how you want to store your instances.
Reinforcing: by default, GetX deletes instances when they are not in use.
It means that: If screen 1 has controller 1 and screen 2 has controller 2 and you remove the first route from stack, (like if you use `Get.off()` or `Get.offNamed()`) the controller 1 lost its use so it will be erased.
But if you want to opt for using `permanent:true`, then the controller will not be lost in this transition - which is very useful for services that you want to keep alive throughout the entire application.
`fenix` in the other hand is for services that you don't worry in losing between screen changes, but when you need that service, you expect that it is alive. So basically, it will dispose the unused controller/service/class, but when you need it, it will "recreate from the ashes" a new instance.
Proceeding with the differences between methods:
- Get.put and Get.putAsync follows the same creation order, with the difference that the second uses an asynchronous method: those two methods creates and initializes the instance. That one is inserted directly in the memory, using the internal method `insert` with the parameters `permanent: false` and `isSingleton: true` (this isSingleton parameter only purpose is to tell if it is to use the dependency on `dependency` or if it is to use the dependency on `FcBuilderFunc`). After that, `Get.find()` is called that immediately initialize the instances that are on memory.
- Get.create: As the name implies, it will "create" your dependency! Similar to `Get.put()`, it also calls the internal method `insert` to instancing. But `permanent` became true and `isSingleton` became false (since we are "creating" our dependency, there is no way for it to be a singleton instace, that's why is false). And because it has `permanent: true`, we have by default the benefit of not losing it between screens! Also, `Get.find()` is not called immediately, it wait to be used in the screen to be called. It is created this way to make use of the parameter `permanent`, since then, worth noticing, `Get.create()` was made with the goal of create not shared instances, but don't get disposed, like for example a button in a listView, that you want a unique instance for that list - because of that, Get.create must be used together with GetWidget.
- Get.lazyPut: As the name implies, it is a lazy proccess. The instance is create, but it is not called to be used immediately, it remains waiting to be called. Contrary to the other methods, `insert` is not called here. Instead, the instance is inserted in another part of the memory, a part responsible to tell if the instance can be recreated or not, let's call it "factory". If we want to create something to be used later, it will not be mix with things been used right now. And here is where `fenix` magic enters: if you opt to leaving `fenix: false`, and your `smartManagement` are not `keepFactory`, then when using `Get.find` the instance will change the place in the memory from the "factory" to common instance memory area. Right after that, by default it is removed from the "factory". Now, if you opt for `fenix: true`, the instance continues to exist in this dedicated part, even going to the common area, to be called again in the future.
## Bindings
One of the great differentials of this package, perhaps, is the possibility of full integration of the routes, state manager and dependency manager.
When a route is removed from the Stack, all controllers, variables, and instances of objects related to it are removed from memory. If you are using streams or timers, they will be closed automatically, and you don't have to worry about any of that.
In version 2.10 Get completely implemented the Bindings API.
Now you no longer need to use the init method. You don't even have to type your controllers if you don't want to. You can start your controllers and services in the appropriate place for that.
The Binding class is a class that will decouple dependency injection, while "binding" routes to the state manager and dependency manager.
This allows Get to know which screen is being displayed when a particular controller is used and to know where and how to dispose of it.
In addition, the Binding class will allow you to have SmartManager configuration control. You can configure the dependencies to be arranged when removing a route from the stack, or when the widget that used it is laid out, or neither. You will have intelligent dependency management working for you, but even so, you can configure it as you wish.
### Bindings class
- Create a class and implements Binding
```dart
class HomeBinding implements Bindings {}
```
Your IDE will automatically ask you to override the "dependencies" method, and you just need to click on the lamp, override the method, and insert all the classes you are going to use on that route:
```dart
class HomeBinding implements Bindings {
@override
void dependencies() {
Get.lazyPut<HomeController>(() => HomeController());
Get.put<Service>(()=> Api());
}
}
class DetailsBinding implements Bindings {
@override
void dependencies() {
Get.lazyPut<DetailsController>(() => DetailsController());
}
}
```
Now you just need to inform your route, that you will use that binding to make the connection between route manager, dependencies and states.
- Using named routes:
```dart
getPages: [
GetPage(
name: '/',
page: () => HomeView(),
binding: HomeBinding(),
),
GetPage(
name: '/details',
page: () => DetailsView(),
binding: DetailsBinding(),
),
];
```
- Using normal routes:
```dart
Get.to(Home(), binding: HomeBinding());
Get.to(DetailsView(), binding: DetailsBinding())
```
There, you don't have to worry about memory management of your application anymore, Get will do it for you.
The Binding class is called when a route is called, you can create an "initialBinding in your GetMaterialApp to insert all the dependencies that will be created.
```dart
GetMaterialApp(
initialBinding: SampleBind(),
home: Home(),
);
```
### BindingsBuilder
The default way of creating a binding is by creating a class that implements Bindings.
But alternatively, you can use `BindingsBuilder` callback so that you can simply use a function to instantiate whatever you desire.
Example:
```dart
getPages: [
GetPage(
name: '/',
page: () => HomeView(),
binding: BindingsBuilder(() {
Get.lazyPut<ControllerX>(() => ControllerX());
Get.put<Service>(()=> Api());
}),
),
GetPage(
name: '/details',
page: () => DetailsView(),
binding: BindingsBuilder(() {
Get.lazyPut<DetailsController>(() => DetailsController());
}),
),
];
```
That way you can avoid to create one Binding class for each route making this even simpler.
Both ways of doing work perfectly fine and we want you to use what most suit your tastes.
### SmartManagement
GetX by default disposes unused controllers from memory, even if a failure occurs and a widget that uses it is not properly disposed.
This is what is called the `full` mode of dependency management.
But if you want to change the way GetX controls the disposal of classes, you have `SmartManagement` class that you can set different behaviors.
#### How to change
If you want to change this config (which you usually don't need) this is the way:
```dart
void main () {
runApp(
GetMaterialApp(
smartManagement: SmartManagement.onlyBuilder //here
home: Home(),
)
)
}
```
#### SmartManagement.full
It is the default one. Dispose classes that are not being used and were not set to be permanent. In the majority of the cases you will want to keep this config untouched. If you new to GetX then don't change this.
#### SmartManagement.onlyBuilder
With this option, only controllers started in `init:` or loaded into a Binding with `Get.lazyPut()` will be disposed.
If you use `Get.put()` or `Get.putAsync()` or any other approach, SmartManagement will not have permissions to exclude this dependency.
With the default behavior, even widgets instantiated with "Get.put" will be removed, unlike SmartManagement.onlyBuilder.
#### SmartManagement.keepFactory
Just like SmartManagement.full, it will remove it's dependencies when it's not being used anymore. However, it will keep their factory, which means it will recreate the dependency if you need that instance again.
### How bindings work under the hood
Bindings creates transitory factories, which are created the moment you click to go to another screen, and will be destroyed as soon as the screen-changing animation happens.
This happens so fast that the analyzer will not even be able to register it.
When you navigate to this screen again, a new temporary factory will be called, so this is preferable to using SmartManagement.keepFactory, but if you don't want to create Bindings, or want to keep all your dependencies on the same Binding, it will certainly help you.
Factories take up little memory, they don't hold instances, but a function with the "shape" of that class you want.
This has a very low cost in memory, but since the purpose of this lib is to get the maximum performance possible using the minimum resources, Get removes even the factories by default.
Use whichever is most convenient for you.
## Notes
- DO NOT USE SmartManagement.keepFactory if you are using multiple Bindings. It was designed to be used without Bindings, or with a single Binding linked in the GetMaterialApp's initialBinding.
- Using Bindings is completely optional, if you want you can use `Get.put()` and `Get.find()` on classes that use a given controller without any problem.
However, if you work with Services or any other abstraction, I recommend using Bindings for a better organization.

View File

@@ -0,0 +1,569 @@
- [Route Management](#route-management)
- [How to use](#how-to-use)
- [Navigation without named routes](#navigation-without-named-routes)
- [Navigation with named routes](#navigation-with-named-routes)
- [Send data to named Routes](#send-data-to-named-routes)
- [Dynamic urls links](#dynamic-urls-links)
- [Middleware](#middleware)
- [Navigation without context](#navigation-without-context)
- [SnackBars](#snackbars)
- [Dialogs](#dialogs)
- [BottomSheets](#bottomsheets)
- [Nested Navigation](#nested-navigation)
# Route Management
This is the complete explanation of all there is to Getx when the matter is route management.
## How to use
Add this to your pubspec.yaml file:
```yaml
dependencies:
get:
```
If you are going to use routes/snackbars/dialogs/bottomsheets without context, or use the high-level Get APIs, you need to simply add "Get" before your MaterialApp, turning it into GetMaterialApp and enjoy!
```dart
GetMaterialApp( // Before: MaterialApp(
home: MyHome(),
)
```
## Navigation without named routes
To navigate to a new screen:
```dart
Get.to(NextScreen());
```
To close snackbars, dialogs, bottomsheets, or anything you would normally close with Navigator.pop(context);
```dart
Get.back();
```
To go to the next screen and no option to go back to the previous screen (for use in SplashScreens, login screens and etc.)
```dart
Get.off(NextScreen());
```
To go to the next screen and cancel all previous routes (useful in shopping carts, polls, and tests)
```dart
Get.offAll(NextScreen());
```
To navigate to the next route, and receive or update data as soon as you return from it:
```dart
var data = await Get.to(Payment());
```
on other screen, send a data for previous route:
```dart
Get.back(result: 'success');
```
And use it:
ex:
```dart
if(data == 'success') madeAnything();
```
Don't you want to learn our syntax?
Just change the Navigator (uppercase) to navigator (lowercase), and you will have all the functions of the standard navigation, without having to use context
Example:
```dart
// Default Flutter navigator
Navigator.of(context).push(
context,
MaterialPageRoute(
builder: (BuildContext context) {
return HomePage();
},
),
);
// Get using Flutter syntax without needing context
navigator.push(
MaterialPageRoute(
builder: (_) {
return HomePage();
},
),
);
// Get syntax (It is much better, but you have the right to disagree)
Get.to(HomePage());
```
## Navigation with named routes
- If you prefer to navigate by namedRoutes, Get also supports this.
To navigate to nextScreen
```dart
Get.toNamed("/NextScreen");
```
To navigate and remove the previous screen from the tree.
```dart
Get.offNamed("/NextScreen");
```
To navigate and remove all previous screens from the tree.
```dart
Get.offAllNamed("/NextScreen");
```
To define routes, use GetMaterialApp:
```dart
void main() {
runApp(
GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => MyHomePage()),
GetPage(name: '/second', page: () => Second()),
GetPage(
name: '/third',
page: () => Third(),
transition: Transition.zoom
),
],
)
);
}
```
To handle navigation to non-defined routes (404 error), you can define an unknownRoute page in GetMaterialApp.
```dart
void main() {
runApp(
GetMaterialApp(
unknownRoute: GetPage(name: '/notfound', page: () => UnknownRoutePage()),
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => MyHomePage()),
GetPage(name: '/second', page: () => Second()),
],
)
);
}
```
### Send data to named Routes
Just send what you want for arguments. Get accepts anything here, whether it is a String, a Map, a List, or even a class instance.
```dart
Get.toNamed("/NextScreen", arguments: 'Get is the best');
```
on your class or controller:
```dart
print(Get.arguments);
//print out: Get is the best
```
### Dynamic urls links
Get offer advanced dynamic urls just like on the Web. Web developers have probably already wanted this feature on Flutter, and most likely have seen a package promise this feature and deliver a totally different syntax than a URL would have on web, but Get also solves that.
```dart
Get.offAllNamed("/NextScreen?device=phone&id=354&name=Enzo");
```
on your controller/bloc/stateful/stateless class:
```dart
print(Get.parameters['id']);
// out: 354
print(Get.parameters['name']);
// out: Enzo
```
You can also receive NamedParameters with Get easily:
```dart
void main() {
runApp(
GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(
name: '/',
page: () => MyHomePage(),
),
GetPage(
name: '/profile/',
page: () => MyProfile(),
),
//You can define a different page for routes with arguments, and another without arguments, but for that you must use the slash '/' on the route that will not receive arguments as above.
GetPage(
name: '/profile/:user',
page: () => UserProfile(),
),
GetPage(
name: '/third',
page: () => Third(),
transition: Transition.cupertino
),
],
)
);
}
```
Send data on route name
```dart
Get.toNamed("/profile/34954");
```
On second screen take the data by parameter
```dart
print(Get.parameters['user']);
// out: 34954
```
or send multiple parameters like this
```dart
Get.toNamed("/profile/34954?flag=true&country=italy");
```
or
```dart
var parameters = <String, String>{"flag": "true","country": "italy",};
Get.toNamed("/profile/34954", parameters: parameters);
```
On second screen take the data by parameters as usually
```dart
print(Get.parameters['user']);
print(Get.parameters['flag']);
print(Get.parameters['country']);
// out: 34954 true italy
```
And now, all you need to do is use Get.toNamed() to navigate your named routes, without any context (you can call your routes directly from your BLoC or Controller class), and when your app is compiled to the web, your routes will appear in the url <3
### Middleware
If you want to listen Get events to trigger actions, you can to use routingCallback to it
```dart
GetMaterialApp(
routingCallback: (routing) {
if(routing.current == '/second'){
openAds();
}
}
)
```
If you are not using GetMaterialApp, you can use the manual API to attach Middleware observer.
```dart
void main() {
runApp(
MaterialApp(
onGenerateRoute: Router.generateRoute,
initialRoute: "/",
navigatorKey: Get.key,
navigatorObservers: [
GetObserver(MiddleWare.observer), // HERE !!!
],
),
);
}
```
Create a MiddleWare class
```dart
class MiddleWare {
static observer(Routing routing) {
/// You can listen in addition to the routes, the snackbars, dialogs and bottomsheets on each screen.
///If you need to enter any of these 3 events directly here,
///you must specify that the event is != Than you are trying to do.
if (routing.current == '/second' && !routing.isSnackbar) {
Get.snackbar("Hi", "You are on second route");
} else if (routing.current =='/third'){
print('last route called');
}
}
}
```
Now, use Get on your code:
```dart
class First extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.add),
onPressed: () {
Get.snackbar("hi", "i am a modern snackbar");
},
),
title: Text('First Route'),
),
body: Center(
child: ElevatedButton(
child: Text('Open route'),
onPressed: () {
Get.toNamed("/second");
},
),
),
);
}
}
class Second extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.add),
onPressed: () {
Get.snackbar("hi", "i am a modern snackbar");
},
),
title: Text('second Route'),
),
body: Center(
child: ElevatedButton(
child: Text('Open route'),
onPressed: () {
Get.toNamed("/third");
},
),
),
);
}
}
class Third extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Third Route"),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Get.back();
},
child: Text('Go back!'),
),
),
);
}
}
```
## Navigation without context
### SnackBars
To have a simple SnackBar with Flutter, you must get the context of Scaffold, or you must use a GlobalKey attached to your Scaffold
```dart
final snackBar = SnackBar(
content: Text('Hi!'),
action: SnackBarAction(
label: 'I am a old and ugly snackbar :(',
onPressed: (){}
),
);
// Find the Scaffold in the widget tree and use
// it to show a SnackBar.
Scaffold.of(context).showSnackBar(snackBar);
```
With Get:
```dart
Get.snackbar('Hi', 'i am a modern snackbar');
```
With Get, all you have to do is call your Get.snackbar from anywhere in your code or customize it however you want!
```dart
Get.snackbar(
"Hey i'm a Get SnackBar!", // title
"It's unbelievable! I'm using SnackBar without context, without boilerplate, without Scaffold, it is something truly amazing!", // message
icon: Icon(Icons.alarm),
shouldIconPulse: true,
onTap:(){},
barBlur: 20,
isDismissible: true,
duration: Duration(seconds: 3),
);
////////// ALL FEATURES //////////
// Color colorText,
// Duration duration,
// SnackPosition snackPosition,
// Widget titleText,
// Widget messageText,
// bool instantInit,
// Widget icon,
// bool shouldIconPulse,
// double maxWidth,
// EdgeInsets margin,
// EdgeInsets padding,
// double borderRadius,
// Color borderColor,
// double borderWidth,
// Color backgroundColor,
// Color leftBarIndicatorColor,
// List<BoxShadow> boxShadows,
// Gradient backgroundGradient,
// TextButton mainButton,
// OnTap onTap,
// bool isDismissible,
// bool showProgressIndicator,
// AnimationController progressIndicatorController,
// Color progressIndicatorBackgroundColor,
// Animation<Color> progressIndicatorValueColor,
// SnackStyle snackStyle,
// Curve forwardAnimationCurve,
// Curve reverseAnimationCurve,
// Duration animationDuration,
// double barBlur,
// double overlayBlur,
// Color overlayColor,
// Form userInputForm
///////////////////////////////////
```
If you prefer the traditional snackbar, or want to customize it from scratch, including adding just one line (Get.snackbar makes use of a mandatory title and message), you can use
`Get.rawSnackbar();` which provides the RAW API on which Get.snackbar was built.
### Dialogs
To open dialog:
```dart
Get.dialog(YourDialogWidget());
```
To open default dialog:
```dart
Get.defaultDialog(
onConfirm: () => print("Ok"),
middleText: "Dialog made in 3 lines of code"
);
```
You can also use Get.generalDialog instead of showGeneralDialog.
For all other Flutter dialog widgets, including cupertinos, you can use Get.overlayContext instead of context, and open it anywhere in your code.
For widgets that don't use Overlay, you can use Get.context.
These two contexts will work in 99% of cases to replace the context of your UI, except for cases where inheritedWidget is used without a navigation context.
### BottomSheets
Get.bottomSheet is like showModalBottomSheet, but don't need of context.
```dart
Get.bottomSheet(
Container(
child: Wrap(
children: <Widget>[
ListTile(
leading: Icon(Icons.music_note),
title: Text('Music'),
onTap: () {}
),
ListTile(
leading: Icon(Icons.videocam),
title: Text('Video'),
onTap: () {},
),
],
),
)
);
```
## Nested Navigation
Get made Flutter's nested navigation even easier.
You don't need the context, and you will find your navigation stack by Id.
- NOTE: Creating parallel navigation stacks can be dangerous. The ideal is not to use NestedNavigators, or to use sparingly. If your project requires it, go ahead, but keep in mind that keeping multiple navigation stacks in memory may not be a good idea for RAM consumption.
See how simple it is:
```dart
Navigator(
key: Get.nestedKey(1), // create a key by index
initialRoute: '/',
onGenerateRoute: (settings) {
if (settings.name == '/') {
return GetPageRoute(
page: () => Scaffold(
appBar: AppBar(
title: Text("Main"),
),
body: Center(
child: TextButton(
color: Colors.blue,
onPressed: () {
Get.toNamed('/second', id:1); // navigate by your nested route by index
},
child: Text("Go to second"),
),
),
),
);
} else if (settings.name == '/second') {
return GetPageRoute(
page: () => Center(
child: Scaffold(
appBar: AppBar(
title: Text("Main"),
),
body: Center(
child: Text("second")
),
),
),
);
}
}
),
```

View File

@@ -0,0 +1,789 @@
* [State Management](#state-management)
+ [Reactive State Manager](#reactive-state-manager)
- [Advantages](#advantages)
- [Maximum performance:](#maximum-performance)
- [Declaring a reactive variable](#declaring-a-reactive-variable)
- [Having a reactive state, is easy.](#having-a-reactive-state-is-easy)
- [Using the values in the view](#using-the-values-in-the-view)
- [Conditions to rebuild](#conditions-to-rebuild)
- [Where .obs can be used](#where-obs-can-be-used)
- [Note about Lists](#note-about-lists)
- [Why i have to use .value](#why-i-have-to-use-value)
- [Obx()](#obx)
- [Workers](#workers)
+ [Simple State Manager](#simple-state-manager)
- [Advantages](#advantages-1)
- [Usage](#usage)
- [How it handles controllers](#how-it-handles-controllers)
- [You won't need StatefulWidgets anymore](#you-wont-need-statefulwidgets-anymore)
- [Why it exists](#why-it-exists)
- [Other ways of using it](#other-ways-of-using-it)
- [Unique IDs](#unique-ids)
+ [Mixing the two state managers](#mixing-the-two-state-managers)
+ [GetBuilder vs GetX vs Obx vs MixinBuilder](#getbuilder-vs-getx-vs-obx-vs-mixinbuilder)
# State Management
GetX does not use Streams or ChangeNotifier like other state managers. Why? In addition to building applications for android, iOS, web, linux, macos and linux, with GetX you can build server applications with the same syntax as Flutter/GetX. In order to improve response time and reduce RAM consumption, we created GetValue and GetStream, which are low latency solutions that deliver a lot of performance, at a low operating cost. We use this base to build all of our resources, including state management.
* _Complexity_: Some state managers are complex and have a lot of boilerplate. With GetX you don't have to define a class for each event, the code is highly clean and clear, and you do a lot more by writing less. Many people have given up on Flutter because of this topic, and they now finally have a stupidly simple solution for managing states.
* _No code generators_: You spend half your development time writing your application logic. Some state managers rely on code generators to have minimally readable code. Changing a variable and having to run build_runner can be unproductive, and often the waiting time after a flutter clean will be long, and you will have to drink a lot of coffee.
With GetX everything is reactive, and nothing depends on code generators, increasing your productivity in all aspects of your development.
* _It does not depend on context_: You probably already needed to send the context of your view to a controller, making the View's coupling with your business logic high. You have probably had to use a dependency for a place that has no context, and had to pass the context through various classes and functions. This just doesn't exist with GetX. You have access to your controllers from within your controllers without any context. You don't need to send the context by parameter for literally nothing.
* _Granular control_: most state managers are based on ChangeNotifier. ChangeNotifier will notify all widgets that depend on it when notifyListeners is called. If you have 40 widgets on one screen, which have a variable of your ChangeNotifier class, when you update one, all of them will be rebuilt.
With GetX, even nested widgets are respected. If you have Obx watching your ListView, and another watching a checkbox inside the ListView, when changing the CheckBox value, only it will be updated, when changing the List value, only the ListView will be updated.
* _It only reconstructs if its variable REALLY changes_: GetX has flow control, that means if you display a Text with 'Paola', if you change the observable variable to 'Paola' again, the widget will not be reconstructed. That's because GetX knows that 'Paola' is already being displayed in Text, and will not do unnecessary reconstructions.
Most (if not all) current state managers will rebuild on the screen.
## Reactive State Manager
Reactive programming can alienate many people because it is said to be complicated. GetX turns reactive programming into something quite simple:
* You won't need to create StreamControllers.
* You won't need to create a StreamBuilder for each variable
* You will not need to create a class for each state.
* You will not need to create a get for an initial value.
Reactive programming with Get is as easy as using setState.
Let's imagine that you have a name variable and want that every time you change it, all widgets that use it are automatically changed.
This is your count variable:
``` dart
var name = 'Jonatas Borges';
```
To make it observable, you just need to add ".obs" to the end of it:
``` dart
var name = 'Jonatas Borges'.obs;
```
That's all. It's *that* simple.
From now on, we might refer to this reactive-".obs"(ervables) variables as _Rx_.
What did we do under the hood? We created a `Stream` of `String` s, assigned the initial value `"Jonatas Borges"` , we notified all widgets that use `"Jonatas Borges"` that they now "belong" to this variable, and when the _Rx_ value changes, they will have to change as well.
This is the **magic of GetX**, thanks to Dart's capabilities.
But, as we know, a `Widget` can only be changed if it is inside a function, because static classes do not have the power to "auto-change".
You will need to create a `StreamBuilder` , subscribe to this variable to listen for changes, and create a "cascade" of nested `StreamBuilder` if you want to change several variables in the same scope, right?
No, you don't need a `StreamBuilder` , but you are right about static classes.
Well, in the view, we usually have a lot of boilerplate when we want to change a specific Widget, that's the Flutter way.
With **GetX** you can also forget about this boilerplate code.
`StreamBuilder( … )` ? `initialValue: …` ? `builder: …` ? Nope, you just need to place this variable inside an `Obx()` Widget.
``` dart
Obx (() => Text (controller.name));
```
_What do you need to memorize?_ Only `Obx(() =>` .
You are just passing that Widget through an arrow-function into an `Obx()` (the "Observer" of the _Rx_).
`Obx` is pretty smart, and will only change if the value of `controller.name` changes.
If `name` is `"John"` , and you change it to `"John"` ( `name.value = "John"` ), as it's the same `value` as before, nothing will change on the screen, and `Obx` , to save resources, will simply ignore the new value and not rebuild the Widget. **Isn't that amazing?**
> So, what if I have 5 _Rx_ (observable) variables within an `Obx` ?
It will just update when **any** of them changes.
> And if I have 30 variables in a class, when I update one, will it update **all** the variables that are in that class?
Nope, just the **specific Widget** that uses that _Rx_ variable.
So, **GetX** only updates the screen, when the _Rx_ variable changes it's value.
```
final isOpen = false.obs;
// NOTHING will happen... same value.
void onButtonTap() => isOpen.value=false;
```
### Advantages
**GetX()** helps you when you need **granular** control over what's being updated.
If you do not need `unique IDs` , because all your variables will be modified when you perform an action, then use `GetBuilder` ,
because it's a Simple State Updater (in blocks, like `setState()` ), made in just a few lines of code.
It was made simple, to have the least CPU impact, and just to fulfill a single purpose (a _State_ rebuild) and spend the minimum resources possible.
If you need a **powerful** State Manager, you can't go wrong with **GetX**.
It doesn't work with variables, but __flows__, everything in it are `Streams` under the hood.
You can use _rxDart_ in conjunction with it, because everything are `Streams`,
you can listen to the `event` of each "_Rx_ variable",
because everything in it are `Streams`.
It is literally a _BLoC_ approach, easier than _MobX_, and without code generators or decorations.
You can turn **anything** into an _"Observable"_ with just a `.obs` .
### Maximum performance:
In addition to having a smart algorithm for minimal rebuilds, **GetX** uses comparators
to make sure the State has changed.
If you experience any errors in your app, and send a duplicate change of State,
**GetX** will ensure it will not crash.
With **GetX** the State only changes if the `value` change.
That's the main difference between **GetX**, and using _ `computed` from MobX_.
When joining two __observables__, and one changes; the listener of that _observable_ will change as well.
With **GetX**, if you join two variables, `GetX()` (similar to `Observer()` ) will only rebuild if it implies a real change of State.
### Declaring a reactive variable
You have 3 ways to turn a variable into an "observable".
1 - The first is using **`Rx{Type}`**.
``` dart
// initial value is recommended, but not mandatory
final name = RxString('');
final isLogged = RxBool(false);
final count = RxInt(0);
final balance = RxDouble(0.0);
final items = RxList<String>([]);
final myMap = RxMap<String, int>({});
```
2 - The second is to use **`Rx`** and use Darts Generics, `Rx<Type>`
``` dart
final name = Rx<String>('');
final isLogged = Rx<Bool>(false);
final count = Rx<Int>(0);
final balance = Rx<Double>(0.0);
final number = Rx<Num>(0);
final items = Rx<List<String>>([]);
final myMap = Rx<Map<String, int>>({});
// Custom classes - it can be any class, literally
final user = Rx<User>();
```
3 - The third, more practical, easier and preferred approach, just add **`.obs`** as a property of your `value` :
``` dart
final name = ''.obs;
final isLogged = false.obs;
final count = 0.obs;
final balance = 0.0.obs;
final number = 0.obs;
final items = <String>[].obs;
final myMap = <String, int>{}.obs;
// Custom classes - it can be any class, literally
final user = User().obs;
```
##### Having a reactive state, is easy.
As we know, _Dart_ is now heading towards _null safety_.
To be prepared, from now on, you should always start your _Rx_ variables with an **initial value**.
> Transforming a variable into an _observable_ + _initial value_ with **GetX** is the simplest, and most practical approach.
You will literally add a " `.obs` " to the end of your variable, and **thats it**, youve made it observable,
and its `.value` , well, will be the _initial value_).
### Using the values in the view
``` dart
// controller file
final count1 = 0.obs;
final count2 = 0.obs;
int get sum => count1.value + count2.value;
```
``` dart
// view file
GetX<Controller>(
builder: (controller) {
print("count 1 rebuild");
return Text('${controller.count1.value}');
},
),
GetX<Controller>(
builder: (controller) {
print("count 2 rebuild");
return Text('${controller.count2.value}');
},
),
GetX<Controller>(
builder: (controller) {
print("count 3 rebuild");
return Text('${controller.sum}');
},
),
```
If we increment `count1.value++` , it will print:
* `count 1 rebuild`
* `count 3 rebuild`
because `count1` has a value of `1` , and `1 + 0 = 1` , changing the `sum` getter value.
If we change `count2.value++` , it will print:
* `count 2 rebuild`
* `count 3 rebuild`
because `count2.value` changed, and the result of the `sum` is now `2` .
* NOTE: By default, the very first event will rebuild the widget, even if it is the same `value`.
This behavior exists due to Boolean variables.
Imagine you did this:
``` dart
var isLogged = false.obs;
```
And then, you checked if a user is "logged in" to trigger an event in `ever` .
``` dart
@override
onInit() async {
ever(isLogged, fireRoute);
isLogged.value = await Preferences.hasToken();
}
fireRoute(logged) {
if (logged) {
Get.off(Home());
} else {
Get.off(Login());
}
}
```
if `hasToken` was `false` , there would be no change to `isLogged` , so `ever()` would never be called.
To avoid this type of behavior, the first change to an _observable_ will always trigger an event,
even if it contains the same `.value` .
You can remove this behavior if you want, using:
`isLogged.firstRebuild = false;`
### Conditions to rebuild
In addition, Get provides refined state control. You can condition an event (such as adding an object to a list), on a certain condition.
``` dart
// First parameter: condition, must return true or false.
// Second parameter: the new value to apply if the condition is true.
list.addIf(item < limit, item);
```
Without decorations, without a code generator, without complications :smile:
Do you know Flutter's counter app? Your Controller class might look like this:
``` dart
class CountController extends GetxController {
final count = 0.obs;
}
```
With a simple:
``` dart
controller.count.value++
```
You could update the counter variable in your UI, regardless of where it is stored.
### Where .obs can be used
You can transform anything on obs. Here are two ways of doing it:
* You can convert your class values to obs
``` dart
class RxUser {
final name = "Camila".obs;
final age = 18.obs;
}
```
* or you can convert the entire class to be an observable
``` dart
class User {
User({String name, int age});
var name;
var age;
}
// when instantianting:
final user = User(name: "Camila", age: 18).obs;
```
### Note about Lists
Lists are completely observable as are the objects within it. That way, if you add a value to a list, it will automatically rebuild the widgets that use it.
You also don't need to use ".value" with lists, the amazing dart api allowed us to remove that.
Unfortunaly primitive types like String and int cannot be extended, making the use of .value mandatory, but that won't be a problem if you work with gets and setters for these.
``` dart
// On the controller
final String title = 'User Info:'.obs
final list = List<User>().obs;
// on the view
Text(controller.title.value), // String need to have .value in front of it
ListView.builder (
itemCount: controller.list.length // lists don't need it
)
```
When you are making your own classes observable, there is a different way to update them:
``` dart
// on the model file
// we are going to make the entire class observable instead of each attribute
class User() {
User({this.name = '', this.age = 0});
String name;
int age;
}
// on the controller file
final user = User().obs;
// when you need to update the user variable:
user.update( (user) { // this parameter is the class itself that you want to update
user.name = 'Jonny';
user.age = 18;
});
// an alternative way of update the user variable:
user(User(name: 'João', age: 35));
// on view:
Obx(()=> Text("Name ${user.value.name}: Age: ${user.value.age}"))
// you can also access the model values without the .value:
user().name; // notice that is the user variable, not the class (variable has lowercase u)
```
You don't have to work with sets if you don't want to. you can use the "assign 'and" assignAll "api.
The "assign" api will clear your list, and add a single object that you want to start there.
The "assignAll" api will clear the existing list and add any iterable objects that you inject into it.
### Why i have to use .value
We could remove the obligation to use 'value' to `String` and `int` with a simple decoration and code generator, but the purpose of this library is precisely avoid external dependencies. We want to offer an environment ready for programming, involving the essentials (management of routes, dependencies and states), in a simple, lightweight and performant way, without a need of an external package.
You can literally add 3 letters to your pubspec (get) and a colon and start programming. All solutions included by default, from route management to state management, aim at ease, productivity and performance.
The total weight of this library is less than that of a single state manager, even though it is a complete solution, and that is what you must understand.
If you are bothered by `.value` , and like a code generator, MobX is a great alternative, and you can use it in conjunction with Get. For those who want to add a single dependency in pubspec and start programming without worrying about the version of a package being incompatible with another, or if the error of a state update is coming from the state manager or dependency, or still, do not want to worrying about the availability of controllers, whether literally "just programming", get is just perfect.
If you have no problem with the MobX code generator, or have no problem with the BLoC boilerplate, you can simply use Get for routes, and forget that it has state manager. Get SEM and RSM were born out of necessity, my company had a project with more than 90 controllers, and the code generator simply took more than 30 minutes to complete its tasks after a Flutter Clean on a reasonably good machine, if your project it has 5, 10, 15 controllers, any state manager will supply you well. If you have an absurdly large project, and code generator is a problem for you, you have been awarded this solution.
Obviously, if someone wants to contribute to the project and create a code generator, or something similar, I will link in this readme as an alternative, my need is not the need for all devs, but for now I say, there are good solutions that already do that, like MobX.
### Obx()
Typing in Get using Bindings is unnecessary. you can use the Obx widget instead of GetX which only receives the anonymous function that creates a widget.
Obviously, if you don't use a type, you will need to have an instance of your controller to use the variables, or use `Get.find<Controller>()` .value or Controller.to.value to retrieve the value.
### Workers
Workers will assist you, triggering specific callbacks when an event occurs.
``` dart
/// Called every time `count1` changes.
ever(count1, (_) => print("$_ has been changed"));
/// Called only first time the variable $_ is changed
once(count1, (_) => print("$_ was changed once"));
/// Anti DDos - Called every time the user stops typing for 1 second, for example.
debounce(count1, (_) => print("debouce$_"), time: Duration(seconds: 1));
/// Ignore all changes within 1 second.
interval(count1, (_) => print("interval $_"), time: Duration(seconds: 1));
```
All workers (except `debounce` ) have a `condition` named parameter, which can be a `bool` or a callback that returns a `bool` .
This `condition` defines when the `callback` function executes.
All workers returns a `Worker` instance, that you can use to cancel ( via `dispose()` ) the worker.
* **`ever`**
is called every time the _Rx_ variable emits a new value.
* **`everAll`**
Much like `ever` , but it takes a `List` of _Rx_ values Called every time its variable is changed. That's it.
* **`once`**
'once' is called only the first time the variable has been changed.
* **`debounce`**
'debounce' is very useful in search functions, where you only want the API to be called when the user finishes typing. If the user types "Jonny", you will have 5 searches in the APIs, by the letter J, o, n, n, and y. With Get this does not happen, because you will have a "debounce" Worker that will only be triggered at the end of typing.
* **`interval`**
'interval' is different from the debouce. debouce if the user makes 1000 changes to a variable within 1 second, he will send only the last one after the stipulated timer (the default is 800 milliseconds). Interval will instead ignore all user actions for the stipulated period. If you send events for 1 minute, 1000 per second, debounce will only send you the last one, when the user stops strafing events. interval will deliver events every second, and if set to 3 seconds, it will deliver 20 events that minute. This is recommended to avoid abuse, in functions where the user can quickly click on something and get some advantage (imagine that the user can earn coins by clicking on something, if he clicked 300 times in the same minute, he would have 300 coins, using interval, you you can set a time frame for 3 seconds, and even then clicking 300 or a thousand times, the maximum he would get in 1 minute would be 20 coins, clicking 300 or 1 million times). The debounce is suitable for anti-DDos, for functions like search where each change to onChange would cause a query to your api. Debounce will wait for the user to stop typing the name, to make the request. If it were used in the coin scenario mentioned above, the user would only win 1 coin, because it is only executed, when the user "pauses" for the established time.
* NOTE: Workers should always be used when starting a Controller or Class, so it should always be on onInit (recommended), Class constructor, or the initState of a StatefulWidget (this practice is not recommended in most cases, but it shouldn't have any side effects).
## Simple State Manager
Get has a state manager that is extremely light and easy, which does not use ChangeNotifier, will meet the need especially for those new to Flutter, and will not cause problems for large applications.
GetBuilder is aimed precisely at multiple state control. Imagine that you added 30 products to a cart, you click delete one, at the same time that the list is updated, the price is updated and the badge in the shopping cart is updated to a smaller number. This type of approach makes GetBuilder killer, because it groups states and changes them all at once without any "computational logic" for that. GetBuilder was created with this type of situation in mind, since for ephemeral change of state, you can use setState and you would not need a state manager for this.
That way, if you want an individual controller, you can assign IDs for that, or use GetX. This is up to you, remembering that the more "individual" widgets you have, the more the performance of GetX will stand out, while the performance of GetBuilder should be superior, when there is multiple change of state.
### Advantages
1. Update only the required widgets.
2. Does not use changeNotifier, it is the state manager that uses less memory (close to 0mb).
3. Forget StatefulWidget! With Get you will never need it. With the other state managers, you will probably have to use a StatefulWidget to get the instance of your Provider, BLoC, MobX Controller, etc. But have you ever stopped to think that your appBar, your scaffold, and most of the widgets that are in your class are stateless? So why save the state of an entire class, if you can only save the state of the Widget that is stateful? Get solves that, too. Create a Stateless class, make everything stateless. If you need to update a single component, wrap it with GetBuilder, and its state will be maintained.
4. Organize your project for real! Controllers must not be in your UI, place your TextEditController, or any controller you use within your Controller class.
5. Do you need to trigger an event to update a widget as soon as it is rendered? GetBuilder has the property "initState", just like StatefulWidget, and you can call events from your controller, directly from it, no more events being placed in your initState.
6. Do you need to trigger an action like closing streams, timers and etc? GetBuilder also has the dispose property, where you can call events as soon as that widget is destroyed.
7. Use streams only if necessary. You can use your StreamControllers inside your controller normally, and use StreamBuilder also normally, but remember, a stream reasonably consumes memory, reactive programming is beautiful, but you shouldn't abuse it. 30 streams open simultaneously can be worse than changeNotifier (and changeNotifier is very bad).
8. Update widgets without spending ram for that. Get stores only the GetBuilder creator ID, and updates that GetBuilder when necessary. The memory consumption of the get ID storage in memory is very low even for thousands of GetBuilders. When you create a new GetBuilder, you are actually sharing the state of GetBuilder that has a creator ID. A new state is not created for each GetBuilder, which saves A LOT OF ram for large applications. Basically your application will be entirely Stateless, and the few Widgets that will be Stateful (within GetBuilder) will have a single state, and therefore updating one will update them all. The state is just one.
9. Get is omniscient and in most cases it knows exactly the time to take a controller out of memory. You should not worry about when to dispose of a controller, Get knows the best time to do this.
### Usage
``` dart
// Create controller class and extends GetxController
class Controller extends GetxController {
int counter = 0;
void increment() {
counter++;
update(); // use update() to update counter variable on UI when increment be called
}
}
// On your Stateless/Stateful class, use GetBuilder to update Text when increment be called
GetBuilder<Controller>(
init: Controller(), // INIT IT ONLY THE FIRST TIME
builder: (_) => Text(
'${_.counter}',
),
)
//Initialize your controller only the first time. The second time you are using ReBuilder for the same controller, do not use it again. Your controller will be automatically removed from memory as soon as the widget that marked it as 'init' is deployed. You don't have to worry about that, Get will do it automatically, just make sure you don't start the same controller twice.
```
**Done!**
* You have already learned how to manage states with Get.
* Note: You may want a larger organization, and not use the init property. For that, you can create a class and extends Bindings class, and within it mention the controllers that will be created within that route. Controllers will not be created at that time, on the contrary, this is just a statement, so that the first time you use a Controller, Get will know where to look. Get will remain lazyLoad, and will continue to dispose Controllers when they are no longer needed. See the pub.dev example to see how it works.
If you navigate many routes and need data that was in your previously used controller, you just need to use GetBuilder Again (with no init):
``` dart
class OtherClass extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: GetBuilder<Controller>(
builder: (s) => Text('${s.counter}'),
),
),
);
}
```
If you need to use your controller in many other places, and outside of GetBuilder, just create a get in your controller and have it easily. (or use `Get.find<Controller>()` )
``` dart
class Controller extends GetxController {
/// You do not need that. I recommend using it just for ease of syntax.
/// with static method: Controller.to.increment();
/// with no static method: Get.find<Controller>().increment();
/// There is no difference in performance, nor any side effect of using either syntax. Only one does not need the type, and the other the IDE will autocomplete it.
static Controller get to => Get.find(); // add this line
int counter = 0;
void increment() {
counter++;
update();
}
}
```
And then you can access your controller directly, that way:
``` dart
FloatingActionButton(
onPressed: () {
Controller.to.increment(),
} // This is incredibly simple!
child: Text("${Controller.to.counter}"),
),
```
When you press FloatingActionButton, all widgets that are listening to the 'counter' variable will be updated automatically.
### How it handles controllers
Let's say we have this:
`Class a => Class B (has controller X) => Class C (has controller X)`
In class A the controller is not yet in memory, because you have not used it yet (Get is lazyLoad). In class B you used the controller, and it entered memory. In class C you used the same controller as in class B, Get will share the state of controller B with controller C, and the same controller is still in memory. If you close screen C and screen B, Get will automatically take controller X out of memory and free up resources, because Class a is not using the controller. If you navigate to B again, controller X will enter memory again, if instead of going to class C, you return to class A again, Get will take the controller out of memory in the same way. If class C didn't use the controller, and you took class B out of memory, no class would be using controller X and likewise it would be disposed of. The only exception that can mess with Get, is if you remove B from the route unexpectedly, and try to use the controller in C. In this case, the creator ID of the controller that was in B was deleted, and Get was programmed to remove it from memory every controller that has no creator ID. If you intend to do this, add the "autoRemove: false" flag to class B's GetBuilder and use adoptID = true; in class C's GetBuilder.
### You won't need StatefulWidgets anymore
Using StatefulWidgets means storing the state of entire screens unnecessarily, even because if you need to minimally rebuild a widget, you will embed it in a Consumer/Observer/BlocProvider/GetBuilder/GetX/Obx, which will be another StatefulWidget.
The StatefulWidget class is a class larger than StatelessWidget, which will allocate more RAM, and this may not make a significant difference between one or two classes, but it will most certainly do when you have 100 of them!
Unless you need to use a mixin, like TickerProviderStateMixin, it will be totally unnecessary to use a StatefulWidget with Get.
You can call all methods of a StatefulWidget directly from a GetBuilder.
If you need to call initState() or dispose() method for example, you can call them directly;
``` dart
GetBuilder<Controller>(
initState: (_) => Controller.to.fetchApi(),
dispose: (_) => Controller.to.closeStreams(),
builder: (s) => Text('${s.username}'),
),
```
A much better approach than this is to use the onInit() and onClose() method directly from your controller.
``` dart
@override
void onInit() {
fetchApi();
super.onInit();
}
```
* NOTE: If you want to start a method at the moment the controller is called for the first time, you DON'T NEED to use constructors for this, in fact, using a performance-oriented package like Get, this borders on bad practice, because it deviates from the logic in which the controllers are created or allocated (if you create an instance of this controller, the constructor will be called immediately, you will be populating a controller before it is even used, you are allocating memory without it being in use, this definitely hurts the principles of this library). The onInit() methods; and onClose(); were created for this, they will be called when the Controller is created, or used for the first time, depending on whether you are using Get.lazyPut or not. If you want, for example, to make a call to your API to populate data, you can forget about the old-fashioned method of initState/dispose, just start your call to the api in onInit, and if you need to execute any command like closing streams, use the onClose() for that.
### Why it exists
The purpose of this package is precisely to give you a complete solution for navigation of routes, management of dependencies and states, using the least possible dependencies, with a high degree of decoupling. Get engages all high and low level Flutter APIs within itself, to ensure that you work with the least possible coupling. We centralize everything in a single package, to ensure that you don't have any kind of coupling in your project. That way, you can put only widgets in your view, and leave the part of your team that works with the business logic free, to work with the business logic without depending on any element of the View. This provides a much cleaner working environment, so that part of your team works only with widgets, without worrying about sending data to your controller, and part of your team works only with the business logic in its breadth, without depending on no element of the view.
So to simplify this:
You don't need to call methods in initState and send them by parameter to your controller, nor use your controller constructor for that, you have the onInit() method that is called at the right time for you to start your services.
You do not need to call the device, you have the onClose() method that will be called at the exact moment when your controller is no longer needed and will be removed from memory. That way, leave views for widgets only, refrain from any kind of business logic from it.
Do not call a dispose method inside GetxController, it will not do anything, remember that the controller is not a Widget, you should not "dispose" it, and it will be automatically and intelligently removed from memory by Get. If you used any stream on it and want to close it, just insert it into the close method. Example:
``` dart
class Controller extends GetxController {
StreamController<User> user = StreamController<User>();
StreamController<String> name = StreamController<String>();
/// close stream = onClose method, not dispose.
@override
void onClose() {
user.close();
name.close();
super.onClose();
}
}
```
Controller life cycle:
* onInit() where it is created.
* onClose() where it is closed to make any changes in preparation for the delete method
* deleted: you do not have access to this API because it is literally removing the controller from memory. It is literally deleted, without leaving any trace.
### Other ways of using it
You can use Controller instance directly on GetBuilder value:
``` dart
GetBuilder<Controller>(
init: Controller(),
builder: (value) => Text(
'${value.counter}', //here
),
),
```
You may also need an instance of your controller outside of your GetBuilder, and you can use these approaches to achieve this:
``` dart
class Controller extends GetxController {
static Controller get to => Get.find();
[...]
}
// on you view:
GetBuilder<Controller>(
init: Controller(), // use it only first time on each controller
builder: (_) => Text(
'${Controller.to.counter}', //here
)
),
```
or
``` dart
class Controller extends GetxController {
// static Controller get to => Get.find(); // with no static get
[...]
}
// on stateful/stateless class
GetBuilder<Controller>(
init: Controller(), // use it only first time on each controller
builder: (_) => Text(
'${Get.find<Controller>().counter}', //here
),
),
```
* You can use "non-canonical" approaches to do this. If you are using some other dependency manager, like get_it, modular, etc., and just want to deliver the controller instance, you can do this:
``` dart
Controller controller = Controller();
[...]
GetBuilder<Controller>(
init: controller, //here
builder: (_) => Text(
'${controller.counter}', // here
),
),
```
### Unique IDs
If you want to refine a widget's update control with GetBuilder, you can assign them unique IDs:
``` dart
GetBuilder<Controller>(
id: 'text'
init: Controller(), // use it only first time on each controller
builder: (_) => Text(
'${Get.find<Controller>().counter}', //here
),
),
```
And update it this form:
``` dart
update(['text']);
```
You can also impose conditions for the update:
``` dart
update(['text'], counter < 10);
```
GetX does this automatically and only reconstructs the widget that uses the exact variable that was changed, if you change a variable to the same as the previous one and that does not imply a change of state , GetX will not rebuild the widget to save memory and CPU cycles (3 is being displayed on the screen, and you change the variable to 3 again. In most state managers, this will cause a new rebuild, but with GetX the widget will only is rebuilt again, if in fact his state has changed).
## Mixing the two state managers
Some people opened a feature request, as they wanted to use only one type of reactive variable, and the other mechanics, and needed to insert an Obx into a GetBuilder for this. Thinking about it MixinBuilder was created. It allows both reactive changes by changing ".obs" variables, and mechanical updates via update(). However, of the 4 widgets he is the one that consumes the most resources, since in addition to having a Subscription to receive change events from his children, he subscribes to the update method of his controller.
Extending GetxController is important, as they have life cycles, and can "start" and "end" events in their onInit() and onClose() methods. You can use any class for this, but I strongly recommend you use the GetxController class to place your variables, whether they are observable or not.
## StateMixin
Another way to handle your `UI` state is use the `StateMixin<T>` .
To implement it, use the `with` to add the `StateMixin<T>`
to your controller which allows a T model.
``` dart
class Controller extends GetController with StateMixin<User>{}
```
The `change()` method change the State whenever we want.
Just pass the data and the status in this way:
```dart
change(data, status: RxStatus.success());
```
RxStatus allow these status:
``` dart
RxStatus.loading();
RxStatus.success();
RxStatus.empty();
RxStatus.error('message');
```
To represent it in the UI, use:
```dart
class OtherClass extends GetView<Controller> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: controller.obx(
(state)=>Text(state.name),
// here you can put your custom loading indicator, but
// by default would be Center(child:CircularProgressIndicator())
onLoading: CustomLoadingIndicator(),
onEmpty: Text('No data found'),
// here also you can set your own error widget, but by
// default will be an Center(child:Text(error))
onError: (error)=>Text(error),
),
);
}
```
## GetBuilder vs GetX vs Obx vs MixinBuilder
In a decade working with programming I was able to learn some valuable lessons.
My first contact with reactive programming was so "wow, this is incredible" and in fact reactive programming is incredible.
However, it is not suitable for all situations. Often all you need is to change the state of 2 or 3 widgets at the same time, or an ephemeral change of state, in which case reactive programming is not bad, but it is not appropriate.
Reactive programming has a higher RAM consumption that can be compensated for by the individual workflow, which will ensure that only one widget is rebuilt and when necessary, but creating a list with 80 objects, each with several streams is not a good one idea. Open the dart inspect and check how much a StreamBuilder consumes, and you'll understand what I'm trying to tell you.
With that in mind, I created the simple state manager. It is simple, and that is exactly what you should demand from it: updating state in blocks in a simple way, and in the most economical way.
GetBuilder is very economical in RAM, and there is hardly a more economical approach than him (at least I can't imagine one, if it exists, please let us know).
However, GetBuilder is still a mechanical state manager, you need to call update() just like you would need to call Provider's notifyListeners().
There are other situations where reactive programming is really interesting, and not working with it is the same as reinventing the wheel. With that in mind, GetX was created to provide everything that is most modern and advanced in a state manager. It updates only what is necessary and when necessary, if you have an error and send 300 state changes simultaneously, GetX will filter and update the screen only if the state actually changes.
GetX is still more economical than any other reactive state manager, but it consumes a little more RAM than GetBuilder. Thinking about it and aiming to maximize the consumption of resources that Obx was created. Unlike GetX and GetBuilder, you will not be able to initialize a controller inside an Obx, it is just a Widget with a StreamSubscription that receives change events from your children, that's all. It is more economical than GetX, but loses to GetBuilder, which was to be expected, since it is reactive, and GetBuilder has the most simplistic approach that exists, of storing a widget's hashcode and its StateSetter. With Obx you don't need to write your controller type, and you can hear the change from multiple different controllers, but it needs to be initialized before, either using the example approach at the beginning of this readme, or using the Bindings class.

View File

@@ -0,0 +1,266 @@
- [Gestión de dependencias](#gestión-de-dependencias)
- [Simple Instance Manager](#simple-instance-manager)
- [Options](#options)
- [Bindings](#bindings)
- [Cómo utilizar](#cómo-utilizar)
- [SmartManagement](#smartmanagement)
# Gestión de dependencias
## Simple Instance Manager
- Nota: si está utilizando el gestor de estado de GetX, no tiene que preocuparse por esto, solo lea para obtener información, pero preste más atención a la API de bindings, que hará todo esto automáticamente por usted.
¿Ya estás utilizando GetX y quieres que tu proyecto sea lo más ágil posible? GetX tiene un gestor de dependencias simple y poderoso que le permite recuperar la misma clase que su BLoC o Controller con solo una líneas de código, sin contexto de Provider, sin inheritedWidget:
```dart
Controller controller = Get.put(Controller()); // Rather Controller controller = Controller();
```
En lugar de crear una instancia de su clase dentro de la clase que está utilizando, la está creando dentro de la instancia GetX, que la hará disponible en toda su aplicación. Entonces puede usar su Controller (o BLoC) normalmente.
```dart
controller.fetchApi();
```
Imagine que ha navegado a través de numerosas rutas y necesita datos que quedaron en su controlador, necesitaría un gestor de estado combinado con Providere o Get_it, ¿correcto? No con GetX. Solo necesita pedirle a GetX que "encuentre" su controlador, no necesita dependencias adicionales:
```dart
Controller controller = Get.find();
//Yes, it looks like Magic, Get will find your controller, and will deliver it to you. You can have 1 million controllers instantiated, Get will always give you the right controller.
```
Y luego podrá recuperar los datos de su controlador que se obtuvieron allí:
```dart
Text(controller.textFromApi);
```
¿Buscando lazy loading? Puede declarar todos sus controladores, y se llamará solo cuando alguien lo necesite. Puedes hacer esto con:
```dart
Get.lazyPut<Service>(()=> ApiMock());
/// ApiMock will only be called when someone uses Get.find<Service> for the first time
```
Si desea registrar una instancia asincrónica, puede usar Get.putAsync.
```dart
Get.putAsync<SharedPreferences>(() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setInt('counter', 12345);
return prefs;
});
```
uso:
```dart
int count = Get.find<SharedPreferences>().getInt('counter');
print(count);
// out: 12345
}
```
Para eliminar una instancia de GetX:
```dart
Get.delete<Controller>();
```
## Instancing methods
Although Getx already delivers very good settings for use, it is possible to refine them even more so that it become more useful to the programmer. The methods and it's configurable parameters are:
- Get.put():
```dart
Get.put<S>(
// mandatory: the class that you want to get to save, like a controller or anything
// note: that "S" means that it can be anything
S dependency
// optional: this is for when you want multiple classess that are of the same type
// since you normally get a class by using Get.find<Controller>(),
// you need to use tag to tell which instance you need
// must be unique string
String tag,
// optional: by default, get will dispose instances after they are not used anymore (example,
// the controller of a view that is closed), but you might need that the instance
// to be kept there throughout the entire app, like an instance of sharedPreferences or something
// so you use this
// defaults to false
bool permanent = false,
// optional: allows you after using an abstract class in a test, replace it with another one and follow the test.
// defaults to false
bool overrideAbstract = false,
// optional: allows you to create the dependency using function instead of the dependency itself.
InstanceBuilderCallback<S> builder,
)
```
- Get.lazyPut:
```dart
Get.lazyPut<S>(
// mandatory: a method that will be executed when your class is called for the first time
// Example: Get.lazyPut<Controller>( () => Controller() )
InstanceBuilderCallback builder,
// optional: same as Get.put(), it is used for when you want multiple different instance of a same class
// must be unique
String tag,
// optional: It is similar to "permanent", the difference is that the instance is discarded when
// is not being used, but when it's use is needed again, Get will recreate the instance
// just the same as "SmartManagement.keepFactory" in the bindings api
// defaults to false
bool fenix = false
)
```
- Get.putAsync:
```dart
Get.putAsync<S>(
// mandatory: an async method that will be executed to instantiate your class
// Example: Get.putAsync<YourAsyncClass>( () async => await YourAsyncClass() )
AsyncInstanceBuilderCallback<S> builder,
// optional: same as Get.put(), it is used for when you want multiple different instance of a same class
// must be unique
String tag,
// optional: same as in Get.put(), used when you need to maintain that instance alive in the entire app
// defaults to false
bool permanent = false
```
- Get.create:
```dart
Get.create<S>(
// required: a function that returns a class that will be "fabricated" every
// time `Get.find()` is called
// Example: Get.create<YourClass>(() => YourClass())
FcBuilderFunc<S> builder,
// optional: just like Get.put(), but it is used when you need multiple instances
// of a of a same class
// Useful in case you have a list that each item need it's own controller
// needs to be a unique string. Just change from tag to name
String name,
// optional: just like int`Get.put()`, it is for when you need to keep the
// instance alive thoughout the entire app. The difference is in Get.create
// permanent is true by default
bool permanent = true
```
### Diferences between methods:
First, let's of the `fenix` of Get.lazyPut and the `permanent` of the other methods.
The fundamental difference between `permanent` and `fenix` is how you want to store your instances.
Reinforcing: by default, GetX deletes instances when they are not is use.
It means that: If screen 1 has controller 1 and screen 2 has controller 2 and you remove the first route from stack, (like if you use `Get.off()` or `Get.offName()`) the controller 1 lost it's use so it will be erased.
But if you want to opt to `permanent:true`, then the controller will not be lost in this transition - which is very usefult for services that you want to keep alive thoughout the entire application.
`fenix` in the other hand is for services that you don't worry in losing between screen changes, but when you need that service, you expect that it is alive. So basically, it will dispose the unused controller/service/class, but when you need that, it will "recreate from the ashes" a new instance.
Proceeding with the differences between methods:
- Get.put and Get.putAsync follow the same creation order, with the difference that asyn opt for applying a asynchronous method: those two methods create and initialize the instance. That one is inserted directly in the memory, using the internal method `insert` with the parameters `permanent: false` and `isSingleton: true` (this isSingleton parameter only porpuse is to tell if it is to use the dependency on `dependency` or if it is to use the dependency on `FcBuilderFunc`). After that, `Get.find()` is called that immediately initialize the instances that are on memory.
- Get.create: As the name implies, it will "create" your dependency! Similar to `Get.put()`, it also call the internal method `insert` to instancing. But `permanent` became true and `isSingleton` became false (since we are "creating" our dependency, there is no way for it to be a singleton instace, that's why is false). And because it has `permanent: true`, we have by default the benefit of not losing it between screens! Also, `Get.find()` is not called immediately, it wait to be used in the screen to be called. It is created this way to make use of the parameter `permanent`, since then, worth noticing, `Get.create()` was made with the goal of create not shared instances, but don't get disposed, like for example a button in a listView, that you want a unique instance for that list - because of that, Get.create must be used together with GetWidget.
- Get.lazyPut: As the name implies, it is a lazy proccess. The instance is create, but it is not called to be used immediately, it remains waiting to be called. Contrary to the other methods, `insert` is not called here. Instead, the instance is inserted in another part of the memory, a part responsable to tell if the instance can be recreated or not, let's call it "factory". If we want to create something to be used later, it will not be mix with things been used right now. And here is where `fenix` magic enters: if you opt to leaving `fenix: false`, and your `smartManagement` are not `keepFactory`, then when using `Get.find` the instance will change the place in the memory from the "factory" to common instance memory area. Right after that, by default it is removed from the "factory". Now, if you opt for `fenix: true`, the instance continues to exist in this dedicated part, even going to the common area, to be called again in the future.
## Bindings
Una de las grandes diferencias de este paquete, tal vez, es la posibilidad de una integración completa de las rutas, gestor de estado y dependencias.
Cuando se elimina una ruta del stack, todos los controladores, variables e instancias de objetos relacionados con ella se eliminan de la memoria. Si está utilizando stream o timers, se cerrarán automáticamente y no tendrá que preocuparse por nada de eso.
En la versión 2.10 GetX se implementó por completo la API de bindings.
Ahora ya no necesita usar el método init. Ni siquiera tiene que escribir sus controladores si no lo desea. Puede iniciar sus controladores y servicios en un lugar apropiado para eso.
La clase Binding es una clase que desacoplará la inyección de dependencia, al tiempo que vinculará rutas con el gestor de estado y el gestor de dependencias.
Esto permite conocer qué pantalla se muestra cuando se utiliza un controlador en particular y saber dónde y cómo descartarlo.
Además, la clase Binding le permitirá tener el control de configuración SmartManager. Puede configurar las dependencias que se organizarán al eliminar una ruta de la pila, o cuando el widget que lo usó se presenta, o ninguno de los dos. Tendrá una gestión inteligente de dependencias que funcione para usted, pero aun así, puede configurarla como desee.
### Cómo utilizar
- Crea una clase e implementa Binding
```dart
class HomeBinding implements Bindings{
```
Su IDE le pedirá automáticamente que anule el método de "dependencies", y solo necesita hacer clic en la lámpara, anular el método e insertar todas las clases que va a utilizar en esa ruta:
```dart
class HomeBinding implements Bindings{
@override
void dependencies() {
Get.lazyPut<ControllerX>(() => ControllerX());
Get.lazyPut<Service>(()=> Api());
}
}
```
Ahora solo necesita informar su ruta, que utilizará ese binding para establecer la conexión entre el gestor de rutas, las dependencias y los estados.
- Uso de rutas nombradas:
```dart
getPages: [
GetPage(name: '/', page: () => Home(), binding: HomeBinding()),
]
```
- Usando rutas normales:
```dart
Get.to(Home(), binding: HomeBinding());
```
Allí, ya no tiene que preocuparse por la administración de memoria de su aplicación, GetX lo hará por usted.
La clase Binding se llama cuando se llama una ruta, puede crear un initialBinding en su GetMaterialApp para insertar todas las dependencias que se crearán.
```dart
GetMaterialApp(
initialBinding: SampleBind(),
home: Home(),
);
```
## SmartManagement
Siempre prefiera usar SmartManagement estándar (full), no necesita configurar nada para eso, GetX ya se lo proporciona de forma predeterminada. Seguramente eliminará todos los controladores en desuso de la memoria, ya que su control refinado elimina la dependencia, incluso si se produce un error y un widget que lo utiliza no se elimina correctamente.
El modo "full" también es lo suficientemente seguro como para usarlo con StatelessWidget, ya que tiene numerosos callbacks de seguridad que evitarán que un controlador permanezca en la memoria si ningún widget lo está utilizando, y los disposers no son importante aquí. Sin embargo, si le molesta el comportamiento predeterminado, o simplemente no quiere que suceda, GetX ofrece otras opciones más indulgentes para la administración inteligente de la memoria, como SmartManagement.onlyBuilders, que dependerá de la eliminación efectiva de los widgets que estén usando el controller para eliminarlo, y puede evitar que se implemente un controlador usando "autoRemove: false" en su GetBuilder/GetX.
Con esta opción, solo se eliminarán los controladores iniciados en "init:" o cargados en un enlace con "Get.lazyPut"; si usa Get.put o cualquier otro enfoque, SmartManagement no tendrá permisos para excluir esta dependencia.
Con el comportamiento predeterminado, incluso los widgets instanciados con "Get.put" se eliminarán, a diferencia de SmartManagement.onlyBuilders.
SmartManagement.keepFactory es como SmartManagement.full, con una diferencia. SmartManagement.full purga los factories de las premises, de modo que Get.lazyPut() solo podrá llamarse una vez y su factory y sus referencias se autodestruirán. SmartManagement.keepFactory eliminará sus dependencias cuando sea necesario, sin embargo, mantendrá la "forma" de estas, para hacer una igual si necesita una instancia de eso nuevamente.
En lugar de usar SmartManagement.keepFactory, puede usar Bindings.
Bindings crea factories transitorios, que se crean en el momento en que hace clic para ir a otra pantalla, y se destruirán tan pronto como ocurra la animación de cambio de pantalla. Es tan poco tiempo que el analizador ni siquiera podrá registrarlo. Cuando navegue de nuevo a esta pantalla, se llamará a una nueva factory temporal, por lo que es preferible usar SmartManagement.keepFactory, pero si no desea crear enlaces o desea mantener todas sus dependencias en el mismo enlace, sin duda te ayudará. Las factories ocupan poca memoria, no tienen instancias, sino una función con la "forma" de esa clase que desea. Esto es muy poco, pero dado que el propósito de esta lib es obtener el máximo rendimiento posible utilizando los recursos mínimos, GetX elimina incluso las factories por defecto. Use el que sea más conveniente para usted.
- NOTA: NO USE SmartManagement.keepFactory si está utilizando bindings múltiples. Fue diseñado para usarse sin bindings, o con uno único vinculado en el binding inicial de GetMaterialApp.
- NOTA2: El uso de bindings es completamente opcional, puede usar Get.put() y Get.find() en clases que usan un controlador dado sin ningún problema.
Sin embargo, si trabaja con Servicios o cualquier otra abstracción, le recomiendo usar binding para una organización más grande.

View File

@@ -0,0 +1,562 @@
- [Gestión de Rutas](#gestión-de-rutas)
- [¿Cómo utilizarlo](#cómo-utilizarlo)
- [Navegación sin rutas nombradas](#navegación-sin-rutas-nombradas)
- [Navegación con rutas nombradas](#navegación-con-rutas-nombradas)
- [Enviar datos a rutas nombradas](#enviar-datos-a-rutas-nombradas)
- [Enlaces de URL dinámicos](#enlaces-de-url-dinámicos)
- [Middleware](#middleware)
- [Navegación sin contexto](#navegación-sin-contexto)
- [SnackBars](#snackbars)
- [Diálogos](#diálogos)
- [BottomSheets](#bottomsheets)
- [Navegación anidada](#navegación-anidada)
# Gestión de Rutas
Cualquier contribución es bienvenida!
## ¿Cómo utilizarlo
Agregue esto a su archivo pubspec.yaml:
```yaml
dependencies:
get:
```
Si va a utilizar rutas/snackbars/dialogs/bottomsheets sin contexto, o las APIs de GetX de alto nivel, simplemente debe agregar "Get" antes de su MaterialApp, ¡convertirlo en GetMaterialApp y disfrutar!
```dart
GetMaterialApp( // Before: MaterialApp(
home: MyHome(),
)
```
## Navegación sin rutas nombradas
Para navegar a una nueva pantalla:
```dart
Get.to(NextScreen());
```
Para cerrar snackbars, dialogs, bottomsheets o cualquier cosa que normalmente cierre con Navigator.pop(contexto);
```dart
Get.back();
```
Para ir a la siguiente pantalla, sin opción a volver (util por ejemplo en SplashScreens, LoginScreen, etc.)
```dart
Get.off(NextScreen());
```
Para ir a la siguiente pantalla y cancelar todas las rutas anteriores (útil en carritos de compras, encuestas y exámenes)
```dart
Get.offAll(NextScreen());
```
Para navegar a la siguiente ruta y recibir o actualizar datos tan pronto como se regrese de ella:
```dart
var data = await Get.to(Payment());
```
en la otra pantalla, envíe los datos para la ruta anterior:
```dart
Get.back(result: 'success');
```
Y luego usarlo:
ej:
```dart
if(data == 'success') madeAnything();
```
¿No quieres aprender nuestra sintaxis?
Simplemente cambie Navigator (mayúsculas) a navigator (minúsculas), y tendrá todas las funciones de la navegación estándar, pero sin tener que usar el contexto.
Ejemplo:
```dart
// Default Flutter navigator
Navigator.of(context).push(
context,
MaterialPageRoute(
builder: (BuildContext context) {
return HomePage();
},
),
);
// Get using Flutter syntax without needing context
navigator.push(
MaterialPageRoute(
builder: (_) {
return HomePage();
},
),
);
// Get syntax (It is much better, but you have the right to disagree)
Get.to(HomePage());
```
## Navegación con rutas nombradas
- Si prefiere navegar con rutas nombradas, con GetX también es posible.
Para navegar a la siguiente pantalla
```dart
Get.toNamed("/NextScreen");
```
Para navegar y eliminar la pantalla anterior del árbol.
```dart
Get.offNamed("/NextScreen");
```
Para navegar y eliminar todas las pantallas anteriores del árbol.
```dart
Get.offAllNamed("/NextScreen");
```
Para definir rutas, use GetMaterialApp:
```dart
void main() {
runApp(
GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => MyHomePage()),
GetPage(name: '/second', page: () => Second()),
GetPage(
name: '/third',
page: () => Third(),
transition: Transition.zoom
),
],
)
);
}
```
Para manejar la navegación a rutas no definidas (error 404), puede definir una página de ruta desconocida en GetMaterialApp.
```dart
void main() {
runApp(
GetMaterialApp(
unknownRoute: GetPage(name: '/notfound', page: () => UnknownRoutePage()),
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => MyHomePage()),
GetPage(name: '/second', page: () => Second()),
],
)
);
}
```
### Enviar datos a rutas nombradas
Envía lo que quieras usando el parámetro arguments. GetX acepta cualquier cosa aquí, ya sea un String, Map, List o incluso una instancia de clase.
```dart
Get.toNamed("/NextScreen", arguments: 'Get is the best');
```
luego en su clase o controlador:
```dart
print(Get.arguments);
//print out: Get is the best
```
### Enlaces de URL dinámicos
GetX ofrece URLs dinámicas avanzadas como en la Web. Los desarrolladores web probablemente ya quisieron esta característica en Flutter, y lo más probable es que hayan visto un paquete que promete esta característica y pero que ofrece una sintaxis totalmente diferente a la que una URL tendría en la web, pero GetX lo resuelve.
```dart
Get.offAllNamed("/NextScreen?device=phone&id=354&name=Enzo");
```
y luego en su clase controller/bloc/stateful/stateless:
```dart
print(Get.parameters['id']);
// out: 354
print(Get.parameters['name']);
// out: Enzo
```
También puede recibir parámetros nombrados fácilmente:
```dart
void main() {
runApp(
GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(
name: '/',
page: () => MyHomePage(),
),
GetPage(
name: '/profile/',
page: () => MyProfile(),
),
//You can define a different page for routes with arguments, and another without arguments, but for that you must use the slash '/' on the route that will not receive arguments as above.
GetPage(
name: '/profile/:user',
page: () => UserProfile(),
),
GetPage(
name: '/third',
page: () => Third(),
transition: Transition.cupertino
),
],
)
);
}
```
Enviar datos sobre el nombre de la ruta
```dart
Get.toNamed("/second/34954");
```
Y en la segunda pantalla tome los datos por parámetro
```dart
print(Get.parameters['user']);
// salida: 34954
```
o envie multiples parametros de la siguiente manera
```dart
Get.toNamed("/profile/34954?flag=true");
```
En la segunda pantalla tome los parametros como lo haria normalmente
```dart
print(Get.parameters['user']);
print(Get.parameters['flag']);
// salida: 34954 true
```
Y ahora, todo lo que necesita hacer es usar Get.toNamed() para navegar por sus rutas nombradas, sin ningún contexto (puede llamar a sus rutas directamente desde su clase BLoC o Controller), y cuando su aplicación se compila para web, sus rutas aparecerán en la url del navegador <3
### Middleware
Si desea escuchar eventos de GetX para activar acciones, puede usar el routingCallback:
```dart
GetMaterialApp(
routingCallback: (routing) {
if(routing.current == '/second'){
openAds();
}
}
)
```
Si no está usando GetMaterialApp, puede usar la API para adjuntar el observador de Middleware.
```dart
void main() {
runApp(
MaterialApp(
onGenerateRoute: Router.generateRoute,
initialRoute: "/",
navigatorKey: Get.key,
navigatorObservers: [
GetObserver(MiddleWare.observer), // HERE !!!
],
),
);
}
```
Crear la clase MiddleWare:
```dart
class MiddleWare {
static observer(Routing routing) {
/// You can listen in addition to the routes, the snackbars, dialogs and bottomsheets on each screen.
///If you need to enter any of these 3 events directly here,
///you must specify that the event is != Than you are trying to do.
if (routing.current == '/second' && !routing.isSnackbar) {
Get.snackbar("Hi", "You are on second route");
} else if (routing.current =='/third'){
print('last route called');
}
}
}
```
Ahora, usa GetX en tu código:
```dart
class First extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.add),
onPressed: () {
Get.snackbar("hi", "i am a modern snackbar");
},
),
title: Text('First Route'),
),
body: Center(
child: ElevatedButton(
child: Text('Open route'),
onPressed: () {
Get.toNamed("/second");
},
),
),
);
}
}
class Second extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.add),
onPressed: () {
Get.snackbar("hi", "i am a modern snackbar");
},
),
title: Text('second Route'),
),
body: Center(
child: ElevatedButton(
child: Text('Open route'),
onPressed: () {
Get.toNamed("/third");
},
),
),
);
}
}
class Third extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Third Route"),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Get.back();
},
child: Text('Go back!'),
),
),
);
}
}
```
## Navegación sin contexto
### SnackBars
Para tener simple SnackBar con Flutter, debe obtener el contexto de Scaffold, o debe utilizar una GlobalKey:
```dart
final snackBar = SnackBar(
content: Text('Hi!'),
action: SnackBarAction(
label: 'I am a old and ugly snackbar :(',
onPressed: (){}
),
);
// Find the Scaffold in the widget tree and use
// it to show a SnackBar.
Scaffold.of(context).showSnackBar(snackBar);
```
Con GetX esto se resume en:
```dart
Get.snackbar('Hi', 'i am a modern snackbar');
```
Todo lo que tiene que hacer es llamar a Get.snackbar desde cualquier parte de su código y personalizarlo como desee:
```dart
Get.snackbar(
"Hey i'm a Get SnackBar!", // title
"It's unbelievable! I'm using SnackBar without context, without boilerplate, without Scaffold, it is something truly amazing!", // message
icon: Icon(Icons.alarm),
shouldIconPulse: true,
onTap:(){},
barBlur: 20,
isDismissible: true,
duration: Duration(seconds: 3),
);
////////// ALL FEATURES //////////
// Color colorText,
// Duration duration,
// SnackPosition snackPosition,
// Widget titleText,
// Widget messageText,
// bool instantInit,
// Widget icon,
// bool shouldIconPulse,
// double maxWidth,
// EdgeInsets margin,
// EdgeInsets padding,
// double borderRadius,
// Color borderColor,
// double borderWidth,
// Color backgroundColor,
// Color leftBarIndicatorColor,
// List<BoxShadow> boxShadows,
// Gradient backgroundGradient,
// TextButton mainButton,
// OnTap onTap,
// bool isDismissible,
// bool showProgressIndicator,
// AnimationController progressIndicatorController,
// Color progressIndicatorBackgroundColor,
// Animation<Color> progressIndicatorValueColor,
// SnackStyle snackStyle,
// Curve forwardAnimationCurve,
// Curve reverseAnimationCurve,
// Duration animationDuration,
// double barBlur,
// double overlayBlur,
// Color overlayColor,
// Form userInputForm
///////////////////////////////////
```
Si prefiere el snackbar tradicional, o desea personalizarlo desde cero, inclyendo reducirlo a una sola línea de código (dado que Get.snackbar utiliza al menos un título y un mensaje obligatorios), puede usar `Get.rawSnackbar();` que proporciona la API en la que se creó el Get.snackbar.
### Diálogos
Para abrir el dialog:
```dart
Get.dialog(YourDialogWidget());
```
Para abrir un dialog predeterminado:
```dart
Get.defaultDialog(
onConfirm: () => print("Ok"),
middleText: "Dialog made in 3 lines of code"
);
```
También puede usar Get.generalDialog en lugar de showGeneralDialog.
Para todos los demás dialogs de Flutter, incluidos los cupertinos, puede usar Get.overlayContext en lugar de context, y abrirlo en cualquier parte de su código.
Para los widgets que no usan Overlay, puede usar Get.context.
Estos dos contexts funcionarán en el 99% de los casos para reemplazar el context de su UI, excepto en los casos donde inheritedWidget es usado sin un contexto de navegación.
### BottomSheets
Get.bottomSheet es como showModalBottomSheet, pero no necesita contexto.
```dart
Get.bottomSheet(
Container(
child: Wrap(
children: <Widget>[
ListTile(
leading: Icon(Icons.music_note),
title: Text('Music'),
onTap: () {}
),
ListTile(
leading: Icon(Icons.videocam),
title: Text('Video'),
onTap: () {},
),
],
),
)
);
```
## Navegación anidada
GetX hizo la navegación anidada de Flutter aún más fácil.
No necesita el contexto, y encontrará su pila de navegación por Id.
- NOTA: Crear pilas de navegación paralelas puede ser peligroso. Lo ideal es no usar NestedNavigators o hacerlo con moderación. Si su proyecto lo requiere, continúe, pero tenga en cuenta que mantener múltiples pilas de navegación en la memoria puede no ser una buena idea para el consumo de RAM.
Mira qué simple es:
```dart
Navigator(
key: Get.nestedKey(1), // create a key by index
initialRoute: '/',
onGenerateRoute: (settings) {
if (settings.name == '/') {
return GetPageRoute(
page: () => Scaffold(
appBar: AppBar(
title: Text("Main"),
),
body: Center(
child: TextButton(
color: Colors.blue,
onPressed: () {
Get.toNamed('/second', id:1); // navigate by your nested route by index
},
child: Text("Go to second"),
),
),
),
);
} else if (settings.name == '/second') {
return GetPageRoute(
page: () => Center(
child: Scaffold(
appBar: AppBar(
title: Text("Main"),
),
body: Center(
child: Text("second")
),
),
),
);
}
}
),
```

View File

@@ -0,0 +1,601 @@
- [Gestión del Estado](#gestión-del-estado)
- [Gestor de Estado Simple](#gestor-de-estado-simple)
- [Ventajas](#ventajas)
- [Uso](#uso)
- [Cómo maneja los Controllers](#cómo-maneja-los-controllers)
- [Ya no necesitará StatefulWidgets](#ya-no-necesitará-statefulwidgets)
- [Por qué existe](#por-qué-existe)
- [Otras formas de usarlo](#otras-formas-de-usarlo)
- [ID únicos](#id-únicos)
- [Reactivo STATE_MANAGER](#reactivo-state_manager)
- [Ventajas](#ventajas-1)
- [Uso](#uso-1)
- [Donde se pueden usar .obs](#donde-se-pueden-usar-obs)
- [Nota sobre listas](#nota-sobre-listas)
- [¿Por qué tengo que usar .value](#por-qué-tengo-que-usar-value)
- [Obx()](#obx)
- [Workers](#workers)
- [Mezclando los dos State Managers](#mezclando-los-dos-state-managers)
- [GetBuilder vs GetX vs Obx vs MixinBuilder](#getbuilder-vs-getx-vs-obx-vs-mixinbuilder)
# Gestión del Estado
Actualmente hay varios State Managers para Flutter. Sin embargo, con la mayoría de ellos implica utilizar ChangeNotifier para actualizar widgets y este es un enfoque malo y muy malo para el rendimiento de aplicaciones medianas o grandes. Puede verificar en la documentación oficial de Flutter que [ChangeNotifier debe usarse con 1 o un máximo de 2 listeners](https://api.Flutter.dev/Flutter/foundation/ChangeNotifier-class.html), por lo que es prácticamente inutilizable para cualquier aplicación mediana o grande.
Otros state managers son buenos, pero tienen sus matices:
- BLoC es muy seguro y eficiente, pero es muy complejo para principiantes, lo que ha impedido que las personas se desarrollen con Flutter.
- MobX es más fácil que BLoC y reactivo, casi perfecto, diría, pero necesita usar un generador de código, que para aplicaciones grandes, reduce la productividad, ya que necesitará beber muchos cafés hasta que su código esté listo nuevamente después de un `flutter clean` (¡Y esto no es culpa de MobX, sino del codegen que es realmente lento!).
- Provider usa InheritedWidget para entregar el mismo listener, como una forma de resolver el problema mencionado anteriormente con ChangeNotifier, lo que implica que cualquier acceso a su clase ChangeNotifier debe estar dentro del árbol de widgets debido al contexto necesario para acceder.
GetX no es mejor ni peor que cualquier otro gestor de estado, pero debe analizar estos puntos, así como los puntos que se mencionan a continuación, para elegir entre usar GetX en forma pura (vanilla) o usarlo junto con otro gestor de estado.
Definitivamente, GetX no es enemigo de ningún otro gestor de estado, porque GetX es más bien un microframework, no solo un gestor de estado, y se puede usar solo o en combinación con ellos.
## Gestor de Estado Simple
GetX tiene un gestor de estado que es extremadamente ligero y fácil de implementar, especialmente para aquellos nuevos en Flutter, que no utiliza ChangeNotifier, y satisface la necesidad, y no causará problemas en aplicaciones grandes.
### Ventajas
1. Actualiza solo los widgets necesarios.
2. No usa changeNotifier, es el gestor de estados que usa menos memoria (cerca de 0mb).
3. ¡Olvídate de StatefulWidget! Con GetX nunca lo necesitarás. Con los otros state managers, probablemente tendrá que usar un StatefulWidget para obtener la instancia de su Provider,BLoC,MobX Controller, etc. Pero alguna vez se detuvo para pensar que su appBar, su Scaffold y la mayoría de los widgets que están en tu clase son Stateless? Entonces, ¿por qué guardar el estado de una clase completa, si puede guardar solo el estado del widget que es Stateful? GetX también resuelve eso. Crea una clase Stateless, haz todo Stateless. Si necesita actualizar un solo componente, envuélvalo con GetBuilder, y se mantendrá su estado.
4. ¡Organiza tu proyecto de verdad! Los Controllers no deben estar en su UI, colocar su TextEditController o cualquier controller que utilice dentro de su clase Controller.
5. ¿Necesita activar un evento para actualizar un widget tan pronto como este se dibuje? GetBuilder tiene la propiedad "initState", al igual que en un StatefulWidget, y puede llamar a eventos desde su Controller, directamente desde él, sin que se coloquen más eventos en su initState.
6. ¿Necesita activar una acción como cerrar streams, timers, etc.? GetBuilder también tiene la propiedad dispose, donde puede llamar a eventos tan pronto como se destruya ese widget.
7. Use streams solo si es necesario. Puede usar sus StreamControllers dentro de su Controller normalmente, y usar StreamBuilder también normalmente, pero recuerde, un stream consume una cantidad azonablemente de memoria, la programación reactiva es hermosa, pero no debe abusar de ella. 30 streams abiertos simultáneamente pueden ser peores que un changeNotifier (y changeNotifier es muy malo).
8. Actualice los widgets sin consumir ram por eso. GetX almacena solo el creator ID de GetBuilder y lo actualiza cuando es necesario. El consumo de memoria del almacenamiento del Get ID en la memoria es muy bajo, incluso para miles de GetBuilders. Cuando crea un nuevo GetBuilder, en realidad está compartiendo el estado de GetBuilder que tiene un creator ID. No se crea un nuevo estado para cada GetBuilder, lo que ahorra MUCHA RAM para aplicaciones grandes. Básicamente, su aplicación será completamente Stateless, y los pocos Widgets que serán Stateful (dentro de GetBuilder) tendrán un solo estado y por lo tanto actualizar uno los actualizará a todos. El estado es solo uno.
9. GetX es omnisciente y, en la mayoría de los casos, sabe exactamente el momento de sacar un Controller de la memoria. No debe preocuparse por eso, GetX conoce el mejor momento para hacerlo.
### Uso
```dart
// Create controller class and extends GetXController
class Controller extends GetXController {
int counter = 0;
void increment() {
counter++;
update(); // use update() to update counter variable on UI when increment be called
}
}
// On your Stateless/Stateful class, use GetBuilder to update Text when increment be called
GetBuilder<Controller>(
init: Controller(), // INIT IT ONLY THE FIRST TIME
builder: (_) => Text(
'${_.counter}',
),
)
//Initialize your controller only the first time. The second time you are using ReBuilder for the same controller, do not use it again. Your controller will be automatically removed from memory as soon as the widget that marked it as 'init' is deployed. You don't have to worry about that, Get will do it automatically, just make sure you don't start the same controller twice.
```
**¡Listo!**
- Ya has aprendido a gestionar estados con GetX.
- Nota: es posible que desee una organización más grande y no usar la propiedad init. Para eso, puede crear una clase y extender la clase Bindings, dentro de ella mencionar los Controllers que necesita crear dentro de esa ruta. Pero los Controllers no se crearán en ese momento, por el contrario, esto será solo una declaración, por lo que, la primera vez que use un Controller, GetX sabrá dónde buscarlo. GetX seguirá siendo lazyLoad y se ocupará de eliminar los controllers cuando ya no sean necesarios. Vea el ejemplo pub.dev para ver cómo funciona.
Si navega por muchas rutas y necesita datos que estaban en su Controller utilizado previamente, solo necesita usar nuevamente GetBuilder (sin init):
```dart
class OtherClass extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: GetBuilder<Controller>(
builder: (s) => Text('${s.counter}'),
),
),
);
}
```
Si necesita usar su Controller en muchos otros lugares y fuera de GetBuilder, simplemente cree un get en su Controller y obténgalo fácilmente. (o use `Get.find <Controller>()`)
```dart
class Controller extends GetXController {
/// You do not need that. I recommend using it just for ease of syntax.
/// with static method: Controller.to.increment();
/// with no static method: Get.find<Controller>().increment();
/// There is no difference in performance, nor any side effect of using either syntax. Only one does not need the type, and the other the IDE will autocomplete it.
static Controller get to => Get.find(); // add this line
int counter = 0;
void increment() {
counter++;
update();
}
}
```
Y luego puede acceder a su Controller directamente, de esa manera:
```dart
FloatingActionButton(
onPressed: () {
Controller.to.increment(),
} // This is incredibly simple!
child: Text("${Controller.to.counter}"),
),
```
Cuando presiona FloatingActionButton, todos los widgets que escuchan la variable 'counter' se actualizarán automáticamente.
### Cómo maneja los Controllers
Digamos que tenemos esto:
`Class a => Class B (has controller X) => Class C (has controller X)`
En la clase A, el Controller aún no está en la memoria, porque aún no lo ha utilizado (GetX es lazyLoad). En la clase B usaste el Controller y entró en la memoria. En la clase C usó el mismo Controller que en la clase B, GetX compartirá el estado del Controller B con el Controller C, y el mismo Controller todavía está en la memoria. Si cierra la pantalla C y la pantalla B, GetX eliminará automáticamente Controller X de la memoria y liberará recursos, porque la clase A no está utilizando Controller. Si navega nuevamente hacia B, Controller X ingresará nuevamente a la memoria, si en lugar de ir a la clase C, regresa nuevamente a la clase A, GetX eliminará el Controller de la misma manera. Si la clase C no usó el Controller, y usted sacó la clase B de la memoria, ninguna clase estaría usando Controller X y de la misma manera se eliminaría. La única excepción que puede interferir con GetX es si elimina B de la ruta de forma inesperada e intenta utilizar el Controller en C. En este caso, se eliminó el creator ID del Controller que estaba en B y GetX se programó para eliminar de la memoria cada controller que no tiene creator ID. Si tiene la intención de hacer esto, agregue el indicador "autoRemove: false" al GetBuilder de clase B y use "adoptID = true;" en GetBuilder de la clase C.
### Ya no necesitará StatefulWidgets
Usar StatefulWidgets significa almacenar el estado de pantallas enteras innecesariamente, incluso si necesita reconstruir mínimamente un widget, lo incrustará en un Consumer/Observer/BlocProvider/GetBuilder/GetX/Obx, que será será también otro StatefulWidget.
La clase StatefulWidget es una clase más grande que StatelessWidget, que asignará más RAM, y esto puede no hacer una diferencia significativa entre una o dos clases, ¡pero sin duda lo hará cuando tenga 100 de ellas!
A menos que necesite usar un mixin, como TickerProviderStateMixin, será totalmente innecesario usar un StatefulWidget con GetX.
Puede llamar a todos los métodos de un StatefulWidget directamente desde un GetBuilder. Si necesita llamar al método initState() o dispose(), por ejemplo, puede llamarlos directamente;
```dart
GetBuilder<Controller>(
initState: (_) => Controller.to.fetchApi(),
dispose: (_) => Controller.to.closeStreams(),
builder: (s) => Text('${s.username}'),
),
```
Un enfoque mucho mejor que esto es utilizar el método onInit() y onClose() directamente desde su Controller.
```dart
@override
void onInit() {
fetchApi();
super.onInit();
}
```
- NOTA: Si desea iniciar un método en el momento en que se llama al Controller por primera vez, NO NECESITA usar constructores para esto, de hecho, usando un paquete orientado al rendimiento como GetX, esto sería casi una mala práctica, debido a que se desvía de la lógica en la que los controllers son creados o asignados (si crea una instancia de este controller, se llamará al constructor inmediatamente, completará un Controller antes de que se use, estará asignando memoria sin ser usado, esto definitivamente perjudica los principios de esta biblioteca). Los métodos onInit(); y onClose(); fueron creados para esto, se los llamará cuando se cree el controller, o se use por primera vez, dependiendo de si está utilizando GetX.lazyPut o no. Si desea, por ejemplo, hacer una llamada a su API para llenar datos, puede olvidarse del viejo método initState/dispose, simplemente inicie su llamada a la API en onInit y si necesita ejecutar algún comando como cerrar streams, use onClose() para eso.
### Por qué existe
El propósito de este paquete es precisamente brindarle una solución completa para la navegación de rutas, la gestión de dependencias y de estados, utilizando la menor cantidad de dependencias posibles, con un alto grado de desacoplamiento. GetX se acopla internamente a todas las API de Flutter de alto y bajo nivel, para garantizar que trabaje con el menor acoplamiento posible. Centralizamos todo en un solo paquete, para garantizar que no tenga ningún otro tipo de acoplamiento en su proyecto. De esa manera, puede poner solo widgets en su vista y dejar libre la parte de su equipo que trabaja con la lógica de negocios, sin depender de ningún elemento de la vista. Esto proporciona un entorno de trabajo mucho más limpio y ordenado, de modo tal que parte de su equipo trabajará solo con widgets, sin preocuparse por tener que enviar datos a su Controller, mientras la otra parte de su equipo trabajará solo con lógica de negocios, sin depender de ningún elemento de la UI.
Entonces, para simplificar esto:
No necesita llamar a métodos en initState y enviarlos por parámetro a su Controller, ni usar un constructor Controller. Para eso tiene el método onInit() que se llamará en el momento adecuado para que sus servicios sean iniciados. No necesita llamar a dispose(), dado que dispone del método onClose() que se llamará en el momento exacto en que su Controller ya no se necesita, y se eliminará de la memoria. De esa manera, deje las vistas solo para widgets, y abstenerse de incluír cualquier tipo de lógica de negocios.
No llame a un método dispose() dentro de GetXController, no hará nada, recuerde que Controller no es un widget, no debe "eliminarlo" y GetX lo eliminará de forma automática e inteligente de la memoria. Si utilizó algún stream en él y desea cerrarlo, simplemente insértelo en el método de cierre.
Ejemplo:
```dart
class Controller extends GetXController {
StreamController<User> user = StreamController<User>();
StreamController<String> name = StreamController<String>();
/// close stream = onClose method, not dispose.
@override
void onClose() {
user.close();
name.close();
super.onClose();
}
}
```
Ciclo de vida del Controller:
- onInit() donde se crea.
- onClose() donde está cerrado para cualquier tipo de modificación en preparación para ser removido.
- deleted: no tiene acceso a esta API porque literalmente el Controller está eliminando de la memoria, sin dejar rastro (literal).
### Otras formas de usarlo
Puede usar la instancia de Controller directamente en el value de GetBuilder:
```dart
GetBuilder<Controller>(
init: Controller(),
builder: (value) => Text(
'${value.counter}', //here
),
),
```
También puede necesitar una instancia de su Controller fuera de su GetBuilder, y puede usar estos enfoques para lograr esto:
```dart
class Controller extends GetXController {
static Controller get to => Get.find();
[...]
}
// on you view:
GetBuilder<Controller>(
init: Controller(), // use it only first time on each controller
builder: (_) => Text(
'${Controller.to.counter}', //here
)
),
```
o
```dart
class Controller extends GetXController {
// static Controller get to => Get.find(); // with no static get
[...]
}
// on stateful/stateless class
GetBuilder<Controller>(
init: Controller(), // use it only first time on each controller
builder: (_) => Text(
'${Get.find<Controller>().counter}', //here
),
),
```
- Puede utilizar enfoques "no canónicos" para hacer esto. Si está utilizando algún otro gestor de dependencias, como get_it, modular, etc., y solo desea entregar la instancia de Controller, puede hacer esto:
```dart
Controller controller = Controller();
[...]
GetBuilder<Controller>(
init: controller, //here
builder: (_) => Text(
'${controller.counter}', // here
),
),
```
### ID únicos
Si desea refinar el control de actualización de widgets con GetBuilder, puede asignarles ID únicos:
```dart
GetBuilder<Controller>(
id: 'text'
init: Controller(), // use it only first time on each controller
builder: (_) => Text(
'${Get.find<Controller>().counter}', //here
),
),
```
Y actualízalo de esta forma:
```dart
update(['text']);
```
También puede imponer condiciones para la actualización:
```dart
update(['text'], counter < 10);
```
GetX hace esto automáticamente y solo reconstruye el widget que usa la variable exacta que se modificó, si cambia una variable a la misma que la anterior y eso no implica un cambio de estado, GetX no reconstruirá el widget para ahorrar memoria y ciclos de CPU (ej. si se muestra 3 en pantalla y la variable cambia a 3 nuevamente, en la mayoría de los gestores de estados, esto provocará una nueva reconstrucción, pero con GetX el widget solo se reconstruirá si efectivamente su estado ha sido modificado).
## Reactivo STATE_MANAGER
La programación reactiva puede alienar a muchas personas porque se dice que es complicada. GetX convierte la programación reactiva en algo tan simple que puede ser aprendido y utilizado por aquellos que comenzaron en ese mismo momento en Flutter. No, no necesitará crear StreamControllers. Tampoco necesitará crear un StreamBuilder para cada variable. No necesitará crear una clase para cada estado. No necesitará crear un get para un valor inicial. La programación reactiva con GetX es tan fácil como usar setState (¡o incluso más fácil!).
Imaginemos que tiene una variable "name" y desea que cada vez que la modifique, todos los widgets que la usan cambien automáticamente.
Ej. esta es tu variable "name":
```dart
var name = 'Jonatas Borges';
```
Para que sea observable, solo necesita agregar ".obs" al final:
```dart
var name = 'Jonatas Borges'.obs;
```
Esto raya en lo absurdo cuando se trata de practicidad. ¿Qué hicimos, debajo del capó? Creamos un stream de Strings, asignamos el valor inicial "Jonatas Borges", advertimos a todos los widgets que usan "Jonatas Borges" que ahora pertenecen a esta variable, y cuando se modifica, ellos también lo harán. Esta es la magia de GetX, que solo Dart nos permite hacer.
De acuerdo, pero como sabemos, un widget solo se puede modificar si está dentro de una función, porque las clases estáticas no tienen el poder de "auto-change". Tendrá que crear un StreamBuilder, suscribirse para escuchar esta variable y crear una "cascada" de StreamBuilder si desea cambiar multiples variables en el mismo scope, ¿verdad?
No, no necesitas un StreamBuilder, pero tienes razón sobre las clases estáticas.
Bueno, en la vista, generalmente tenemos mucho boilerplate cuando queremos cambiar un widget específico. Con GetX también puedes olvidarte de esto. ¿StreamBuilder? ¿initialValue? ¿builder? No, solo necesitas jugar con esta variable dentro de un widget Obx.
```dart
Obx(() => Text (controller.name));
```
¿Qué necesitas memorizar? "Obx(() =>"
Simplemente está pasando ese widget a través de una función de flecha en un Obx. Obx es inteligente, y solo se cambiará si se modifica el valor de "name". Si el nombre es "John" y usted lo cambia "John", no tendrá ningún cambio en la pantalla, y Obx simplemente ignorará este cambio y no reconstruirá ningún widget, para ahorrar recursos. ¿No es increíble?
¿Qué pasa si tengo 5 variables observables dentro de un Obx? Se actualizará cuando se modifique alguna de ellos. Y si tengo 30 variables en una clase, cuando actualizo una, ¿actualizará todas las variables que están en esa clase? No, solo el widget específico que usa esa variable. Y si ametrallo mi variable observable mil millones de veces con el mismo valor, ¿me congelaré en la pantalla para reconstrucciones innecesarias? No, GetX solo actualiza la pantalla cuando la variable cambia en la pantalla, si la pantalla sigue siendo la misma, no reconstruirá nada.
### Ventajas
GetBuilder está dirigido precisamente al control de múltiples estados. Imagine que agregó 30 productos a un carrito, hace clic en eliminar uno, al mismo tiempo que se actualiza la lista, se actualiza el precio y la insignia en el carrito de compras a un número menor. Este tipo de enfoque hace que GetBuilder sea superior, porque agrupa estados y los cambia todos a la vez sin ninguna "lógica computacional" para eso. GetBuilder se creó con este tipo de situación en mente, ya que para un cambio de estado efímero, puede usar setState y no necesitaría un gestor de estado. Sin embargo, hay situaciones en las que solo desea que el widget donde una determinada variable ha sido modificada sea reconstruida, y esto es lo que GetX hace con un dominio nunca antes visto.
De esa manera, si desea un controlador individual, puede asignar un ID o usar GetX. Esto depende de usted, recordando que cuantos más widgets "individuales" tenga, más se destacará el rendimiento de GetX, mientras que el rendimiento de GetBuilder debería ser superior cuando haya un cambio múltiple de estado.
Puede usar ambos en cualquier situación, pero si desea ajustar su aplicación al máximo rendimiento posible, diría que:
- si sus variables se cambian en diferentes momentos, use GetX, porque no hay competencia para ello cuando el tema es para reconstruir solo lo que es necesario,
- si no necesita ID únicos, porque todas sus variables cambiarán cuando realice una acción, use GetBuilder, porque es un simple actualizador de estado en bloques, hecho en unas pocas líneas de código, para que haga exactamente lo que promete hacer: actualizar el estado en bloques. No hay forma de comparar RAM, CPU o cualquier otra cosa, desde un gestor de estado gigante a un simple StatefulWidget (como GetBuilder) que se actualiza cuando se llama a update(). Se hizo de una manera simple, para involucrar la menor lógica computacional, para cumplir con un solo propósito y consumiendo la menor cantidad de recursos posibles.
Si quieres un poderoso gestor de estado, puedes ir sin temor a GetX. No funciona con variables, pero fluye, todo lo que contiene son streams bajo el capó. Puede usar rxDart junto con él, porque todo es streams, puede escuchar el evento de cada "variable", porque todo en él es streams, es literalmente BLoC, más fácil que MobX, y sin generador de código o decoraciones.
Si quieres poder, GetX te ofrece el gestor de estado más avanzado que puedas tener.
GetX fue construido 100% basado en Streams, y le brinda toda la potencia de fuego que BLoC le brindó, con una instalación más fácil que al de MobX.
Sin decoraciones, puede convertir cualquier cosa en Observable con solo un ".obs".
Máximo rendimiento: además de tener un algoritmo inteligente para una reconstrucción mínima, GetX utiliza comparadores para asegurarse de que el estado realmente haya cambiado. Si experimenta algún error en su aplicación y envía un cambio de estado duplicado, GetX se asegurará de que su aplicación no se colapse.
El estado solo cambia si los valores son modificados. Esa es la principal diferencia entre GetX y usar Computed de MobX. Al unir dos observables, cuando se cambia uno, la audiencia de ese observable cambiará. Con GetX, si une dos variables (lo cual sería innecesario), GetX (similar a Observer) solo cambiará si implica un cambio de estado real.
### Uso
Tienes 3 formas de convertir una variable en observable.
El primero es usar Rx{Type}.
```dart
var count = RxString();
```
El segundo es usar Rx y escribirlo con `Rx<Type>`
```dart
var count = Rx<String>();
```
El tercer enfoque, más práctico, fácil e increíble, es simplemente agregar un .obs a su variable.
```dart
var count = 0.obs;
// or Rxint count = 0.obs;
// or Rx<int> count = 0.obs;
```
Como sabemos, Dart ahora se dirige hacia el null safety. Con eso es una buena idea, de ahora en adelante, que comience a usar sus variables siempre con un valor inicial.
Transformar una variable en observable con un valor inicial con GetX es el enfoque más simple y práctico que existe actualmente en Flutter. Literalmente agregará un ".obs" al final de su variable, y eso es todo, lo ha hecho observable, y su valor será el valor inicial, ¡esto es fantástico!
Puede agregar variables, y si desea escribir un widget que le permita obtener su controlador dentro, solo necesita usar el widget GetX en lugar de Obx
```dart
final count1 = 0.obs;
final count2 = 0.obs;
int get sum => count1.value + count2.value;
```
```dart
GetX<Controller>(
builder: (value) {
print("count 1 rebuild");
return Text('${value.count1.value}');
},
),
GetX<Controller>(
builder: (_) {
print("count 2 rebuild");
return Text('${_.count2.value}');
},
),
GetX<Controller>(
builder: (_) {
print("count 3 rebuild");
return Text('${_.sum}');
},
),
```
Si incrementamos el número de counter1, solo se reconstruyen el counter1 y el counter3, porque el counter1 ahora tiene un valor de 1 y 1 + 0 = 1, cambiando el valor de la suma.
Si cambiamos el counter2, solo se reconstruyen el counter2 y 3, porque el valor de 2 ha cambiado y el resultado de la suma ahora es 2.
Si agregamos el número 1 para counter1, que ya contiene 1, no se reconstruye ningún widget. Si agregamos un valor de 1 para el counter1 y un valor de 2 para el counter2, solo se reconstruirán 2 y 3, porque GetX además de cambiar solo lo que es necesario, evita la duplicación de eventos.
- NOTA: Por defecto, el primer evento permitirá la reconstrucción incluso si es igual. Creamos este comportamiento debido a variables dualistas, como Boolean.
Imaginemos que hiciste esto:
```dart
var isLogged = false.obs;
```
y luego verifica si un usuario ha iniciado sesión para activar un evento en "ever".
```dart
onInit(){
ever(isLogged, fireRoute);
isLogged.value = await Preferences.hasToken();
}
fireRoute(logged) {
if (logged) {
Get.off(Home());
} else {
Get.off(Login());
}
}
```
si hasToken fuera falso, no habría cambios en isLogged, por lo que nunca se llamaría. Para evitar este tipo de comportamiento, el primer cambio a un observable siempre desencadenará un evento, incluso si es el mismo.
Puede eliminar este comportamiento si lo desea, utilizando:
`isLogged.firstRebuild = false;`
Además, GetX proporciona control de estado refinado. Puede condicionar un evento (como agregar un objeto a una lista), en una determinada condición:
```dart
list.addIf(item < limit, item);
```
Sin decoraciones, sin un generador de código, sin complicaciones, GetX cambiará la forma en que administra sus estados en Flutter, y eso no es una promesa, ¡es una certeza!
¿Conoces el counter de Flutter? Su clase de controlador podría verse así:
```dart
class CountCtl extends GetxController {
final count = 0.obs;
}
```
Con un simple:
```dart
ctl.count.value++
```
Puede actualizar la variable de contador en su IU, independientemente de dónde esté almacenada.
### Donde se pueden usar .obs
Puedes transformar cualquier cosa en obs:
```dart
class RxUser {
final name = "Camila".obs;
final age = 18.obs;
}
class User {
User({String name, int age});
final rx = RxUser();
String get name => rx.name.value;
set name(String value) => rx.name.value = value;
int get age => rx.age.value;
set age(int value) => rx.age.value = value;
}
```
```dart
void main() {
final user = User();
print(user.name);
user.age = 23;
user.rx.age.listen((int age) => print(age));
user.age = 24;
user.age = 25;
}
___________
out:
Camila
23
24
25
```
### Nota sobre listas
Trabajar con listas usando GetX es lo mejor y lo más divertido del mundo. Son completamente observables como lo son los objetos dentro de él. De esa manera, si agrega un valor a una lista, reconstruirá automáticamente los widgets que lo usan.
Tampoco necesita usar ".value" con las listas, la increíble api de Dart nos permitió eliminar eso, los tipos primitivos desafortunados como String e int no se pueden extender, haciendo que el uso de .value sea obligatorio, pero eso no será un problema si trabajas con gets y setters para estos.
```dart
final list = List<User>().obs;
```
```dart
ListView.builder (
itemCount: list.length
)
```
No tiene que trabajar con Sets si no lo desea. puede usar la api "assign" y "assignAll".
La API "assign" borrará su lista y agregará un solo objeto, con el que quiere comenzar allí.
La API "assignAll" borrará la lista existente y agregará cualquier objeto iterable que le inyecte.
### ¿Por qué tengo que usar .value
Podríamos eliminar la obligación de usar 'value' para String e int con una simple decoración y generador de código, pero el propósito de esta biblioteca es, precisamente, no necesitar ninguna dependencia externa. Ofrecer un entorno listo para la programación, que incluya lo esencial (gestión de rutas, dependencias y estados), de una manera simple, ligera y de gran rendimiento sin necesidad de ningún otro paquete externo. Literalmente, agrega GetX a su pubspec y puede comenzar a programar.
Todas las soluciones incluidas por defecto, desde gestión de rutas a gestión de estádo, apuntan a la facilidad, la productividad y el rendimiento. El peso total de esta biblioteca es menor que el de un solo gestor de estado, aunque es una solución completa, y eso es lo que debe comprender.
Si le molesta el ".value" y, como además si se trata de un generador de código, MobX ya es una gran alternativa, puede simplemente usarlo junto con GetX.
Para aquellos que desean agregar una sola dependencia en pubspec y comenzar a programar sin preocuparse de que la versión de un paquete sea incompatible con otra, o si el error de una actualización de estado proviene de gestor de estado o dependencia, o aún, no quieren preocuparse por la disponibilidad de controladores, ya sea literalmente "solo programación", GetX es simplemente perfecto.
Si no tiene ningún problema con el generador de código de MobX, o no tiene ningún problema con el boilerplate de BLoC, simplemente puede usar GetX para las rutas y olvidar que incluye un gestor de estado. GetX, SEM y RSM nacieron por necesidad, mi empresa tenía un proyecto con más de 90 controladores, y el generador de código tardó más de 30 minutos en completar sus tareas después de un Flutter Clean en una máquina razonablemente buena. Si su proyecto tiene 5, 10, 15 controladores, cualquier gestor de estado te vendrá bien. Si tiene un proyecto absurdamente grande y el generador de código es un problema para usted, se le ha otorgado esta solución.
Obviamente, si alguien quiere contribuir al proyecto y crear un generador de código, o algo similar, lo añadiré a este archivo como una alternativa, mi necesidad no es la necesidad de todos los desarrolladores, pero lo que ahora digo es que hay buenas soluciones que ya hacen eso, como MobX.
### Obx()
El Typing en GetX usando Bindings es innecesario. Puede usar el widget Obx (en lugar de GetX), que solo recibe la función anónima que crea un widget.
Obviamente, si no usa un tipo, necesitará tener una instancia de su controlador para usar las variables, o usar `Get.find <Controller>()` .value o Controller.to.value para recuperar el valor.
### Workers
Los workers lo ayudarán, activando callbacks específicos cuando ocurra un evento.
```dart
/// Called every time the variable $_ is changed
ever(count1, (_) => print("$_ has been changed"));
/// Called only first time the variable $_ is changed
once(count1, (_) => print("$_ was changed once"));
/// Anti DDos - Called every time the user stops typing for 1 second, for example.
debounce(count1, (_) => print("debouce$_"), time: Duration(seconds: 1));
/// Ignore all changes within 1 second.
interval(count1, (_) => print("interval $_"), time: Duration(seconds: 1));
```
- ever: se llama cada vez que se cambia su variable. Eso es.
- once: se llama solo la primera vez que se ha cambiado la variable.
- debounce: es muy útil en las funciones de búsqueda, donde solo desea que se llame a la API cuando el usuario termina de escribir. Si el usuario escribe "JONNY", tendrá 5 búsquedas en las API, por la letra J, O, N, N e Y. Con GetX esto no sucede, porque tendrá un worker "debounce" que solo se activará al final de la escritura.
- interval: es diferente del debouce. Con debouce si el usuario realiza 1000 cambios en una variable dentro de 1 segundo, enviará solo el último después del timer estipulado (el valor predeterminado es 800 milisegundos). En cambio, el interval ignorará todas las acciones del usuario durante el período estipulado. Si envía eventos durante 1 minuto, 1000 por segundo, la función antirrebote solo le enviará el último, cuando el usuario deje de enviar eventos. Interval entregará eventos cada segundo, y si se establece en 3 segundos, entregará 20 eventos ese minuto. Esto se recomienda para evitar abusos, en funciones en las que el usuario puede hacer clic rápidamente en algo y obtener alguna ventaja (imagine que el usuario puede ganar monedas haciendo clic en algo, si hace clic 300 veces en el mismo minuto, tendría 300 monedas, usando el interval, puede establecer un time frame de 3 segundos, e incluso luego hacer clic 300 o mil veces, el máximo que obtendría en 1 minuto sería 20 monedas, haciendo clic 300 o 1 millón de veces). El debouce es adecuado para anti-DDos, para funciones como la búsqueda donde cada cambio en onChange provocaría una consulta en su API. Debounce esperará a que el usuario deje de escribir el nombre para realizar la solicitud. Si se usara en el escenario de monedas mencionado anteriormente, el usuario solo ganaría 1 moneda, ya que solo se ejecuta cuando el usuario "hace una pausa" durante el tiempo establecido.
- NOTE: Los Workers siempre deben usarse al iniciar un controlador o clase, por lo que siempre debe estar en onInit (recomendado), Class Constructor o initState de un StatefulWidget (esta práctica no se recomienda en la mayoría de los casos, pero no debería tener efectos secundarios).
## Mezclando los dos State Managers
Algunas personas abrieron una feature request, ya que querían usar solo un tipo de variable reactiva, y la otra mecánica, y necesitaban insertar un Obx en un GetBuilder para esto. Pensando en ello, se creó MixinBuilder. Permite cambios reactivos cambiando las variables ".obs" y actualizaciones mecánicas a través de update(). Sin embargo, de los 4 widgets, es el que consume más recursos, ya que además de tener una suscripción para recibir eventos de cambio de sus hijos, se suscribe al método update de su controlador.
Extender GetxController es importante, ya que tienen ciclos de vida y pueden "iniciar" y "finalizar" eventos en sus métodos onInit() y onClose(). Puede usar cualquier clase para esto, pero le recomiendo que use la clase GetxController para colocar sus variables, sean observables o no.
## GetBuilder vs GetX vs Obx vs MixinBuilder
En una década trabajando con programación pude aprender algunas lecciones valiosas.
Mi primer contacto con la programación reactiva fue tan "guau, esto es increíble" y, de hecho, la programación reactiva es increíble.
Sin embargo, no es adecuado para todas las situaciones. A menudo, todo lo que necesita es cambiar el estado de 2 o 3 widgets al mismo tiempo, o un cambio de estado efímero, en cuyo caso la programación reactiva no es mala, pero no es apropiada.
La programación reactiva tiene un mayor consumo de RAM que se puede compensar con el workflow individual, lo que garantizará que solo se reconstruya un widget y cuando sea necesario, pero crear una lista con 80 objetos, cada uno con varios streams no es una buena idea. Abra el dart inspector y compruebe cuánto consume un StreamBuilder, y comprenderá lo que estoy tratando de decirle.
Con eso en mente, creé el Simple State Manager. Es simple, y eso es exactamente lo que debe exigirle: actualizar el estado en bloques de una manera simple y de la manera más económica.
GetBuilder es muy económico en RAM, y es difícil que haya un enfoque más económico que él (al menos no puedo imaginar uno, si existe, háganoslo saber).
Sin embargo, GetBuilder sigue siendo un gestor de estado mecánico, debe llamar a update() al igual que necesitaría llamar a notifyListeners() con Provider.
Hay otras situaciones, en las que la programación reactiva es realmente interesante, y no trabajar con ella es lo mismo que reinventar la rueda. Con eso en mente, GetX fue creado para proporcionar todo lo más moderno y avanzado en un gestor de estado. Actualiza solo lo necesario y solo si es necesario, si tiene un error y envía 300 cambios de estado simultáneamente, GetX lo filtrará y actualizará la pantalla solo si el estado realmente fue modificado.
GetX es aún más económico que cualquier otro gestor de estado reactivo, pero consume un poco más de RAM que GetBuilder. Pensando en ello y con el objetivo de maximizar el consumo de recursos es que se creó Obx. A diferencia de GetX y GetBuilder, no podrá inicializar un controlador dentro de un Obx, es solo un Widget con una stream suscription que recibe eventos de cambio de sus children, eso es todo. Es más económico que GetX, pero pierde con GetBuilder, lo que era de esperar, ya que es reactivo, y GetBuilder tiene el enfoque más simplista que existe, de almacenar el código hash de un widget y su StateSetter. Con Obx no necesita escribir su tipo de controlador, y puede escuchar el cambio desde varios controladores diferentes, pero debe inicializarse antes, ya sea utilizando el enfoque de ejemplo al comienzo de este archivo o utilizando la clase Bindings.

View File

@@ -0,0 +1,383 @@
# Gestion des dépendances
- [Gestion des dépendances](#Gestion-des-dépendances)
- [Instanciation des methodes](#Instanciation-des-methodes)
- [Get.put()](#getput)
- [Get.lazyPut](#getlazyput)
- [Get.putAsync](#getputasync)
- [Get.create](#getcreate)
- [Utilisation de méthodes/classes instanciées](#utilisation-de-mthodes-classes-instancies)
- [Différences entre les méthodes](#differences-entre-les-methodes)
- [Bindings](#bindings)
- [Classe Bindings](#classe-bindings)
- [BindingsBuilder](#bindingsbuilder)
- [SmartManagement](#smartmanagement)
- [Comment changer](#comment-changer)
- [SmartManagement.full](#smartmanagementfull)
- [SmartManagement.onlyBuilders](#smartmanagementonlybuilders)
- [SmartManagement.keepFactory](#smartmanagementkeepfactory)
- [Comment Bindings fonctionne sous le capot](#comment-bindings-fonctionne-sous-le-capot)
- [Notes](#notes)
Get a un gestionnaire de dépendances simple et puissant qui vous permet de récupérer la même classe que votre Bloc ou Controller avec une seule ligne de code, pas de context Provider, pas d' inheritedWidget:
```dart
Controller controller = Get.put(Controller()); // Au lieu de Controller controller = Controller();
```
Au lieu d'instancier votre classe dans la classe que vous utilisez, vous l'instanciez dans l'instance Get, qui la rendra disponible dans toute votre application.
Vous pouvez donc utiliser votre contrôleur (ou classe Bloc) normalement
- Note: Si vous utilisez le gestionnaire d'état de Get, faites plus attention à l'API [Bindings] (# bindings), qui facilitera la connexion de votre vue à votre contrôleur.
- Note²: La gestion des dépendances est découplée des autres parties du package, donc si, par exemple, votre application utilise déjà un gestionnaire d'état (n'importe lequel, peu importe), vous n'avez pas besoin de changer cela, vous pouvez utiliser ce manager d'injection de dépendance sans aucun problème.
## Instanciation des methodes
Les méthodes et leurs paramètres configurables sont:
### Get.put()
La manière la plus courante d'insérer une dépendance. Bon pour les contrôleurs de vos vues par exemple.
```dart
Get.put<SomeClass>(SomeClass());
Get.put<LoginController>(LoginController(), permanent: true);
Get.put<ListItemController>(ListItemController, tag: "un String unique");
```
Ce sont toutes les options que vous pouvez définir lorsque vous utilisez put:
```dart
Get.put<S>(
// obligatoire: la classe que vous voulez que get enregistre, comme un 'controler' ou autre
// note: "S" signifie que ca peut etre une classe de n'importe quel type
S dependency
// optionnel: c'est pour quand vous voulez plusieurs classes qui sont du même type
// puisque vous obtenez normalement une classe en utilisant Get.find<Controller>(),
// vous devez utiliser ce tag pour indiquer de quelle instance vous avez besoin
// doit être un String unique
String tag,
// optionnel: par défaut, get supprimera les instances une fois qu'elles ne seront plus utilisées (exemple,
// le contrôleur d'une vue qui est fermée), mais vous pourriez avoir besoin que l'instance
// soit conservée dans toute l'application, comme une instance de sharedPreferences ou quelque chose du genre
// donc vous utilisez ceci
// équivaut à false par défaut
bool permanent = false,
// facultatif: permet après avoir utilisé une classe abstraite dans un test, de la remplacer par une autre et de suivre le test.
// équivaut à false par défaut
bool overrideAbstract = false,
// facultatif: vous permet de créer la dépendance en utilisant la fonction au lieu de la dépendance elle-même.
// ce n'est pas couramment utilisé
InstanceBuilderCallback<S> builder,
)
```
### Get.lazyPut
Il est possible de lazyLoad une dépendance afin qu'elle ne soit instanciée que lorsqu'elle est utilisée. Très utile pour les classes qui demandent beaucoup de ressources ou si vous souhaitez instancier plusieurs classes en un seul endroit (comme dans une classe Bindings) et que vous savez que vous n'utiliserez pas cette classe à ce moment-là.
```dart
/// ApiMock ne sera appelé que lorsque quelqu'un utilise Get.find <ApiMock> pour la première fois
Get.lazyPut<ApiMock>(() => ApiMock());
Get.lazyPut<FirebaseAuth>(
() {
// ... some logic if needed
return FirebaseAuth();
},
tag: Math.random().toString(),
fenix: true
)
Get.lazyPut<Controller>(() => Controller() )
```
Ce sont toutes les options que vous pouvez définir lors de l'utilisation de lazyPut:
```dart
Get.lazyPut<S>(
// obligatoire: une méthode qui sera exécutée lorsque votre classe sera appelée pour la première fois
InstanceBuilderCallback builder,
// facultatif: identique à Get.put(), il est utilisé lorsque vous voulez plusieurs instances différentes d'une même classe
// doit être unique
String tag,
// facultatif: cela est similaire à "permanent", la différence est que l'instance est supprimée lorsqu'elle
// n'est pas utilisée, mais lorsqu'elle est à nouveau nécessaire, Get recrée l'instance
// identique à "SmartManagement.keepFactory" dans l'API Bindings
// vaut false par défaut
bool fenix = false
)
```
### Get.putAsync
Si vous souhaitez enregistrer une instance async, vous pouvez utiliser `Get.putAsync`:
```dart
Get.putAsync<SharedPreferences>(() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setInt('counter', 12345);
return prefs;
});
Get.putAsync<YourAsyncClass>(() async => await YourAsyncClass())
```
Ce sont toutes les options que vous pouvez définir lors de l'utilisation de putAsync:
```dart
Get.putAsync<S>(
// obligatoire: une méthode async qui sera exécutée pour instancier votre classe
AsyncInstanceBuilderCallback<S> builder,
// facultatif: identique à Get.put(), il est utilisé lorsque vous voulez plusieurs instances différentes d'une même classe
// doit être unique
String tag,
// facultatif: identique à Get.put(), utilisé lorsque vous devez maintenir cette instance active dans l'ensemble de l'application
// vaut false par défaut
bool permanent = false
)
```
### Get.create
Celui-ci est délicat. Une explication détaillée de ce que c'est et des différences d'avec les autres peut être trouvée dans la section [Différences entre les méthodes:](#differences-entre-les-methodes).
```dart
Get.Create<SomeClass>(() => SomeClass());
Get.Create<LoginController>(() => LoginController());
```
Ce sont toutes les options que vous pouvez définir lors de l'utilisation de create:
```dart
Get.create<S>(
// requis: une fonction qui renvoie une classe qui sera "fabriquée" chaque
// fois que `Get.find()` est appelé
// Exemple: Get.create<YourClass>(() => YourClass())
FcBuilderFunc<S> builder,
// facultatif: comme Get.put(), mais il est utilisé lorsque vous avez besoin de plusieurs instances
// d'une même classe
// Utile dans le cas où vous avez une liste oú chaque élément a besoin de son propre contrôleur
// doit être une String unique. Changez simplement de 'tag' en 'name'
String name,
// optionnel: tout comme dans `Get.put()`, c'est pour quand vous devez garder l'
// instance vivante dans toute l'application. La différence est que dans Get.create,
// permanent est 'true' par défaut
bool permanent = true
```
## Utilisation de méthodes/classes instanciées
Imaginez que vous ayez parcouru de nombreuses routes et que vous ayez besoin d'une donnée qui a été laissée dans votre contrôleur, vous auriez besoin d'un gestionnaire d'état combiné avec le 'Provider' ou Get_it, n'est-ce pas? Pas avec Get. Il vous suffit de demander à Get de "find" (trouver) votre contrôleur, vous n'avez pas besoin de dépendances supplémentaires:
```dart
final controller = Get.find<Controller>();
// OR
Controller controller = Get.find();
// Oui, cela ressemble à Magic, Get trouvera votre contrôleur et vous le livrera.
// Vous pouvez avoir 1 million de contrôleurs instanciés, Get vous trouvera toujours le bon contrôleur.
```
Et puis vous pourrez récupérer les données de votre contrôleur qui ont été obtenues là-bas:
```dart
Text(controller.textFromApi);
```
La valeur renvoyée étant une classe normale, vous pouvez faire tout ce que vous voulez:
```dart
int count = Get.find<SharedPreferences>().getInt('counter');
print(count); // donne: 12345
```
Pour supprimer une instance de Get:
```dart
Get.delete<Controller>(); //généralement, vous n'avez pas besoin de le faire car GetX supprime déjà les contrôleurs inutilisés-
```
## Differences entre les methodes
Commençons par le `fenix` de Get.lazyPut et le `permanent` des autres méthodes.
La différence fondamentale entre `permanent` et `fenix` réside dans la manière dont vous souhaitez stocker vos instances.
Renforcement: par défaut, GetX supprime les instances lorsqu'elles ne sont pas utilisées.
Cela signifie que: Si l'écran 1 a le contrôleur 1 et l'écran 2 a le contrôleur 2 et que vous supprimez la première route du Stack, (comme si vous utilisez `Get.off()` ou `Get.offNamed()`) le contrôleur 1 a perdu son utilisation, il sera donc effacé.
Mais si vous optez pour l'utilisation de `permanent: true`, alors le contrôleur ne sera pas perdu dans cette transition - ce qui est très utile pour les services que vous souhaitez maintenir actif dans toute l'application.
`fenix`, quant à lui, est destiné aux services que vous ne craignez pas de perdre entre les changements d'écran, mais lorsque vous avez besoin de ce service, vous vous attendez à ce qu'il soit vivant. Donc, fondamentalement, il supprimera le contrôleur / service / classe inutilisé, mais lorsque vous en aurez besoin, il "recréera à partir de ses cendres" une nouvelle instance.
Différences entre les méthodes:
- Get.put et Get.putAsync suivent le même ordre de création, à la différence que la seconde utilise une méthode asynchrone: ces deux méthodes créent et initialisent l'instance. Celle-ci est insérée directement dans la mémoire, en utilisant la méthode interne `insert` avec les paramètres` permanent: false` et `isSingleton: true` (ce paramètre isSingleton a pour seul but de dire s'il faut utiliser la dépendance sur` dependency` ou s'il doit utiliser la dépendance sur `FcBuilderFunc`). Après cela, `Get.find()` est appelé pour initialiser immédiatement les instances qui sont en mémoire.
- Get.create: Comme son nom l'indique, il "créera" votre dépendance! Similaire à `Get.put()`, il appelle également la méthode interne `insert` pour l'instanciation. Mais `permanent` devient vrai et` isSingleton` devient faux (puisque nous "créons" notre dépendance, il n'y a aucun moyen pour que ce soit une instance singleton, c'est pourquoi il est faux). Et comme il a `permanent: true`, nous avons par défaut l'avantage de ne pas le perdre entre les écrans! De plus, `Get.find()` n'est pas appelé immédiatement, il attend d'être utilisé dans l'écran pour être appelé. Il est créé de cette façon pour utiliser le paramètre `permanent`, depuis lors, il convient de noter que` Get.create() `a été créé dans le but de créer des instances non partagées, mais qui ne sont pas supprimées, comme par exemple un bouton dans un listView, pour lequel vous voulez une instance unique pour cette liste - à cause de cela, Get.create doit être utilisé avec GetWidget.
- Get.lazyPut: Comme son nom l'indique, il s'agit d'un processus 'paresseux'. L'instance est créée, mais elle n'est pas appelée pour être utilisée immédiatement, elle reste en attente d'être appelée. Contrairement aux autres méthodes, `insert` n'est pas appelé ici. Au lieu de cela, l'instance est insérée dans une autre partie de la mémoire, une partie chargée de dire si l'instance peut être recréée ou non, appelons-la "factory". Si nous voulons créer quelque chose pour être utilisé plus tard, il ne sera pas mélangé avec les choses actuellement utilisées. Et voici où la magie de `fenix` apparaît: si vous choisissez de laisser` fenix: false`, et que votre `smartManagement` n'est pas` keepFactory`, alors lors de l'utilisation de `Get.find`, l'instance changera la place dans la mémoire de la "factory" à la zone de mémoire d'instance commune. Juste après cela, par défaut, il est retiré de `la factory`. Maintenant, si vous optez pour `fenix: true`, l'instance continue d'exister dans cette partie dédiée, allant même vers la zone commune, pour être appelée à nouveau dans le futur.
## Bindings
L'une des grandes différences de ce package, peut-être, est la possibilité d'une intégration complète des routes, du gestionnaire d'état et du gestionnaire de dépendances.
Lorsqu'une route est supprimée de la pile, tous les contrôleurs, variables et instances d'objets qui lui sont associés sont supprimés de la mémoire. Si vous utilisez des streams ou timers, ils seront fermés automatiquement et vous n'aurez à vous soucier de rien de tout cela.
Dans la version 2.10, Get a complètement implémenté l'API Bindings.
Vous n'avez plus besoin d'utiliser la méthode init. Vous n'avez même pas besoin de `typer`(declaration de type) vos contrôleurs si vous ne le souhaitez pas. Vous pouvez démarrer vos contrôleurs et services à l'endroit approprié pour cela.
La classe Binding est une classe qui découplera l'injection de dépendances, en faisant du "binding" des routes entre le gestionnaire d'état et le gestionnaire de dépendances.
Cela permet à Get de savoir quel écran est affiché lorsqu'un contrôleur particulier est utilisé et de savoir où et comment s'en débarrasser.
De plus, la classe Binding vous permettra d'avoir le contrôle de la configuration de SmartManager. Vous pouvez configurer les dépendances à organiser lors de la suppression d'une route du Stack, ou lorsque le widget qui l'utilisait est disposé, ou ni l'un ni l'autre. Vous disposerez d'une gestion intelligente des dépendances qui fonctionnera pour vous, mais vous pourrez malgré tout la configurer comme vous le souhaitez.
### Classe Bindings
- Créer une classe et implémenter Bindings
```dart
class HomeBinding implements Bindings {}
```
Votre IDE vous demandera automatiquement de remplacer la méthode "dependencies", et il vous suffit de cliquer sur la lampe, de remplacer la méthode et d'insérer toutes les classes que vous allez utiliser sur cette route:
```dart
class HomeBinding implements Bindings {
@override
void dependencies() {
Get.lazyPut<HomeController>(() => HomeController());
Get.put<Service>(()=> Api());
}
}
class DetailsBinding implements Bindings {
@override
void dependencies() {
Get.lazyPut<DetailsController>(() => DetailsController());
}
}
```
Il vous suffit maintenant d'informer votre route, que vous utiliserez ce Binding pour établir la connexion entre le gestionnaire de routes, les dépendances et les états.
- En utilisant les routes nommées:
```dart
getPages: [
GetPage(
name: '/',
page: () => HomeView(),
binding: HomeBinding(),
),
GetPage(
name: '/details',
page: () => DetailsView(),
binding: DetailsBinding(),
),
];
```
- En utilisant les routes normales:
```dart
Get.to(Home(), binding: HomeBinding());
Get.to(DetailsView(), binding: DetailsBinding())
```
Là, vous n'avez plus à vous soucier de la gestion de la mémoire de votre application, Get le fera pour vous.
La classe Binding est appelée lorsqu'une route est appelée, vous pouvez créer un "initialBinding dans votre GetMaterialApp pour insérer toutes les dépendances qui seront créées.
```dart
GetMaterialApp(
initialBinding: SampleBind(),
home: Home(),
);
```
### BindingsBuilder
La manière par défaut de créer un binding est de créer une classe qui implémente Bindings.
Mais alternativement, vous pouvez utiliser le callback `BindingsBuilder` afin de pouvoir simplement utiliser une fonction pour instancier ce que vous désirez.
Exemple:
```dart
getPages: [
GetPage(
name: '/',
page: () => HomeView(),
binding: BindingsBuilder(() {
Get.lazyPut<ControllerX>(() => ControllerX());
Get.put<Service>(()=> Api());
}),
),
GetPage(
name: '/details',
page: () => DetailsView(),
binding: BindingsBuilder(() {
Get.lazyPut<DetailsController>(() => DetailsController());
}),
),
];
```
De cette façon, vous pouvez éviter de créer une classe Binding pour chaque route, ce qui est encore plus simple.
Les deux méthodes fonctionnent parfaitement bien et nous voulons que vous utilisiez ce qui correspond le mieux à vos goûts.
### SmartManagement
GetX par défaut supprime les contrôleurs inutilisés de la mémoire, même si un échec se produit et qu'un widget qui l'utilise n'est pas correctement supprimé.
C'est ce qu'on appelle le mode `full` de gestion des dépendances.
Mais si vous voulez changer la façon dont GetX contrôle la suppression des classes, vous avez la classe `SmartManagement` pour définir différents comportements.
#### Comment changer
Si vous souhaitez modifier cette configuration (dont vous n'avez généralement pas besoin), procédez comme suit:
```dart
void main () {
runApp(
GetMaterialApp(
smartManagement: SmartManagement.onlyBuilders //Ici
home: Home(),
)
)
}
```
#### SmartManagement.full
C'est celui par défaut. Supprime les classes qui ne sont pas utilisées et qui n'ont pas été définies pour être permanentes. Dans la majorité des cas, vous voudrez garder cette configuration intacte. Si vous débutez avec GetX, ne changez pas cela.
#### SmartManagement.onlyBuilders
Avec cette option, seuls les contrôleurs démarrés dans `init:` ou chargés dans un Binding avec `Get.lazyPut()` seront supprimés.
Si vous utilisez `Get.put()` ou `Get.putAsync()` ou toute autre approche, SmartManagement n'aura pas les autorisations pour exclure cette dépendance.
Avec le comportement par défaut, même les widgets instanciés avec "Get.put" seront supprimés, contrairement à SmartManagement.onlyBuilders.
#### SmartManagement.keepFactory
Tout comme SmartManagement.full, il supprimera ses dépendances lorsqu'elles ne seront plus utilisées. Cependant, il conservera leur factory, ce qui signifie qu'il recréera la dépendance si vous avez à nouveau besoin de cette instance.
### Comment Bindings fonctionne sous le capot
Bindings crée des `'factories' transitoires`, qui sont créées au moment où vous cliquez pour aller à un autre écran, et seront détruites dès que l'animation de changement d'écran se produit.
Cela arrive si vite que l'analyseur ne pourra même pas l'enregistrer.
Lorsque vous accédez à nouveau à cet écran, une nouvelle fabrique temporaire sera appelée, c'est donc préférable à l'utilisation de SmartManagement.keepFactory, mais si vous ne voulez pas créer de Bindings, ou si vous voulez garder toutes vos dépendances sur le même Binding , cela vous aidera certainement.
Les factories prennent peu de mémoire, elles ne contiennent pas d'instances, mais une fonction avec la "forme" de cette classe que vous voulez.
Cela a un très faible coût en mémoire, mais comme le but de cette bibliothèque est d'obtenir le maximum de performances possible en utilisant le minimum de ressources, Get supprime même les factories par défaut.
Utilisez celui qui vous convient le mieux.
## Notes
- N'UTILISEZ PAS SmartManagement.keepFactory si vous utilisez plusieurs Bindings. Il a été conçu pour être utilisé sans Bindings ou avec une seule Binding liée dans le fichier initialBinding de GetMaterialApp.
- L'utilisation de Bindings est complètement facultative, si vous le souhaitez, vous pouvez utiliser `Get.put()` et `Get.find()` sur les classes qui utilisent un contrôleur donné sans aucun problème.
Cependant, si vous travaillez avec des services ou toute autre abstraction, je vous recommande d'utiliser Bindings pour une meilleure organisation.

View File

@@ -0,0 +1,560 @@
- [Gestion de route](#gestion-de-route)
- [Utilisation](#utilisation)
- [Navigation sans nom](#navigation-sans-nom)
- [Navigation par nom](#navigation-par-nom)
- [Envoyer des données aux routes nommées](#envoyer-des-donnes-aux-routes-nommes)
- [Liens URL dynamiques](#liens-url-dynamiques)
- [Middleware](#middleware)
- [Navigation sans context](#navigation-sans-context)
- [SnackBars](#snackbars)
- [Dialogs](#dialogs)
- [BottomSheets](#bottomsheets)
- [Nested Navigation](#nested-navigation)
# Gestion de route
C'est l'explication complète de tout ce qu'il y a à savoir sur Getx quand il s'agit de la gestion des routes.
## Utilisation
Ajoutez ceci à votre fichier pubspec.yaml:
```yaml
dependencies:
get:
```
Si vous allez utiliser des routes/snackbars/dialogs/bottomsheets sans contexte, ou utiliser les API Get de haut niveau, vous devez simplement ajouter "Get" avant votre MaterialApp, en le transformant en GetMaterialApp et en profiter!
```dart
GetMaterialApp( // Avant: MaterialApp(
home: MyHome(),
)
```
## Navigation sans nom
Pour accéder à un nouvel écran:
```dart
Get.to(NextScreen());
```
Pour fermer les snackbars, dialogs, bottomsheets ou tout ce que vous fermez normalement avec Navigator.pop(context);
```dart
Get.back();
```
Pour aller à l'écran suivant et aucune option pour revenir à l'écran précédent (pour une utilisation dans SplashScreens, écrans de connexion, etc.)
```dart
Get.off(NextScreen());
```
Pour aller à l'écran suivant et annuler toutes les routes précédents (utile dans les paniers d'achat e-commerce, les sondages et les tests)
```dart
Get.offAll(NextScreen());
```
Pour naviguer vers l'écran suivant et recevoir ou mettre à jour des données dès que vous en revenez:
```dart
var data = await Get.to(Payment());
```
sur l'autre écran, envoyez les données pour l'écran précédent:
```dart
Get.back(result: 'success');
```
Et utilisez-les:
ex:
```dart
if(data == 'success') madeAnything();
```
Vous ne voulez pas apprendre notre syntaxe?
Changez simplement le Navigateur (majuscule) en navigateur (minuscule), et vous aurez toutes les fonctions de la navigation standard, sans avoir à utiliser 'context'.
Exemple:
```dart
// Navigateur Flutter par défaut
Navigator.of(context).push(
context,
MaterialPageRoute(
builder: (BuildContext context) {
return HomePage();
},
),
);
// Utilisez la syntaxe Flutter sans avoir besoin de 'context'
navigator.push(
MaterialPageRoute(
builder: (_) {
return HomePage();
},
),
);
// Syntaxe Get (c'est beaucoup mieux, mais vous avez le droit d'être en désaccord)
Get.to(HomePage());
```
## Navigation Par Nom
- Si vous préférez naviguer par namedRoutes, Get prend également en charge cela.
Pour aller à nextScreen
```dart
Get.toNamed("/NextScreen");
```
Pour naviguer et supprimer l'écran précédent du stack.
```dart
Get.offNamed("/NextScreen");
```
Pour naviguer et supprimer tous les écrans précédents du stack.
```dart
Get.offAllNamed("/NextScreen");
```
Pour définir des routes, utilisez GetMaterialApp:
```dart
void main() {
runApp(
GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => MyHomePage()),
GetPage(name: '/second', page: () => Second()),
GetPage(
name: '/third',
page: () => Third(),
transition: Transition.zoom
),
],
)
);
}
```
Pour gérer la navigation vers des routes non définies (erreur 404), vous pouvez définir une page 'unknownRoute' dans GetMaterialApp.
```dart
void main() {
runApp(
GetMaterialApp(
unknownRoute: GetPage(name: '/notfound', page: () => UnknownRoutePage()),
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => MyHomePage()),
GetPage(name: '/second', page: () => Second()),
],
)
);
}
```
### Envoyer des données aux routes nommées
Envoyez simplement ce que vous voulez comme arguments. Get accepte n'importe quoi ici, qu'il s'agisse d'une String, d'une Map, d'une List ou même d'une instance de classe.
```dart
Get.toNamed("/NextScreen", arguments: 'Get is the best');
```
dans votre classe ou contrôleur:
```dart
print(Get.arguments);
//montre: Get is the best
```
### Liens URL dynamiques
Get propose des URL dynamiques avancées, tout comme sur le Web. Les développeurs Web ont probablement déjà voulu cette fonctionnalité sur Flutter, et ont très probablement vu un package promettre cette fonctionnalité et fournir une syntaxe totalement différente de celle d'une URL sur le Web, mais Get résout également cela.
```dart
Get.offAllNamed("/NextScreen?device=phone&id=354&name=Enzo");
```
sur votre classe controller/bloc/stateful/stateless:
```dart
print(Get.parameters['id']);
// donne: 354
print(Get.parameters['name']);
// donne: Enzo
```
Vous pouvez également recevoir facilement des paramètres nommés avec Get:
```dart
void main() {
runApp(
GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(
name: '/',
page: () => MyHomePage(),
),
GetPage(
name: '/profile/',
page: () => MyProfile(),
),
//Vous pouvez définir une page différente pour les routes avec arguments, et une autre sans arguments, mais pour cela vous devez utiliser la barre oblique '/' sur la route qui ne recevra pas d'arguments comme ci-dessus.
GetPage(
name: '/profile/:user',
page: () => UserProfile(),
),
GetPage(
name: '/third',
page: () => Third(),
transition: Transition.cupertino
),
],
)
);
}
```
Envoyer des données sur le nom de la route
```dart
Get.toNamed("/profile/34954");
```
Sur le deuxième écran, recevez les données par paramètre
```dart
print(Get.parameters['user']);
// donne: 34954
```
ou envoyer plusieurs paramètres comme celui-ci
```dart
Get.toNamed("/profile/34954?flag=true");
```
Sur le deuxième écran, prenez les données par paramètres comme d'habitude
```dart
print(Get.parameters['user']);
print(Get.parameters['flag']);
// donne: 34954 true
```
Et maintenant, tout ce que vous avez à faire est d'utiliser Get.toNamed() pour parcourir vos routes nommées, sans aucun contexte (vous pouvez appeler vos routes directement à partir de votre classe BLoC ou Controller), et lorsque votre application est compilée sur le Web, vos routes apparaîtront dans l'url <3
### Middleware
Si vous souhaitez écouter les événements Get pour déclencher des actions, vous pouvez utiliser routingCallback pour le faire:
```dart
GetMaterialApp(
routingCallback: (routing) {
if(routing.current == '/second'){
openAds();
}
}
)
```
Si vous n'utilisez pas GetMaterialApp, vous pouvez utiliser l'API manuelle pour attacher l'observateur Middleware.
```dart
void main() {
runApp(
MaterialApp(
onGenerateRoute: Router.generateRoute,
initialRoute: "/",
navigatorKey: Get.key,
navigatorObservers: [
GetObserver(MiddleWare.observer), // ICI !!!
],
),
);
}
```
Créez une classe MiddleWare
```dart
class MiddleWare {
static observer(Routing routing) {
/// Vous pouvez écouter en plus des routes, des snackbars, des dialogs et des bottomsheets sur chaque écran.
/// Si vous devez saisir l'un de ces 3 événements directement ici,
/// vous devez spécifier que l'événement est != Ce que vous essayez de faire.
if (routing.current == '/second' && !routing.isSnackbar) {
Get.snackbar("Hi", "You are on second route");
} else if (routing.current =='/third'){
print('dernière route');
}
}
}
```
Maintenant, utilisez Get sur votre code:
```dart
class First extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.add),
onPressed: () {
Get.snackbar("hi", "i am a modern snackbar");
},
),
title: Text('First Route'),
),
body: Center(
child: ElevatedButton(
child: Text('Open route'),
onPressed: () {
Get.toNamed("/second");
},
),
),
);
}
}
class Second extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.add),
onPressed: () {
Get.snackbar("hi", "i am a modern snackbar");
},
),
title: Text('second Route'),
),
body: Center(
child: ElevatedButton(
child: Text('Open route'),
onPressed: () {
Get.toNamed("/third");
},
),
),
);
}
}
class Third extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Third Route"),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Get.back();
},
child: Text('Go back!'),
),
),
);
}
}
```
## Navigation sans context
### SnackBars
Pour avoir un simple SnackBar avec Flutter, vous devez obtenir le 'context' de Scaffold, ou vous devez utiliser un GlobalKey attaché à votre Scaffold
```dart
final snackBar = SnackBar(
content: Text('Hi!'),
action: SnackBarAction(
label: 'I am a old and ugly snackbar :(',
onPressed: (){}
),
);
// Trouvez le scaffold dans l'arborescence des widgets et utilisez-le pour afficher un SnackBar.
Scaffold.of(context).showSnackBar(snackBar);
```
Avec Get:
```dart
Get.snackbar('Hi', 'i am a modern snackbar');
```
Avec Get, tout ce que vous avez à faire est d'appeler votre Get.snackbar à partir de n'importe où dans votre code ou de le personnaliser comme vous le souhaitez!
```dart
Get.snackbar(
"Hey i'm a Get SnackBar!", // title
"C'est incroyable! J'utilise SnackBar sans context, sans code standard, sans Scaffold, c'est quelque chose de vraiment incroyable!", // message
icon: Icon(Icons.alarm),
shouldIconPulse: true,
onTap:(){},
barBlur: 20,
isDismissible: true,
duration: Duration(seconds: 3),
);
////////// TOUTES LES FONCTIONNALITÉS //////////
// Color colorText,
// Duration duration,
// SnackPosition snackPosition,
// Widget titleText,
// Widget messageText,
// bool instantInit,
// Widget icon,
// bool shouldIconPulse,
// double maxWidth,
// EdgeInsets margin,
// EdgeInsets padding,
// double borderRadius,
// Color borderColor,
// double borderWidth,
// Color backgroundColor,
// Color leftBarIndicatorColor,
// List<BoxShadow> boxShadows,
// Gradient backgroundGradient,
// TextButton mainButton,
// OnTap onTap,
// bool isDismissible,
// bool showProgressIndicator,
// AnimationController progressIndicatorController,
// Color progressIndicatorBackgroundColor,
// Animation<Color> progressIndicatorValueColor,
// SnackStyle snackStyle,
// Curve forwardAnimationCurve,
// Curve reverseAnimationCurve,
// Duration animationDuration,
// double barBlur,
// double overlayBlur,
// Color overlayColor,
// Form userInputForm
///////////////////////////////////
```
Si vous préférez le snack-bar traditionnel, ou souhaitez le personnaliser à partir de zéro, y compris en ajoutant une seule ligne (Get.snackbar utilise un titre et un message obligatoires), vous pouvez utiliser
`Get.rawSnackbar ();` qui fournit l'API brute sur laquelle Get.snackbar a été construit.
### Dialogs
Pour ouvrir un 'dialog':
```dart
Get.dialog(VotreDialogWidget());
```
Pour ouvrir le 'dialog' par défaut:
```dart
Get.defaultDialog(
onConfirm: () => print("Ok"),
middleText: "Dialog made in 3 lines of code"
);
```
Vous pouvez également utiliser Get.generalDialog au lieu de showGeneralDialog.
Pour tous les autres widgets de la boîte de dialogue Flutter, y compris cupertinos, vous pouvez utiliser Get.overlayContext au lieu du context et l'ouvrir n'importe où dans votre code.
Pour les widgets qui n'utilisent pas Overlay, vous pouvez utiliser Get.context.
Ces deux contextes fonctionneront dans 99% des cas pour remplacer le context de votre interface utilisateur, sauf dans les cas où inheritedWidget est utilisé sans context de navigation.
### BottomSheets
Get.bottomSheet est comme showModalBottomSheet, mais n'a pas besoin de 'context'.
```dart
Get.bottomSheet(
Container(
child: Wrap(
children: <Widget>[
ListTile(
leading: Icon(Icons.music_note),
title: Text('Music'),
onTap: () {}
),
ListTile(
leading: Icon(Icons.videocam),
title: Text('Video'),
onTap: () {},
),
],
),
)
);
```
## Nested Navigation
Getx a rendu la navigation imbriquée de Flutter encore plus facile.
Vous n'avez pas besoin de 'context' et vous trouverez votre stack de navigation par ID.
- NOTE: La création de stacks de navigation parallèles peut être dangereuse. L'idéal est de ne pas utiliser NestedNavigators, ou de l'utiliser avec parcimonie. Si votre projet l'exige, allez-y, mais gardez à l'esprit que conserver plusieurs stacks de navigation en mémoire n'est peut-être pas une bonne idée pour la consommation de RAM.
Voyez comme c'est simple:
```dart
Navigator(
key: Get.nestedKey(1), // créez une clé par index
initialRoute: '/',
onGenerateRoute: (settings) {
if (settings.name == '/') {
return GetPageRoute(
page: () => Scaffold(
appBar: AppBar(
title: Text("Main"),
),
body: Center(
child: TextButton(
color: Colors.blue,
onPressed: () {
Get.toNamed('/second', id:1); // naviguer votre itinéraire imbriqué par index
},
child: Text("Go to second"),
),
),
),
);
} else if (settings.name == '/second') {
return GetPageRoute(
page: () => Center(
child: Scaffold(
appBar: AppBar(
title: Text("Main"),
),
body: Center(
child: Text("second")
),
),
),
);
}
}
),
```

View File

@@ -0,0 +1,721 @@
- [Gestion d'État](#gestion-d-etat)
- [Gestionnaire d'état réactif](#gestionnaire-d-etat-reactif)
- [Avantages](#avantages)
- [Performance maximale:](#performance-maximale)
- [Déclaration d'une variable réactive](#declaration-d-une-variable-reactive)
- [Avoir un état réactif, c'est facile.](#avoir-un-etat-reactif-c-est-facile)
- [Utilisation des valeurs dans la Vue](#utilisation-des-valeurs-dans-la-vue)
- [Conditions pour reconstruire](#conditions-pour-reconstruire)
- [Quand utiliser .obs](#quand-utiliser-obs)
- [Remarque sur List](#remarque-sur-list)
- [Pourquoi je dois utiliser .value](#pourquoi-je-dois-utiliser-value)
- [Obx()](#obx)
- [Workers](#workers)
- [Gestionnaire d'état simple](#gestionnaire-d-etat-simple)
- [Atouts](#atouts)
- [Utilisation](#utilisation)
- [Comment il gère les contrôleurs](#comment-il-gre-les-contrleurs)
- [Vous n'aurez plus besoin de StatefulWidgets](#vous-naurez-plus-besoin-de-statefulwidgets)
- [Pourquoi ça existe](#pourquoi-ca-existe)
- [Autres façons de l'utiliser](#autres-formes-d-utilisation)
- [IDs Uniques](#ids-uniques)
- [Mélanger les deux gestionnaires d'état](#mixing-the-two-state-managers)
- [GetBuilder vs GetX vs Obx vs MixinBuilder](#getbuilder-vs-getx-vs-obx-vs-mixinbuilder)
# Gestion d Etat
GetX n'utilise pas Streams ou ChangeNotifier comme les autres gestionnaires d'état. Pourquoi? En plus de créer des applications pour Android, iOS, Web, Linux, MacOS et Linux, GetX vous permet de créer des applications serveur avec la même syntaxe que Flutter / GetX. Afin d'améliorer le temps de réponse et de réduire la consommation de RAM, nous avons créé GetValue et GetStream, des solutions à faible latence qui offrent beaucoup de performances, à un faible coût d'exploitation. Nous utilisons cette base pour construire toutes nos ressources, y compris la gestion d'état.
- _Complexité_: Certains gestionnaires d'État sont complexes et ont beaucoup de code standard. Avec GetX, vous n'avez pas à définir une classe pour chaque événement, le code est très propre et clair, et vous faites beaucoup plus en écrivant moins. Beaucoup de gens ont abandonné Flutter à cause de ce sujet, et ils ont enfin une solution stupidement simple pour gérer les états.
- _Aucun générateur de code_: Vous passez la moitié de votre temps de développement à écrire la logique de votre application. Certains gestionnaires d'état s'appuient sur des générateurs de code pour avoir un code lisible minimal. Changer une variable et avoir à exécuter build_runner peut être improductif, et souvent le temps d'attente après un redémarrage sera long, et vous devrez boire beaucoup de café.
Avec GetX, tout est réactif, et rien ne dépend des générateurs de code, augmentant votre productivité dans tous les aspects de votre développement.
- _Cela ne dépend pas de 'context'_: Vous avez probablement déjà eu besoin d'envoyer le contexte de votre vue à un contrôleur, ce qui rend le couplage de la vue avec votre logique métier élevé. Vous avez probablement dû utiliser une dépendance dans un endroit qui n'a pas de contexte, et avez dû passer le contexte à travers différentes classes et fonctions. Cela n'existe tout simplement pas avec GetX. Vous avez accès à vos contrôleurs depuis vos contrôleurs sans aucun contexte. Vous n'avez pas besoin d'envoyer le contexte par paramètre pour rien.
- _Contrôle granulaire_: la plupart des gestionnaires d'état sont basés sur ChangeNotifier. ChangeNotifier notifiera tous les widgets qui en dépendent lors de l'appel de notifyListeners. Si vous avez 40 widgets sur un écran, qui ont une variable de votre classe ChangeNotifier, lorsque vous en mettez un à jour, tous seront reconstruits.
Avec GetX, même les widgets imbriqués sont respectés. Si Obx gère votre ListView et un autre gère une case à cocher dans ListView, lors de la modification de la valeur CheckBox, il ne sera mis à jour que, lors de la modification de la valeur List, seul le ListView sera mis à jour.
- _Il ne reconstruit que si sa variable change VRAIMENT_: GetX a un contrôle de flux, cela signifie que si vous affichez un texte avec 'Paola', si vous changez à nouveau la variable observable en 'Paola', le widget ne sera pas reconstruit. C'est parce que GetX sait que `Paola` est déjà affiché dans Text et ne fera pas de reconstructions inutiles.
La plupart (sinon tous) les gestionnaires d'état actuels se reconstruiront à l'écran.
## Gestionnaire d etat reactif
La programmation réactive peut aliéner de nombreuses personnes car on dit qu'elle est compliquée. GetX transforme la programmation réactive en quelque chose d'assez simple:
- Vous n'aurez pas besoin de créer des StreamControllers.
- Vous n'aurez pas besoin de créer un StreamBuilder pour chaque variable
- Vous n'aurez pas besoin de créer une classe pour chaque état.
- Vous n'aurez pas besoin de créer un 'get' pour une valeur initiale.
La programmation réactive avec Get est aussi simple que d'utiliser setState.
Imaginons que vous ayez une variable de 'name' et que vous souhaitiez que chaque fois que vous la modifiez, tous les widgets qui l'utilisent soient automatiquement modifiés.
Voici votre variable:
```dart
var name = 'Jonatas Borges';
```
Pour la rendre observable, il vous suffit d'ajouter ".obs" à la fin:
```dart
var name = 'Jonatas Borges'.obs;
```
C'est *tout*. Si simple que ca.
A partir de maintenant, nous pourrions désigner ces variables réactives - ". Obs" (ervables) comme _Rx_.
Qu'est ce qui s'est passé derrière les rideaux? Nous avons créé un `Stream` de` String`s, assigné la valeur initiale `" Jonatas Borges "`, nous avons notifié tous les widgets qui utilisent `" Jonatas Borges "` qu'ils "appartiennent" maintenant à cette variable, et quand la valeur _Rx_ changements, ils devront également changer.
C'est la **magie de GetX**, grâce aux performances de Dart.
Mais, comme nous le savons, un `Widget` ne peut être changé que s'il est à l'intérieur d'une fonction, car les classes statiques n'ont pas le pouvoir de" changer automatiquement ".
Vous devrez créer un `StreamBuilder`, vous abonner à cette variable pour écouter les changements et créer une" cascade "de` StreamBuilder` imbriqués si vous voulez changer plusieurs variables dans la même portée, non?
Non, vous n'avez pas besoin d'un `StreamBuilder`, mais vous avez raison pour les classes statiques.
Eh bien, dans la vue, nous avons généralement beaucoup de code standard lorsque nous voulons changer un widget spécifique, c'est la manière Flutter.
Avec **GetX**, vous pouvez également oublier ce code passe-partout.
`StreamBuilder( … )`? `initialValue: …`? `builder: …`? Non, il vous suffit de placer cette variable dans un widget `Obx()`.
```dart
Obx (() => Text (controller.name));
```
_Que devez-vous mémoriser?_ Seulement `Obx(() =>`.
Vous passez simplement ce Widget via une fonction dans un `Obx()` (l' "Observateur" du _Rx_).
`Obx` est assez intelligent et ne changera que si la valeur de` controller.name` change.
Si `name` est` "John" `, et que vous le changez en` "John" `(` name.value = "John" `), comme c'est la même` valeur` qu'avant, rien ne changera à l'écran, et `Obx`, pour économiser les ressources, ignorera simplement la nouvelle valeur et ne reconstruira pas le widget. **N'est-ce pas incroyable?**
> Alors, que faire si j'ai 5 variables _Rx_ (observables) dans un `Obx`?
Il sera simplement mis à jour lorsque **l'un d'entre eux** change.
> Et si j'ai 30 variables dans une classe, lorsque j'en mets une à jour, est-ce que cela va mettre à jour **toutes** les variables qui sont dans cette classe?
Non, juste le **Widget spécifique** qui utilise cette variable _Rx_.
Ainsi, **GetX** ne met à jour l'écran que lorsque la variable _Rx_ change sa valeur.
```
final isOpen = false.obs;
// Rien de ne change... valeur identique.
void onButtonTap() => isOpen.value=false;
```
### Avantages
**GetX()** vous aide lorsque vous avez besoin d'un contrôle **granulaire** sur ce qui est mis à jour.
Si vous n'avez pas besoin d'ID uniques, car toutes vos variables seront modifiées lorsque vous effectuez une action, utilisez `GetBuilder`,
parce que c'est un Simple State Updater (en blocs, comme `setState()`), fait en seulement quelques lignes de code.
Il a été rendu simple, pour avoir le moins d'impact sur le processeur, et juste pour remplir un seul objectif (une reconstruction de _l'état_) et dépenser le minimum de ressources possible.
Si vous avez besoin d'un State Manager **puissant** , vous ne pouvez pas vous tromper avec **GetX**.
Cela ne fonctionne pas avec les variables, mais __flows__, tout ce qu'il contient sont des `Streams` en réalité.
Vous pouvez utiliser _rxDart_ en conjonction avec lui, car tout est `Streams`.
Vous pouvez écouter les changements de chaque "variable _Rx_",
parce que tout ce qui se trouve dedans est un `Streams`.
C'est littéralement une approche _BLoC_, plus facile que _MobX_, et sans générateurs de code ni décorations.
Vous pouvez transformer **n'importe quoi** en un _"Observable"_ avec juste un `.obs`.
### Performance maximale:
En plus d'avoir un algorithme intelligent pour des reconstructions minimales, **GetX** utilise des comparateurs
pour s'assurer que l'État a changé.
Si vous rencontrez des erreurs dans votre application et envoyez un changement d'état en double,
**GetX** garantira qu'il ne plantera pas.
Avec **GetX**, l'état ne change que si la `valeur` change.
C'est la principale différence entre **GetX** et l'utilisation de _`computed` de MobX_.
Lors de la jonction de deux __observables__, si l'une change; le listener de cet _observable_ changera également.
Avec **GetX**, si vous joignez deux variables, `GetX()` (similaire à `Observer()`), ne se reconstruira que si cela implique un réel changement d'état.
### Declaration d une variable reactive
Vous avez 3 façons de transformer une variable en "observable".
1 - La première est d'utiliser **`Rx{Type}`**.
```dart
// la valeur initiale est recommandée, mais pas obligatoire
final name = RxString('');
final isLogged = RxBool(false);
final count = RxInt(0);
final balance = RxDouble(0.0);
final items = RxList<String>([]);
final myMap = RxMap<String, int>({});
```
2 - La seconde consiste à utiliser **`Rx`** et à utiliser les types `Rx<Type>` Génériques Darts
```dart
final name = Rx<String>('');
final isLogged = Rx<Bool>(false);
final count = Rx<Int>(0);
final balance = Rx<Double>(0.0);
final number = Rx<Num>(0);
final items = Rx<List<String>>([]);
final myMap = Rx<Map<String, int>>({});
// Classes personnalisées - il peut s'agir de n'importe quelle classe, littéralement
final user = Rx<User>();
```
3 - La troisième approche, plus pratique, plus facile et préférée, ajoutez simplement **`.obs`** comme propriété de votre` valeur`:
```dart
final name = ''.obs;
final isLogged = false.obs;
final count = 0.obs;
final balance = 0.0.obs;
final number = 0.obs;
final items = <String>[].obs;
final myMap = <String, int>{}.obs;
// Classes personnalisées - il peut s'agir de n'importe quelle classe, littéralement
final user = User().obs;
```
##### Avoir un etat reactif, c est facile.
Comme nous le savons, _Dart_ se dirige maintenant vers _null safety_.
Pour être prêt, à partir de maintenant, vous devez toujours commencer vos variables _Rx_ avec une **valeur initiale**.
> Transformer une variable en _observable_ + _valeurInitiale_ avec **GetX** est l'approche la plus simple et la plus pratique.
Vous allez littéralement ajouter un "".obs"" à la fin de votre variable, et **c'est tout**, vous l'avez rendue observable,
et sa `.value`, eh bien, sera la _valeurInitiale_.
### Utilisation des valeurs dans la Vue
```dart
// dans le controlleur
final count1 = 0.obs;
final count2 = 0.obs;
int get sum => count1.value + count2.value;
```
```dart
// dans la vue
GetX<Controller>(
builder: (controller) {
print("count 1 reconstruction");
return Text('${controller.count1.value}');
},
),
GetX<Controller>(
builder: (controller) {
print("count 2 reconstruction");
return Text('${controller.count2.value}');
},
),
GetX<Controller>(
builder: (controller) {
print("count 3 reconstruction");
return Text('${controller.sum}');
},
),
```
Si nous incrémentons `count1.value++`, cela affichera:
- `count 1 reconstruction`
- `count 3 reconstruction`
parce que `count1` a une valeur de `1`, et `1 + 0 = 1`, changeant la valeur du getter `sum`.
Si nous incrémentons `count2.value++`, cela affichera:
- `count 2 reconstruction`
- `count 3 reconstruction`
parce que `count2.value` a changé et que le résultat de `sum` est maintenant `2`.
- NOTE: Par défaut, le tout premier événement reconstruira le widget, même s'il s'agit de la même `valeur`.
Ce comportement existe en raison de variables booléennes.
Imaginez que vous fassiez ceci:
```dart
var isLogged = false.obs;
```
Et puis, vous vérifiez si un utilisateur est "connecté" pour déclencher un événement dans `ever`.
```dart
@override
onInit(){
ever(isLogged, fireRoute);
isLogged.value = await Preferences.hasToken();
}
fireRoute(logged) {
if (logged) {
Get.off(Home());
} else {
Get.off(Login());
}
}
```
si `hasToken` était `false`, il n'y aurait pas de changement à `isLogged`, donc` ever() `ne serait jamais appelé.
Pour éviter ce type de comportement, la première modification d'un _observable_ déclenchera toujours un événement,
même s'il contient la même `.value`.
Vous pouvez supprimer ce comportement si vous le souhaitez, en utilisant:
`isLogged.firstRebuild = false;`
### Conditions pour reconstruire
En outre, Get fournit un contrôle d'état raffiné. Vous pouvez conditionner un événement (comme l'ajout d'un objet à une liste), à une certaine condition.
```dart
// Premier paramètre: condition, doit retourner vrai ou faux.
// Deuxième paramètre: la nouvelle valeur à appliquer si la condition est vraie.
list.addIf(item < limit, item);
```
Sans décorations, sans générateur de code, sans complications :smile:
Connaissez-vous l'application 'counter' de Flutter? Votre classe Controller pourrait ressembler à ceci:
```dart
class CountController extends GetxController {
final count = 0.obs;
}
```
Avec un simple:
```dart
controller.count.value++
```
Vous pouvez mettre à jour la variable de compteur dans votre interface utilisateur, quel que soit l'endroit où elle est stockée.
### Quand utiliser .obs
Vous pouvez tout transformer sur obs. Voici deux façons de procéder:
* Vous pouvez convertir vos valeurs de classe en obs
```dart
class RxUser {
final name = "Camila".obs;
final age = 18.obs;
}
```
* ou vous pouvez convertir la classe entière en un observable:
```dart
class User {
User({String name, int age});
var name;
var age;
}
// en instanciant:
final user = User(name: "Camila", age: 18).obs;
```
### Remarque sur List
Les listes sont complètement observables, tout comme les objets qu'elles contiennent. De cette façon, si vous ajoutez une valeur à une liste, cela reconstruira automatiquement les widgets qui l'utilisent.
Vous n'avez pas non plus besoin d'utiliser ".value" avec des listes, l'incroyable api de Dart nous a permis de supprimer cela.
Malheureusement, les types primitifs comme String et int ne peuvent pas être étendus, ce qui rend l'utilisation de .value obligatoire, mais ce ne sera pas un problème si vous travaillez avec des getters et des setters pour ceux-ci.
```dart
// Dans le controlleur
final String title = 'User Info:'.obs;
final list = List<User>().obs;
// Dans la vue
Text(controller.title.value), // La String doit avoir .value devant elle
ListView.builder (
itemCount: controller.list.length // pas besoin pour List
)
```
Lorsque vous rendez vos propres classes observables, il existe une manière différente de les mettre à jour:
```dart
// sur le fichier modèle
// nous allons rendre la classe entière observable au lieu de chaque attribut
class User() {
User({this.name = '', this.age = 0});
String name;
int age;
}
// Dans le controlleur
final user = User().obs;
// lorsque vous devez mettre à jour la variable utilisateur:
user.update( (user) { // ce paramètre est la classe même que vous souhaitez mettre à jour
user.name = 'Jonny';
user.age = 18;
});
// une autre manière de mettre à jour la variable user:
user(User(name: 'João', age: 35));
// Dans la vue:
Obx(()=> Text("Name ${user.value.name}: Age: ${user.value.age}"))
// vous pouvez également accéder aux valeurs du modèle sans le .value:
user().name; //notez que c'est la variable utilisateur, pas la classe (la variable a un u minuscule)
```
Vous n'êtes pas obligé de travailler avec des setters si vous ne le souhaitez pas. vous pouvez utiliser les API `assign` et `assignAll`.
L'API `assign` effacera votre liste et ajoutera un seul objet que vous souhaitez.
L'API `assignAll` effacera la liste existante et ajoutera tous les objets itérables que vous y injecterez.
### Pourquoi je dois utiliser .value
Nous pourrions supprimer l'obligation d'utiliser 'value' pour `String` et` int` avec une simple décoration et un générateur de code, mais le but de cette bibliothèque est précisément d'éviter les dépendances externes. Nous souhaitons proposer un environnement prêt à la programmation, impliquant l'essentiel (gestion des routes, des dépendances et des états), de manière simple, légère et performante, sans avoir besoin d'un package externe.
Vous pouvez littéralement ajouter 3 lettres à votre pubspec (get) et un signe deux-points et commencer la programmation. Toutes les solutions incluses par défaut, de la gestion des routes à la gestion des états, visent la facilité, la productivité et la performance.
Le poids total de cette bibliothèque est inférieur à celui d'un seul gestionnaire d'état, bien qu'il s'agisse d'une solution complète, et c'est ce que vous devez comprendre.
Si vous êtes dérangé par `.value`, et comme un générateur de code, MobX est une excellente alternative, et vous pouvez l'utiliser en conjonction avec Get. Pour ceux qui veulent ajouter une seule dépendance dans pubspec et commencer à programmer sans se soucier de l'incompatibilité de la version d'un package avec un autre, ou si l'erreur d'une mise à jour d'état vient du gestionnaire d'état ou de la dépendance, ou encore, ne veulent pas s'inquiéter de la disponibilité des contrôleurs, que ce soit littéralement "juste de la programmation", get est tout simplement parfait.
Si vous n'avez aucun problème avec le générateur de code MobX, ou si vous n'avez aucun problème avec le code standard BLoC, vous pouvez simplement utiliser Get pour les routes et oublier qu'il a un gestionnaire d'état. Get SEM et RSM sont nés par nécessité, mon entreprise avait un projet avec plus de 90 contrôleurs, et le générateur de code a simplement pris plus de 30 minutes pour terminer ses tâches après un Flutter Clean sur une machine raisonnablement bonne, si votre projet il a 5, 10, 15 contrôleurs, n'importe quel gestionnaire d'état vous suffira bien. Si vous avez un projet d'une taille absurde et que le générateur de code est un problème pour vous, cette solution vous a été attribuée.
Évidemment, si quelqu'un veut contribuer au projet et créer un générateur de code, ou quelque chose de similaire, je vais créer un lien dans ce readme comme alternative, mon besoin n'est pas le besoin de tous les développeurs, mais pour l'instant je dis q'il y a de bonnes solutions qui font déjà cela, comme MobX.
### Obx()
Les types dans Get à l'aide de Bindings ne sont pas nécessaires. Vous pouvez utiliser le widget Obx, au lieu de GetX, qui ne reçoit que la fonction anonyme qui crée un widget.
Évidemment, si vous n'utilisez pas de type, vous devrez avoir une instance de votre contrôleur pour utiliser les variables, ou utiliser `Get.find<Controller>()` .value ou Controller.to.value pour récupérer la valeur .
### Workers
Les 'workers' vous assisteront, déclenchant des callbacks spécifiques lorsqu'un événement se produit.
```dart
/// Appelée à chaque fois que `count1` change.
ever(count1, (_) => print("$_ a été modifié"));
/// Appelée uniquement la première fois que la variable est modifiée
once(count1, (_) => print("$_ a été changé une fois"));
/// Anti DDos - Appelée chaque fois que l'utilisateur arrête de taper pendant 1 seconde, par exemple.
debounce(count1, (_) => print("debouce$_"), time: Duration(seconds: 1));
/// Ignore toutes les modifications pendant 1 seconde.
interval(count1, (_) => print("interval $_"), time: Duration(seconds: 1));
```
Tous les workers (sauf `debounce`) ont un paramètre nommé `condition`, qui peut etre un `bool` ou un callback qui retourne un `bool`.
Cette `condition` definit quand la fonction `callback` est executée.
Tous les workers renvoyent un objet `Worker`, qui peut être utilisé pour annuler ( via `dispose()` ) le `worker`.
- **`ever`**
est appelée chaque fois que la variable _Rx_ émet une nouvelle valeur.
- **`everAll`**
Un peu comme `ever`, mais il prend une` List` de valeurs _Rx_. Appelée chaque fois que sa variable est changée. C'est tout.
- **`once`**
'once' est appelée uniquement la première fois que la variable a été modifiée.
- **`debounce`**
'debounce' est très utile dans les fonctions de recherche, où vous souhaitez que l'API ne soit appelée que lorsque l'utilisateur a fini de taper. Si l'utilisateur tape "Jonny", vous aurez 5 recherches dans les API, par la lettre J, o, n, n et y. Avec Get, cela ne se produit pas, car vous aurez un Worker "anti-rebond" qui ne sera déclenché qu'à la fin de la saisie.
- **`interval`**
'interval' est différent de 'debounce'. Avec `debounce` si l'utilisateur fait 1000 changements à une variable en 1 seconde, il n'enverra que le dernier après le temporisateur stipulé (la valeur par défaut est 800 millisecondes). 'Interval' ignorera à la place toutes les actions de l'utilisateur pour la période stipulée. Si vous envoyez des événements pendant 1 minute, 1000 par seconde, debounce ne vous enverra que le dernier, lorsque l'utilisateur arrête de mitrailler les événements. interval délivrera des événements toutes les secondes, et s'il est réglé sur 3 secondes, il fournira 20 événements cette minute. Ceci est recommandé pour éviter les abus, dans des fonctions où l'utilisateur peut rapidement cliquer sur quelque chose et obtenir un avantage (imaginez que l'utilisateur puisse gagner des pièces en cliquant sur quelque chose, s'il cliquait 300 fois dans la même minute, il aurait 300 pièces, en utilisant l'intervalle, vous pouvez définir une période de 3 secondes, et même en cliquant 300 ou mille fois, le maximum qu'il obtiendrait en 1 minute serait de 20 pièces, en cliquant 300 ou 1 million de fois). Le 'debounce' convient aux anti-DDos, pour des fonctions comme la recherche où chaque changement de onChange entraînerait une requête à votre api. Debounce attendra que l'utilisateur arrête de taper le nom, pour faire la demande. S'il était utilisé dans le scénario de pièces mentionné ci-dessus, l'utilisateur ne gagnerait qu'une pièce, car il n'est exécuté que lorsque l'utilisateur "fait une pause" pendant le temps établi.
- NOTE: Les 'workers' doivent toujours être utilisés lors du démarrage d'un contrôleur ou d'une classe, il doit donc toujours être dans onInit (recommandé), le constructeur de classe ou l'initState d'un StatefulWidget (cette pratique n'est pas recommandée dans la plupart des cas, mais cela ne devrait poser aucun problème).
## Gestionnaire d etat simple
Get a un gestionnaire d'état extrêmement léger et facile, qui n'utilise pas ChangeNotifier, répondra aux besoins en particulier des nouveaux utilisateurs de Flutter et ne posera pas de problèmes pour les applications volumineuses.
GetBuilder vise précisément le contrôle de plusieurs états. Imaginez que vous avez ajouté 30 produits à un panier, que vous cliquez sur supprimer un, en même temps que la liste est mise à jour, le prix est mis à jour et le badge dans le panier est mis à jour avec un nombre plus petit. Ce type d'approche fait de GetBuilder un tueur, car il regroupe les états et les modifie tous à la fois sans aucune "logique de calcul" pour cela. GetBuilder a été créé avec ce type de situation à l'esprit, car pour un changement d'état éphémère, vous pouvez utiliser setState et vous n'aurez pas besoin d'un gestionnaire d'état pour cela.
De cette façon, si vous voulez un contrôleur individuel, vous pouvez lui attribuer des ID ou utiliser GetX. Cela dépend de vous, en vous rappelant que plus vous avez de widgets "individuels", plus les performances de GetX se démarqueront, tandis que les performances de GetBuilder devraient être supérieures, en cas de changement d'état multiple.
### Atouts
1. Met à jour uniquement les widgets requis.
2. N'utilise pas changeNotifier, c'est le gestionnaire d'état qui utilise le moins de mémoire (proche de 0 Mo).
3. Oubliez StatefulWidget! Avec Get, vous n'en aurez jamais besoin. Avec les autres gestionnaires d'états, vous devrez probablement utiliser un StatefulWidget pour obtenir l'instance de votre fournisseur, BLoC, MobX Controller, etc. Mais vous êtes-vous déjà arrêté pour penser que votre appBar, votre 'scaffold', et la plupart des les widgets de votre classe sont sans état (stateless)? Alors pourquoi sauvegarder l'état d'une classe entière, si vous pouvez sauvegarder l'état du widget qui est `avec état` (statefull)? Get résout cela aussi. Créez une classe sans état, rendez tout `sans état`. Si vous devez mettre à jour un seul composant, enveloppez-le avec GetBuilder et son état sera conservé.
4. Organisez votre projet pour de vrai! Les contrôleurs ne doivent pas être dans votre interface utilisateur, placer votre TextEditController ou tout contrôleur que vous utilisez dans votre classe Controller.
5. Avez-vous besoin de déclencher un événement pour mettre à jour un widget dès son rendu? GetBuilder a la propriété "initState", tout comme StatefulWidget, et vous pouvez appeler des événements depuis votre contrôleur, directement depuis celui-ci, aucun événement n'étant placé dans votre initState.
6. Avez-vous besoin de déclencher une action comme la fermeture de stream, de timers, etc.? GetBuilder a également la propriété dispose(), où vous pouvez appeler des événements dès que ce widget est détruit.
7. N'utilisez les streams que si nécessaire. Vous pouvez utiliser vos StreamControllers à l'intérieur de votre contrôleur normalement, et utiliser StreamBuilder également normalement, mais rappelez-vous qu'un stream consomme raisonnablement de la mémoire, la programmation réactive est belle, mais vous ne devriez pas en abuser. 30 streams ouverts simultanément peuvent être pires que changeNotifier (et changeNotifier est très mauvais).
8. Mettez à jour les widgets sans dépenser de RAM pour cela. Get stocke uniquement l'ID de créateur GetBuilder et met à jour ce GetBuilder si nécessaire. La consommation de mémoire du stockage get ID en mémoire est très faible, même pour des milliers de GetBuilders. Lorsque vous créez un nouveau GetBuilder, vous partagez en fait l'état de GetBuilder qui a un ID de créateur. Un nouvel état n'est pas créé pour chaque GetBuilder, ce qui économise BEAUCOUP de RAM pour les applications volumineuses. Fondamentalement, votre application sera entièrement sans état (stateless), et les quelques widgets qui seront stateful (dans GetBuilder) auront un seul état, et par conséquent, la mise à jour d'un seul les mettra tous à jour. L'état est unique.
9. Get est omniscient et, dans la plupart des cas, il sait exactement quand sortir de mémoire un contrôleur. Vous ne devez pas vous soucier du moment de vous débarrasser d'un contrôleur, Get connaît le meilleur moment pour le faire.
### Utilisation
```dart
// Créez la classe controller qui 'extends' GetxController
class Controller extends GetxController {
int counter = 0;
void increment() {
counter++;
update(); // utilisez update() pour mettre à jour la variable de compteur sur l'interface utilisateur lorsque incrément() est appelé
}
}
// Sur votre classe Stateless / Stateful, utilisez GetBuilder pour mettre à jour le texte lorsque incrément() est appelé
GetBuilder<Controller>(
init: Controller(), // INITIER CA UNIQUEMENT LA PREMIÈRE FOIS
builder: (_) => Text(
'${_.counter}',
),
)
//Initialisez votre contrôleur uniquement la première fois. La deuxième fois que vous utilisez ReBuilder pour le même contrôleur, ne recommencez pas. Votre contrôleur sera automatiquement supprimé de la mémoire dès que le widget qui l'a marqué comme `init` sera déployé. Vous n'avez pas à vous en soucier, Get le fera automatiquement, assurez-vous simplement de ne pas démarrer deux fois le même contrôleur.
```
**Fait!**
- Vous avez déjà appris à gérer les états avec Get.
- Note: Vous pouvez souhaiter une organisation plus grande et ne pas utiliser la propriété init. Pour cela, vous pouvez créer une classe et étendre la classe Bindings, et y mentionner les contrôleurs qui seront créés dans cette route. Les contrôleurs ne seront pas créés à ce moment-là, au contraire, il ne s'agit que d'une déclaration, de sorte que la première fois que vous utilisez un contrôleur, Get saura où chercher. Get restera lazyLoad et continuera à supprimer les contrôleurs lorsqu'ils ne seront plus nécessaires. Voir l'exemple pub.dev pour voir comment cela fonctionne.
Si vous parcourez de nombreuses routes et avez besoin de données qui se trouvaient dans votre contrôleur précédemment utilisé, il vous suffit de réutiliser GetBuilder (sans init):
```dart
class OtherClass extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: GetBuilder<Controller>(
builder: (s) => Text('${s.counter}'),
),
),
);
}
```
Si vous devez utiliser votre contrôleur dans de nombreux autres endroits, et en dehors de GetBuilder, créez simplement un get dans votre contrôleur et ayez-le facilement. (ou utilisez `Get.find<Controller>()`)
```dart
class Controller extends GetxController {
/// Vous n'en avez pas besoin. Je recommande de l'utiliser uniquement pour faciliter la syntaxe.
/// avec la méthode statique: Controller.to.counter();
/// sans méthode statique: Get.find<Controller>() .counter();
/// Il n'y a aucune différence de performances, ni aucun effet secondaire de l'utilisation de l'une ou l'autre syntaxe. Un seul n'a pas besoin du type, et l'autre l'EDI le complétera automatiquement.
static Controller get to => Get.find(); // Ajouter cette ligne
int counter = 0;
void increment() {
counter++;
update();
}
}
```
Et puis vous pouvez accéder directement à votre contrôleur, de cette façon:
```dart
FloatingActionButton(
onPressed:() {
Controller.to.increment(),
} // This is incredibly simple!
child: Text("${Controller.to.counter}"),
),
```
Lorsque vous appuyez sur FloatingActionButton, tous les widgets qui écoutent la variable `counter` seront mis à jour automatiquement.
### Comment il gère les contrôleurs
Disons que nous avons ceci:
`Class a => Class B (has controller X) => Class C (has controller X)`
Dans la classe A, le contrôleur n'est pas encore en mémoire, car vous ne l'avez pas encore utilisé (Get est lazyLoad). Dans la classe B, vous avez utilisé le contrôleur et il est entré en mémoire. Dans la classe C, vous avez utilisé le même contrôleur que dans la classe B, Get partagera l'état du contrôleur B avec le contrôleur C, et le même contrôleur est toujours en mémoire. Si vous fermez l'écran C et l'écran B, Get retirera automatiquement le contrôleur X de la mémoire et libèrera des ressources, car la classe A n'utilise pas le contrôleur. Si vous naviguez à nouveau vers B, le contrôleur X entrera à nouveau en mémoire, si au lieu de passer à la classe C, vous revenez en classe A, Get retirera le contrôleur de la mémoire de la même manière. Si la classe C n'utilisait pas le contrôleur et que vous retiriez la classe B de la mémoire, aucune classe n'utiliserait le contrôleur X et de même, elle serait éliminée. La seule exception qui peut gâcher Get, est si vous supprimez B de l'itinéraire de manière inattendue et essayez d'utiliser le contrôleur dans C.Dans ce cas, l'ID de créateur du contrôleur qui était dans B a été supprimé et Get a été programmé pour supprimer de la mémoire tous les contrôleurs qui n'ont pas d'ID de créateur. Si vous avez l'intention de faire cela, ajoutez l'indicateur "autoRemove: false" au GetBuilder de la classe B et utilisez adoptID = true; dans GetBuilder de la classe C.
### Vous n'aurez plus besoin de StatefulWidgets
Utiliser StatefulWidgets signifie stocker inutilement l'état d'écrans entiers, même parce que si vous avez besoin de reconstruire au minimum un widget, vous l'intègrerez dans un Consumer / Observer / BlocProvider / GetBuilder / GetX / Obx, qui sera un autre StatefulWidget.
La classe StatefulWidget est une classe plus grande que StatelessWidget, qui allouera plus de RAM, et cela ne fera peut-être pas une différence significative entre une ou deux classes, mais cela le fera très certainement lorsque vous en aurez 100!
À moins que vous n'ayez besoin d'utiliser un mixin, comme TickerProviderStateMixin, il sera totalement inutile d'utiliser un StatefulWidget avec Get.
Vous pouvez appeler toutes les méthodes d'un StatefulWidget directement à partir d'un GetBuilder.
Si vous devez appeler la méthode initState() ou dispose() par exemple, vous pouvez les appeler directement:
```dart
GetBuilder<Controller>(
initState: (_) => Controller.to.fetchApi(),
dispose: (_) => Controller.to.closeStreams(),
builder: (s) => Text('${s.username}'),
),
```
Une bien meilleure approche que celle-ci consiste à utiliser les méthodes onInit() et onClose() directement à partir de votre contrôleur.
```dart
@override
void onInit() {
fetchApi();
super.onInit();
}
```
- NOTE: Si vous voulez démarrer une méthode au moment où le contrôleur est appelé pour la première fois, vous N'AVEZ PAS BESOIN d'utiliser des constructeurs pour cela, en fait, en utilisant un package orienté performance comme Get, cela frôle la mauvaise pratique, car il s'écarte de la logique dans laquelle les contrôleurs sont créés ou alloués (si vous créez une instance de ce contrôleur, le constructeur sera appelé immédiatement, vous remplirez un contrôleur avant même qu'il ne soit utilisé, vous allouez de la mémoire sans qu'elle ne soit utilisée , cela nuit définitivement aux principes de cette bibliothèque). Les méthodes onInit(); et onClose(); ont été créés pour cela, ils seront appelés lors de la création du Controller, ou lors de sa première utilisation, selon que vous utilisez Get.lazyPut ou non. Si vous voulez, par exemple, faire un appel à votre API pour remplir des données, vous pouvez oublier la méthode à l'ancienne de initState / dispose, lancez simplement votre appel à l'API dans onInit, et si vous devez exécuter une commande comme la fermeture des flux, utilisez onClose() pour cela.
### Pourquoi ca existe
Le but de ce package est précisément de vous donner une solution complète pour la navigation des routes, la gestion des dépendances et des états, en utilisant le moins de dépendances possible, avec un haut degré de découplage. Get engage toutes les API Flutter de haut et bas niveau en lui-même, pour vous assurer de travailler avec le moins de couplage possible. Nous centralisons tout dans un seul package, pour vous assurer que vous n'avez aucun type de couplage dans votre projet. De cette façon, vous pouvez mettre uniquement des widgets dans votre vue et laisser la partie de votre équipe qui travaille avec la `business logique` libre, pour travailler avec la business logique sans dépendre d'aucun élément de la vue. Cela fournit un environnement de travail beaucoup plus propre, de sorte qu'une partie de votre équipe ne travaille qu'avec des widgets, sans se soucier d'envoyer des données à votre contrôleur, et une partie de votre équipe ne travaille qu'avec la business logique dans toute son ampleur, sans dépendre d'aucun élément de la Vue.
Donc, pour simplifier cela:
Vous n'avez pas besoin d'appeler des méthodes dans initState et de les envoyer par paramètre à votre contrôleur, ni d'utiliser votre constructeur de contrôleur pour cela, vous avez la méthode onInit() qui est appelée au bon moment pour démarrer vos services.
Vous n'avez pas besoin d'appeler l'appareil, vous avez la méthode onClose() qui sera appelée au moment exact où votre contrôleur n'est plus nécessaire et sera supprimé de la mémoire. De cette façon, ne laissez les vues que pour les widgets, abstenez-vous d'y mettre tout type de business logique.
N'appelez pas une méthode dispose() dans GetxController, cela ne fera rien, rappelez-vous que le contrôleur n'est pas un Widget, vous ne devez pas le `supprimer`, et il sera automatiquement et intelligemment supprimé de la mémoire par Get. Si vous avez utilisé un Stream et que vous souhaitez le fermer, insérez-le simplement dans la méthode close(). Exemple:
```dart
class Controller extends GetxController {
StreamController<User> user = StreamController<User>();
StreamController<String> name = StreamController<String>();
/// pour fermer stream = méthode onClose(), pas dispose().
@override
void onClose() {
user.close();
name.close();
super.onClose();
}
}
```
Cycle de vie du controlleur:
- onInit() quand il est créé.
- onClose() quand il est fermé pour apporter des modifications en préparation de la méthode delete.
- deleted: vous n'avez pas accès à cette API car elle supprime littéralement le contrôleur de la mémoire. Il est littéralement supprimé, sans laisser de trace.
### Autres formes d utilisation
Vous pouvez utiliser l'instance Controller directement sur la valeur GetBuilder:
```dart
GetBuilder<Controller>(
init: Controller(),
builder: (value) => Text(
'${value.counter}', // ici
),
),
```
Vous pouvez également avoir besoin d'une instance de votre contrôleur en dehors de votre GetBuilder, et vous pouvez utiliser ces approches pour y parvenir:
```dart
class Controller extends GetxController {
static Controller get to => Get.find();
[...]
}
// Dans la vue:
GetBuilder<Controller>(
init: Controller(), // utilisez-le seulement la première fois sur chaque contrôleur
builder: (_) => Text(
'${Controller.to.counter}', // ici
)
),
```
ou encore
```dart
class Controller extends GetxController {
// static Controller get to => Get.find(); // sans static get
[...]
}
// Dans la classe stateful/stateless
GetBuilder<Controller>(
init: Controller(), // utilisez-le seulement la première fois sur chaque contrôleur
builder: (_) => Text(
'${Get.find<Controller>().counter}', // ici
),
),
```
- Vous pouvez utiliser des approches `non canoniques` pour ce faire. Si vous utilisez un autre gestionnaire de dépendances, comme get_it, modular, etc., et que vous souhaitez simplement fournir l'instance de contrôleur, vous pouvez le faire:
```dart
Controller controller = Controller();
[...]
GetBuilder<Controller>(
init: controller, // ici
builder: (_) => Text(
'${controller.counter}', // ici
),
),
```
### IDs Uniques
Si vous souhaitez affiner le contrôle de mise à jour d'un widget avec GetBuilder, vous pouvez leur attribuer des ID uniques:
```dart
GetBuilder<Controller>(
id: 'text'
init: Controller(), // utilisez-le seulement la première fois sur chaque contrôleur
builder: (_) => Text(
'${Get.find<Controller>().counter}', // ici
),
),
```
Et mettez-le à jour de cette façon:
```dart
update(['text']);
```
Vous pouvez également imposer des conditions pour la mise à jour:
```dart
update(['text'], counter < 10);
```
GetX le fait automatiquement et ne reconstruit que le widget qui utilise la variable exacte qui a été modifiée, si vous remplacez une variable par la même que la précédente et que cela n'implique pas un changement d'état, GetX ne reconstruira pas le widget pour économiser de la mémoire et Cycles CPU (3 est affiché à l'écran, et vous changez à nouveau la variable à 3. Dans la plupart des gestionnaires d'états, cela entraînera une nouvelle reconstruction, mais avec GetX, le widget ne sera reconstruit qu'à nouveau, si en fait son état a changé ).
## Mélanger les deux gestionnaires d'état
Certaines personnes ont ouvert une demande de fonctionnalité, car elles ne voulaient utiliser qu'un seul type de variable réactive, et les autres mécanismes, et devaient insérer un Obx dans un GetBuilder pour cela. En y réfléchissant, MixinBuilder a été créé. Il permet à la fois des changements réactifs en changeant les variables ".obs" et des mises à jour mécaniques via update(). Cependant, des 4 widgets c'est celui qui consomme le plus de ressources, car en plus d'avoir un Abonnement pour recevoir les événements de changement de ses enfants, il souscrit à la méthode de mise à jour de son contrôleur.
L'extension de GetxController est importante, car ils ont des cycles de vie et peuvent `démarrer` et `terminer` des événements dans leurs méthodes onInit() et onClose(). Vous pouvez utiliser n'importe quelle classe pour cela, mais je vous recommande fortement d'utiliser la classe GetxController pour placer vos variables, qu'elles soient observables ou non.
## GetBuilder vs GetX vs Obx vs MixinBuilder
En une décennie de travail avec la programmation, j'ai pu apprendre de précieuses leçons.
Mon premier contact avec la programmation réactive a été tellement "wow, c'est incroyable" et en fait la programmation réactive est incroyable.
Cependant, elle ne convient pas à toutes les situations. Souvent, il suffit de changer l'état de 2 ou 3 widgets en même temps, ou d'un changement d'état éphémère, auquel cas la programmation réactive n'est pas mauvaise, mais elle n'est pas appropriée.
La programmation réactive a une consommation de RAM plus élevée qui peut être compensée par le flux de travail individuel, ce qui garantira qu'un seul widget est reconstruit et si nécessaire, mais créer une liste avec 80 objets, chacun avec plusieurs flux n'est pas une bonne idée . Ouvrez le 'dart inspect' et vérifiez combien un StreamBuilder consomme, et vous comprendrez ce que j'essaie de vous dire.
Dans cet esprit, j'ai créé le gestionnaire d'état simple. C'est simple, et c'est exactement ce que vous devriez lui demander: mettre à jour l'état par blocs de manière simple et de la manière la plus économique.
GetBuilder est très économique en RAM, et il n'y a guère d'approche plus économique que lui (du moins je ne peux pas en imaginer une, si elle existe, merci de nous le faire savoir).
Cependant, GetBuilder est toujours un gestionnaire d'état mécanique, vous devez appeler update() comme vous auriez besoin d'appeler les notifyListeners() de Provider.
Il y a d'autres situations où la programmation réactive est vraiment intéressante, et ne pas travailler avec elle revient à réinventer la roue. Dans cet esprit, GetX a été créé pour fournir tout ce qui est le plus moderne et le plus avancé dans un gestionnaire d'état. Il met à jour uniquement ce qui est nécessaire et si nécessaire, si vous avez une erreur et envoyez 300 changements d'état simultanément, GetX filtrera et mettra à jour l'écran uniquement si l'état change réellement.
GetX est toujours plus économique que tout autre gestionnaire d'état réactif, mais il consomme un peu plus de RAM que GetBuilder. En y réfléchissant et en visant à maximiser la consommation de ressources, Obx a été créé. Contrairement à GetX et GetBuilder, vous ne pourrez pas initialiser un contrôleur à l'intérieur d'un Obx, c'est juste un widget avec un StreamSubscription qui reçoit les événements de changement de vos widgets enfants, c'est tout. Il est plus économique que GetX, mais perd face à GetBuilder, ce qui était prévisible, car il est réactif, et GetBuilder a l'approche la plus simpliste qui existe, de stocker le hashcode d'un widget et son StateSetter. Avec Obx, vous n'avez pas besoin d'écrire votre type de contrôleur, et vous pouvez entendre le changement de plusieurs contrôleurs différents, mais il doit être initialisé avant, soit en utilisant l'approche d'exemple au début de ce readme, soit en utilisant la classe Bindings.

View File

@@ -0,0 +1,395 @@
- [Dependency Management](#dependency-management)
- [Menginstansiasi method](#menginstansiasi-method)
- [Get.put()](#getput)
- [Get.lazyPut](#getlazyput)
- [Get.putAsync](#getputasync)
- [Get.create](#getcreate)
- [Menggunakan method/kelas yang terinstansiasi](#menggunakan-methodkelas-yang-terinstansiasi)
- [Perbedaan antar method](#perbedaan-antar-method)
- [Bindings](#bindings)
- [Bindings class](#bindings-class)
- [BindingsBuilder](#bindingsbuilder)
- [SmartManagement](#smartmanagement)
- [Cara mengubahnya](#cara-mengubahnya)
- [SmartManagement.full](#smartmanagementfull)
- [SmartManagement.onlyBuilders](#smartmanagementonlybuilders)
- [SmartManagement.keepFactory](#smartmanagementkeepfactory)
- [Cara kerja bindings dibalik layar](#cara-kerja-bindings-dibalik-layar)
- [Catatan](#catatan)
# Dependency Management
Get memiliki dependency manager sederhana dan powerful yang memungkinkan anda mendapatkan kelas yang setara dengan Bloc atau Controller hanya dengan 1 baris kode, tanpa Provider context, tanpa inheritedWidget:
```dart
Controller controller = Get.put(Controller());
```
Daripada menginstansiasi kelas anda didalam kelas yang anda gunakan, cukup lakukan hal itu di dalam Get instance, ini akan membuatnya tersedia di semua tempat di Aplikasimu. Jadi anda bisa menggunakan controller (atau class Bloc) secara normal.
- Catatan: Jika anda menggunakan State Manager milik Get, harap untuk lebih memperhatikan [Bindings](#bindings) api, yang mana akan membuat pengkoneksian View terhadap Controller jadi lebih mudah.
- Note²: Dependency Management Get terpisah dari bagian lain dari package, jadi jika sebagai contoh aplikasi anda sudah menggunakan state manager (tidak peduli apapun itu), anda tidak perlu menulis ulang sama sekali, anda bisa menggunakan dependency injection tanpa masalah.
## Menginstansiasi method
Berikut adalah metode dan parameternya yang dapat dikonfigurasi:
### Get.put()
Cara paling umum untuk memasukkan dependensi, untuk kontroler dari view anda contohnya.
```dart
Get.put<SomeClass>(SomeClass());
Get.put<LoginController>(LoginController(), permanent: true);
Get.put<ListItemController>(ListItemController, tag: "some unique string");
```
Berikut adalah semua opsi yang bisa anda atur ketika menggunakan put:
```dart
Get.put<S>(
// wajib: kelas yang ingin anda simpan, seperti controller, atau apapun
// catatan: "S" menandakan bahwa tipenya bisa jadi sebuah kelas dari tipe apapun.
S dependency
// opsional: ini digunakan ketika anda ingin memasukkan banyak kelas yang memiliki tipe yang sama.
// berhubung normalnya anda memanggil kelas menggunakan Get.find<Controller>(),
// anda perlu menggunakan tag untuk menandai instance mana yang anda butuhkan
// tag harus unik, dan bertipe String.
String tag,
// opsional: secara default, get akan men-dispose instance setelah tidak digunakan lagi (contoh,
// sebuah controller dari view yang ditutup), tapi mungkin anda membutuhkannya untuk digunakan
// ditempat lain di aplikasi anda, contohnya seperti sebuah instance dari SharedPreference, atau yang lain.
// Maka anda perlu ini
// nilai defaultnya adalah false
bool permanent = false,
// opsional: memungkinkan anda setelah menggunakan kelas abstrak didalam test, menggantinya dengan yang lain dan mengikuti testnya.
// nilai defaultnya adalah false
bool overrideAbstract = false,
// opsional: memungkinkan anda untuk memasukkan dependensi menggunakan fungsi daripada dependensi itu sendiri.
// ini jarang dipakai.
InstanceBuilderCallback<S> builder,
)
```
### Get.lazyPut
Anda bisa melakukan lazyload terhadap sebuah dependensi supaya dependensi tersebut terinstansiasi hanya ketika digunakan saja. Sangat berguna untuk kelas komputasional yang "mahal" atau jika anda ingin menginstansiasi beberapa kelas hanya dalam satu lokasi (seperti pada kelas Bindings) dan anda tahu anda tidak akan menggunakannya secara langsung.
```dart
/// ApiMock hanya akan dipanggil ketika seseorang menggunakan Get.find<ApiMock> pertama kali.
Get.lazyPut<ApiMock>(() => ApiMock());
Get.lazyPut<FirebaseAuth>(
() {
// ... beberapa logic jika diperlukan..
return FirebaseAuth();
},
tag: Math.random().toString(),
fenix: true
)
Get.lazyPut<Controller>( () => Controller() )
```
Berikut adalah semua opsi yang bisa anda atur ketika menggunakan lazyPut:
```dart
Get.lazyPut<S>(
// wajib: sebuah method yang akan di eksekusi ketika kelas anda dipanggil untuk pertama kali
InstanceBuilderCallback builder,
// opsional: sama seperti Get.put(), ini digunakan ketika anda menginginkan banyak instance berbeda dengan kelas yang sama
// harus unik dan harus String.
String tag,
// opsional: Mirip seperti "permanent", bedanya adalah instance akan dihapus ketika tidak
// digunakan, tetapi ketika diperlukan lagi, Get akan membuat ulang instance yang sama,
// seperti "SmartManagement.keepFactory" pada bindings api.
bool fenix = false
)
```
### Get.putAsync
Jika anda ingin mendaftarkan instance yang asynchronous, anda bisa menggunakan `Get.putAsync()`:
```dart
Get.putAsync<SharedPreferences>(() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setInt('counter', 12345);
return prefs;
});
Get.putAsync<YourAsyncClass>( () async => await YourAsyncClass() )
```
Berikut adalah semua opsi yang anda bisa atur ketika menggunakan putAsync:
```dart
Get.putAsync<S>(
// wajib: sebuah async method yang akan di eksekusi untuk menginstansiasi kelas anda
AsyncInstanceBuilderCallback<S> builder,
// opsional: sama seperti Get.put(), ini digunakan ketika anda menginginkan banyak instance berbeda dengan kelas yang sama
// harus unik dan harus String.
String tag,
// opsional: sama seperti Get.put(), digunakan ketika anda ingin mempertahankan
// instance tersebut (keep-alive) untuk digunakan diseluruh aplikasi anda.
// nilai defaultnya adalah false
bool permanent = false
)
```
### Get.create
Yang satu ini agak rumit. Penjelasan lebih detail tentang ini dan perbedaannya dengan yang lain bisa ditemukan di sesi [Perbedaan antar method](#perbedaan-antar-method).
```dart
Get.create<SomeClass>(() => SomeClass());
Get.create<LoginController>(() => LoginController());
```
Berikut adalah semua opsi yang bisa anda atur ketika menggunakan create:
```dart
Get.create<S>(
// diperlukan: sebuah fungsi yang mereturn sebuah kelas yang akan "terfabrikasi" setiap
// kali `Get.find()` dipanggil
// Contoh: Get.create<YourClass>(() => YourClass())
FcBuilderFunc<S> builder,
// opsional: sama seperti Get.put(), ini digunakan ketika anda menginginkan
// banyak instance berbeda dengan kelas yang sama.
// Berguna dalam kasus ketika anda memiliki sebuah list yang setiap isinya membutuhkan
// controllernya masing-masing.
// Harus unik dan harus String. Cukup ganti `tag` menjadi `name`.
String name,
// opsional: sama seperti Get.put(), digunakan ketika anda ingin mempertahankan
// instance tersebut (keep-alive) untuk digunakan diseluruh aplikasi anda.
// Untuk Get.create, `permanent` nilainya `true` secara default.
bool permanent = true
```
## Menggunakan method/kelas yang terinstansiasi
Bayangkan anda bernavigasi melewati route yang sangat banyak, dan anda membutuhkan data yang tertinggal didalam controller jauh di belakang route sebelumnya, anda akan butuh state manager dikombinasikan dengan Provider atau Get_it, benar kan? Tidak dengan Get. Anda hanya perlu meminta Get untuk "menemukan" controllernya, anda tidak perlu dependensi tambahan:
```dart
final controller = Get.find<Controller>();
// ATAU
Controller controller = Get.find();
// Ya, terlihat seperti Sulap, Get akan menemukan controller anda, dan akan mengantarkannya ke lokasi anda.
// Anda bisa memiliki 1 juta controller terinisialisasi, Get akan selalu memberimu controller yang tepat.
```
Dan setelahnya anda bisa memperoleh data yang tertinggal sebelumnya:
```dart
Text(controller.textFromApi);
```
Berhubung value yang direturn adalah sebuah kelas normal, anda bisa melakukan apapun yang anda mau:
```dart
int count = Get.find<SharedPreferences>().getInt('counter');
print(count); // keluaran: 12345
```
Untuk menghapus sebuah instance dari Get:
```dart
Get.delete<Controller>(); // biasanya anda tidak perlu melakukan ini karena GetX sudah melakukannya untuk anda
```
## Perbedaan antar method
Sebelum kita mulai, mari kita bahas tentang `fenix` dari Get.lazyPut dan `permanent` dari method lainnya.
Perbedaan mendasar diantara `permanent` dan `fenix` adalah bagaimana anda ingin menyimpannya (kelas anda).
Menguatkan: secara default, GetX menghapus instance ketika tidak digunakan.
Artinya: Jika screen 1 memiliki controller 1, dan screen 2 memiliki controller 2, dan anda menghapus route pertama dari stack, (seperti pada halnya anda menggunakan `Get.off()` atau `Get.offNamed()`), controller 1 akan kehilangan status kegunaannya dan akan dihapus.
Tapi jika anda menggunakan `permanent:true`, maka controller tidak akan dihapus pada saat berpindah halaman - yang mana sangat berguna untuk service yang ingin anda pertahankan supaya tetap ada (keep-alive) diseluruh aplikasi anda.
`fenix` di sisi lain adalah sebuah service yang anda tidak perlu khawatir kehilangan ketika berpindah antar halaman, tetapi ketika anda membutuhkan service tersebut, anda berekspektasi bahwa service tersebut masih ada. Walaupun sebenarnya, controller/service/class tetap ter-dispose, dan ketika anda membutuhkannya, dia akan membuat ulang (dari abu-nya) sebuah instance baru.
Lanjut dengan perbedaan antar method:
- Get.put dan Get.putAsync mengikuti urutan pembuatan yang sama, bedanya, yang kedua menggunakan asynchronous method: kedua method tersebut membuat dan menginisialisasi sebuah instance. Dimasukkan secara langsung kedalam memori, menggunakan method internal `insert` dengan parameter `permanent:false` dan `isSingleton:true` (isSingleton parameter ini hanya hanya bertujuan untuk membedakan apakah harus menggunakannya pada `dependency` atau menggunakannya pada `FcBuilderFunc`). Setelah itu, `Get.find()` dipanggil dan segera menginisialisasi instance yang ada didalam memori.
- Get.create: seperti namanya, dia akan "membuat" dependensi anda! Mirip seperti `Get.put()`, dia juga memanggil metode internal `insert` untuk menginstansiasi. Namun mengubah `permanent` menjadi true dan `isSingleton` menjadi false (karena kita "membuat" dependensi, tidak ada cara untuk menjadikannya sebagai singleton, inilah kenapa nilainya false). Dan karena dia memiliki `permanent:true`, kita secara default memiliki keuntungan untuk tidak kehilangannya pada saat berpindah halaman! Dan juga, `Get.find()` tidak dipanggil secara langsung, dia menunggu untuk digunakan disuatu halaman untuk dipanggil. Ini dibuat dengan cara tersebut supaya bisa menggunakan parameter `permanent`, sementara itu, perlu diketahui, `Get.create()` dibuat dengan tujuan untuk membuat instance yang tidak dapat di-share, tetapi juga tidak ter-dispose, seperti contohnya sebuah button didalam ListView, dan anda menginginkan sebuah instance unik untuk list tersebut - karena itu, Get.create harus digunakan bersamaan dengan GetWidget.
- Get.lazyPut: seperti namanya, ini membuat "lazy process". Instance nya dibuat, namun tidak digunakan secara langsung, dia menunggu untuk dipanggil. Bertentangan dengan metode lain, `insert` tidak dipanggil disini. Sebaliknya, instance dimasukkan ke bagian lain didalam memori, bagian yang bertanggung jawab untuk memberi tahu apakah instance bisa dibuat ulang atau tidak, mari kita sebut itu sebagai "factory". Jika kita ingin membuat sesuatu untuk digunakan nanti, ini tidak akan tercampur dengan yang digunakan sekarang. Dan disini adalah dimana "sihir" `fenix` terjadi: jika anda memilih untuk membiarkan `fenix: false`, dan `smartManagement` bukan `keepFactory`, maka ketika menggunakan `Get.find`, instance akan mengubah posisinya didalam memori supaya tersedia di bagian terpisah ini, bahkan menuju ke area umum, untuk dipanggil lagi di masa yang akan datang.
## Bindings
Salah satu dari perbedaan besar dari package ini, mungkin, adalah suatu kemungkinan untuk mengintegrasikan route, state manager, dan dependency manager secara penuh.
Ketika route dihapus dari Stack, semua kontroler, variabel, dan instance yang berhubungan dengan itu akan dihapus dari memori. Jika anda menggunakan stream atau timer, mereka akan ditutup secara otomatis, dan anda tidak perlu khawatir tentang hal itu.
Di versi 2.10, Get benar-benar mengimplementasi Bindings API. Sekarang anda tidak perlu menggunakan init method. Bahkan anda tidak perlu mengetik controller yang ingin anda tuju jika anda mau. Anda bisa memulai controller dan service di tempat yang tepat untuk itu.
Binding class adalah sebuah kelas yang memisahkan dependency injection, sembari "mengaitkan" route ke state manager dan dependency manager.
Ini memungkinkan Get untuk mengetahui tampilan mana yang sedang ditampilkan ketika controller yang bersangkutan digunakan dan untuk mengetahui bagaimana cara men-dispose nya.
Sebagai tambahan, Binding class memungkinkan anda untuk memiliki kontrol terhadap konfigurasi SmartManager. Anda bisa mengkonfigurasi dependensi anda untuk diurutkan ketika penghapusan route dari stack, atau ketika widget yang digunakan diletakkan, atau tidak keduanya. Anda akan memiliki dependensi management pintar yang bekerja untuk anda, tapi meski begitu, anda bisa mengkonfigurasinya sesuka hati anda.
### Bindings class
- Buat sebuah kelas yang meng-implements Bindings
```dart
class HomeBinding implements Bindings {}
```
IDE anda akan secara otomatis meminta anda untuk meng-override "dependencies method", dan anda hanya perlu klik ikon lampu, override method nya, dan masukkan semua kelas yang akan anda gunakan dalam route tersebut:
```dart
class HomeBinding implements Bindings {
@override
void dependencies() {
Get.lazyPut<HomeController>(() => HomeController());
Get.put<Service>(()=> Api());
}
}
class DetailsBinding implements Bindings {
@override
void dependencies() {
Get.lazyPut<DetailsController>(() => DetailsController());
}
}
```
Sekarang anda hanya perlu memberi tahu route anda, bahwa anda akan menggunakan binding tersebut untuk membuat koneksi diantara route manager, dependency manager dan state.
- Menggunakan named route:
```dart
getPages: [
GetPage(
name: '/',
page: () => HomeView(),
binding: HomeBinding(),
),
GetPage(
name: '/details',
page: () => DetailsView(),
binding: DetailsBinding(),
),
];
```
- Menggunakan normal route:
```dart
Get.to(Home(), binding: HomeBinding());
Get.to(DetailsView(), binding: DetailsBinding())
```
Disana, anda tidak perlu lagi khawatir tentang manajemen memori aplikasi anda, Get akan melakukannya untuk anda.
Binding akan dipanggil ketika route dipanggil, anda juga bisa membuat sebuah "initialBinding" di GetMaterialApp dan memasukkan semua dependensi yang diperlukan.
```dart
GetMaterialApp(
initialBinding: SampleBind(),
home: Home(),
);
```
### BindingsBuilder
Secara default, untuk membuat sebuah binding adalah dengan membuat kelas baru yang meng-implements Bindings.
Secara alternatif, anda bisa menggunakan `BindingsBuilder` untuk menginstansiasi apapun yang anda mau melalui sebuah fungsi callback.
Example:
```dart
getPages: [
GetPage(
name: '/',
page: () => HomeView(),
binding: BindingsBuilder(() {
Get.lazyPut<ControllerX>(() => ControllerX());
Get.put<Service>(()=> Api());
}),
),
GetPage(
name: '/details',
page: () => DetailsView(),
binding: BindingsBuilder(() {
Get.lazyPut<DetailsController>(() => DetailsController());
}),
),
];
```
Dengan cara ini, anda bisa menghindari membuat satu kelas Binding untuk setiap route yang anda buat, membuatnya jadi semakin simpel.
Kedua cara tersebut bekerja secara sempurna dan kami ingin anda menggunakan yang manapun yang anda rasa cocok untuk anda.
### SmartManagement
GetX secara default men-dispose controller yang tidak digunakan dari memori, bahkan jika kegagalan terjadi dan widget yang menggunakannya tidak ter-dispose dengan benar.
Ini adalah apa yang disebut dengan `full` mode dari sebuah dependency management.
Tetapi jika anda ingin mengubah cara kerja GetX dalam mengontrol "disposal" terhadap kelas, anda memiliki kelas `SmartManagement` yang anda bisa atur perilakunya.
#### Cara mengubahnya
Jika anda ingin mengubah konfigurasi ini (yang biasanya tidak diperlukan), ini caranya:
```dart
void main () {
runApp(
GetMaterialApp(
smartManagement: SmartManagement.onlyBuilders // Disini
home: Home(),
)
)
}
```
#### SmartManagement.full
Ini adalah pengaturan default. Dispose semua kelas yang tidak digunakan dan tidak ditandai sebagai permanent. Di kebanyakan kasus anda ingin konfigurasi ini tidak disentuh. Jika anda baru di GetX, jangan mengubahnya.
#### SmartManagement.onlyBuilders
Dengan opsi ini, hanya controller yang dimulai didalam `init:` atau yang ter-load kedalam sebuah Binding dengan `Get.lazyPut()`, yang akan di dispose.
Jika anda menggunakan `Get.put()` atau `Get.putAsync()` atau cara lain, SmartManagement tidak akan memiliki izin untuk meng-exclude dependensi ini.
Dengan perilaku default, bahkan widget yang terinstansiasi dengan "Get.put" akan dihapus, tidak seperti SmartManagement.onlyBuilders.
#### SmartManagement.keepFactory
Sama seperti SmartManagement.full, ini akan menghapus semua dependensi ketika tidak digunakan lagi. Meski begitu, ini akan menyimpan factory mereka, yang artinya dia akan membuat ulang dependensi jika anda membutuhkannya lagi.
### Cara kerja bindings dibalik layar
Bindings membuat factory sementara, yang mana dibuat ketika anda meng-klik untuk pindah ke halaman lain, dan akan dihapus segera setelah animasi perpindahan halaman terjadi.
Ini terjadi begitu cepat bahkan analyzer sekalipun tidak bisa meregistrasikannya.
Ketika anda kembali ke halaman itu lagi, factory baru akan dipanggil, jadi ini lebih disarankan daripada menggunakan SmartManagement.keepFactory, tapi jika anda tidak ingin membuat Bindings, atau ingin menyimpan semua dependensi didalam Binding yang sama, itu akan sangat membantu.
Konsumsi memori terhadap Factory sangatlah kecil, mereka tidak memegang instance, hanya sebuah fungsi dengan "bentukan" dari kelas yang anda inginkan.
Ini sangat menghemat penggunaan memori, tetapi karena tujuan dari lib ini adalah untuk mendapatkan performa maksimum dan menggunakan resource seminimum mungkin, Get menghapus bahkan si factory itu sendiri secara default.
Gunakan mana yang anda rasa mudah untuk anda.
## Catatan
- JANGAN GUNAKAN SmartManagement.keepFactory jika anda menggunakan lebih dari satu Bindings. Ini dedesain untuk digunakan tanpa Bindings, atau dengan satu Binding terhubung dengan initialBinding milik GetMaterialApp.
- Menggunakan Binding sifatnya opsional, jika anda mau, anda bisa menggunakan `Get.put()` dan `Get.find()` untuk kelas yang menggunakan controller tanpa masalah.
Meski begitu, jika anda bekerja dengan Service atau segala jenis abstraksi lain, Saya merekomendasikan menggunakan Bindings supaya pengorganisiran menjadi lebih baik.

View File

@@ -0,0 +1,564 @@
- [Route Management](#route-management)
- [Cara pakai](#cara-pakai)
- [Navigasi tanpa named route](#navigasi-tanpa-named-route)
- [Navigasi menggunakan named route](#navigasi-menggunakan-named-route)
- [Mengirim data ke named route](#mengirim-data-ke-named-route)
- [Tautan URL dinamis](#tautan-url-dinamis)
- [Middleware](#middleware)
- [Navigasi tanpa konteks](#navigasi-tanpa-konteks)
- [SnackBar](#snackbar)
- [Dialog](#dialog)
- [BottomSheet](#bottomsheet)
- [Navigasi Bersarang](#navigasi-bersarang)
# Route Management
Ini adalah penjelasan lengkap mengenai route management di GetX.
## Cara pakai
Tambahkan ini kedalam pubspec.yaml anda:
```yaml
dependencies:
get:
```
Jika anda akan menggunakan route/snackbar/dialog/bottomsheet tanpa konteks, atau menggunakan high-level API dari Get, cukup tambahkan "Get" sebelum MaterialApp, mengubahnya menjadi GetMaterialApp, dan selamat menikmati!
```dart
GetMaterialApp( // Sebelumnya: MaterialApp(
home: MyHome(),
)
```
## Navigasi tanpa named route
Untuk pindah ke halaman baru:
```dart
Get.to(NextScreen());
```
Untuk menutup snackbar, dialog, bottomsheet, atau apapun yang normalnya anda tutup menggunakan Navigator.pop(context);
```dart
Get.back();
```
Untuk pergi ke halaman baru dan mencegah user kembali ke halaman sebelumnya (biasanya digunakan untuk SplashScreen, LoginScreen, dsb).
```dart
Get.off(NextScreen());
```
Untuk pergi ke halaman baru dan batalkan navigasi sebelumnya (berguna untuk shopping cart, polls, dan test).
```dart
Get.offAll(NextScreen());
```
Untuk pergi ke halaman baru dan menerima atau memperbarui data segera setelah anda kembali dari halaman tersebut:
```dart
var data = await Get.to(Payment());
```
pada halaman lain, kirim data ke halaman sebelumnya:
```dart
Get.back(result: 'success');
```
Lalu gunakan:
contoh:
```dart
if(data == 'success') madeAnything();
```
Bukankah anda ingin mempelajari sintaks kami?
Cukup ubah Navigator (uppercase) ke navigator (lowercase), dan anda akan mendapatkan semua fungsi standar navigasi, tanpa harus menggunakan konteks.
Contoh:
```dart
// Navigator bawaan Flutter
Navigator.of(context).push(
context,
MaterialPageRoute(
builder: (BuildContext context) {
return HomePage();
},
),
);
// Get menggunakan sintaks Flutter tanpa membutuhkan konteks.
navigator.push(
MaterialPageRoute(
builder: (_) {
return HomePage();
},
),
);
// Sintaks Get (Lebih baik, tapi anda juga berhak untuk tidak setuju)
Get.to(HomePage());
```
## Navigasi menggunakan named route
- Jika anda lebih suka bernavigasi menggunakan namedRoutes, Get juga bisa melakukannya.
Untuk pindah ke halaman nextScreen
```dart
Get.toNamed("/NextScreen");
```
Untuk pindah dan hapus halaman sebelumnya dari widget tree.
```dart
Get.offNamed("/NextScreen");
```
Untuk pindah dan hapus semua halaman sebelumnya dari widget tree.
```dart
Get.offAllNamed("/NextScreen");
```
Untuk mendifinisikan route, gunakan GetMaterialApp:
```dart
void main() {
runApp(
GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => MyHomePage()),
GetPage(name: '/second', page: () => Second()),
GetPage(
name: '/third',
page: () => Third(),
transition: Transition.zoom
),
],
)
);
}
```
Untuk menangani navigasi ke route yang tidak terdefinisi (404), anda bisa mendefinisikan sebuah halaman unknownRoute didalam GetMaterialApp.
```dart
void main() {
runApp(
GetMaterialApp(
unknownRoute: GetPage(name: '/notfound', page: () => UnknownRoutePage()),
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => MyHomePage()),
GetPage(name: '/second', page: () => Second()),
],
)
);
}
```
### Mengirim data ke named route
Cukup kirim apa yang anda mau sebagai arguments. Get menerima apapun disitu, baik dalam bentuk String, Map, List atau bahkan instance dari sebuah Kelas.
```dart
Get.toNamed("/NextScreen", arguments: 'Get is the best');
```
di dalam kelas atau controller anda:
```dart
print(Get.arguments);
// keluaran: Get is the best
```
### Tautan URL dinamis
Get menawarkan tautan URL dinamis lebih lanjut sama seperti di Web. Para Web developer mungkin sudah menginginkan fitur ini di Flutter, dan mungkin juga sering melihat sebuah package menjanjikan fitur ini dan mengantarkan sintaks yang benar benar berbeda dari sebuah URL yang kita miliki di web, Get juga menyelesaikan masalah ini.
```dart
Get.offAllNamed("/NextScreen?device=phone&id=354&name=Enzo");
```
didalam controller/bloc/stateful/stateless class anda:
```dart
print(Get.parameters['id']);
// keluaran: 354
print(Get.parameters['name']);
// keluaran: Enzo
```
Anda juga bisa menerima NamedParameters dengan Get dengan mudah:
```dart
void main() {
runApp(
GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(
name: '/',
page: () => MyHomePage(),
),
GetPage(
name: '/profile/',
page: () => MyProfile(),
),
// Anda bisa mendefinisikan halaman berbeda untuk routes dengan arguments,
// dan yang lainnya tanpa arguments, namun untuk itu anda perlu slash '/'
// pada route yang tidak menerima arguments seperti diatas.
GetPage(
name: '/profile/:user',
page: () => UserProfile(),
),
GetPage(
name: '/third',
page: () => Third(),
transition: Transition.cupertino
),
],
)
);
}
```
Kirim data ke named route
```dart
Get.toNamed("/profile/34954");
```
Pada halaman kedua, ambil data menggunakan parameter
```dart
print(Get.parameters['user']);
// keluaran: 34954
```
atau kirim beberapa parameter seperti ini
```dart
Get.toNamed("/profile/34954?flag=true");
```
Pada layar kedua, ambil data berdasarkan parameter seperti biasanya
```dart
print(Get.parameters['user']);
print(Get.parameters['flag']);
// keluaran: 34954 true
```
Dan sekarang, yang anda perlu lakukan adalah menggunakan Get.toNamed() untuk bernavigasi ke named route anda, tanpa konteks (anda bisa memanggil route secara langsung dari kelas BLoC atau Controller), dan ketika aplikasi anda di-compile di web, route anda akan muncul di url <3
### Middleware
Jika anda ingin me-listen sebuah Get event untuk melakukan sebuah action, anda bisa menggunakan routingCallback didalamnya.
```dart
GetMaterialApp(
routingCallback: (routing) {
if(routing.current == '/second'){
openAds();
}
}
)
```
Jika anda tidak menggunakan GetMaterialApp, anda bisa menggunakan API untuk mengaitkan Middleware observer secara manual.
```dart
void main() {
runApp(
MaterialApp(
onGenerateRoute: Router.generateRoute,
initialRoute: "/",
navigatorKey: Get.key,
navigatorObservers: [
GetObserver(MiddleWare.observer), // Disini
],
),
);
}
```
Membuat sebuah kelas Middleware
```dart
class MiddleWare {
static observer(Routing routing) {
/// Anda bisa me-listen sebuah route, snackbar, dialog, dan bottomsheet disetiap halaman.
/// Jika anda harus memasukkan salah satu dari 3 event tersebut secara langsung disini,
/// anda perlu menyebutkan bahwa event tersebut != (tidak sama dengan) apa yang mau anda lakukan.
if (routing.current == '/second' && !routing.isSnackbar) {
Get.snackbar("Halo", "Anda sedang berada di route kedua");
} else if (routing.current =='/third'){
print('route terakhir dipanggil');
}
}
}
```
Sekarang, gunakan Get dalam kode anda:
```dart
class First extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.add),
onPressed: () {
Get.snackbar("halo", "saya adalah snackbar modern");
},
),
title: Text('Halaman Pertama'),
),
body: Center(
child: ElevatedButton(
child: Text('Pindah halaman'),
onPressed: () {
Get.toNamed("/second");
},
),
),
);
}
}
class Second extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.add),
onPressed: () {
Get.snackbar("halo", "saya adalah snackbar modern");
},
),
title: Text('Halaman kedua'),
),
body: Center(
child: ElevatedButton(
child: Text('Pindah halaman'),
onPressed: () {
Get.toNamed("/third");
},
),
),
);
}
}
class Third extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Halaman ketiga"),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Get.back();
},
child: Text('Kembali!'),
),
),
);
}
}
```
## Navigasi tanpa konteks
### SnackBar
Untuk mendapatkan SnackBar sederhana dengan Flutter, anda harus mendapatkan konteks dari sebuah Scaffold, atau anda harus menggunakan GlobalKey yang dikaitkan pada Scaffold anda
```dart
final snackBar = SnackBar(
content: Text('Halo!'),
action: SnackBarAction(
label: 'Saya adalah snackbar tua yang jelek :(',
onPressed: () {}
),
);
// Temukan Scaffold didalam widget tree dan gunakan itu
// untuk menampilkan snackbar
Scaffold.of(context).showSnackBar(snackBar);
```
Dengan Get:
```dart
Get.snackbar('Halo', 'Saya adalah snackbar modern');
```
Dengan Get, yang anda butuhkan hanya memanggil Get.snackbar darimanapun di kode anda atau menyesuaikannya sesuka hati anda!
```dart
Get.snackbar(
"Halo, saya snackbar milik Get!", // judul
"Sulit dipercaya! Saya menggunakan SnackBar tanpa konteks, tanpa boilerplate, tanpa Scaffold, ini benar benar keren!", // pesan
icon: Icon(Icons.alarm),
shouldIconPulse: true,
onTap: () {},
barBlur: 20,
isDismissible: true,
duration: Duration(seconds: 3),
);
////////// SEMUA FITUR //////////
// Color colorText,
// Duration duration,
// SnackPosition snackPosition,
// Widget titleText,
// Widget messageText,
// bool instantInit,
// Widget icon,
// bool shouldIconPulse,
// double maxWidth,
// EdgeInsets margin,
// EdgeInsets padding,
// double borderRadius,
// Color borderColor,
// double borderWidth,
// Color backgroundColor,
// Color leftBarIndicatorColor,
// List<BoxShadow> boxShadows,
// Gradient backgroundGradient,
// TextButton mainButton,
// OnTap onTap,
// bool isDismissible,
// bool showProgressIndicator,
// AnimationController progressIndicatorController,
// Color progressIndicatorBackgroundColor,
// Animation<Color> progressIndicatorValueColor,
// SnackStyle snackStyle,
// Curve forwardAnimationCurve,
// Curve reverseAnimationCurve,
// Duration animationDuration,
// double barBlur,
// double overlayBlur,
// Color overlayColor,
// Form userInputForm
///////////////////////////////////
```
Jika anda lebih menyukai snackbar tradisional, atau ingin menyesuaikannya sendiri dari awal, termasuk menambahkan hanya satu baris (Get.snackbar memanfaatkan title dan message yang diperlukan), anda bisa gunakan
`Get.rawSnackbar();` yang akan menyediakan RAW API untuk Get.snackbar yang dibuat.
### Dialog
Untuk membuka dialog:
```dart
Get.dialog(YourDialogWidget());
```
Untuk membuka default dialog:
```dart
Get.defaultDialog(
onConfirm: () => print("Ok"),
middleText: "Dialog made in 3 lines of code"
);
```
Anda juga bisa menggunakan Get.generalDialog daripada showGeneralDialog.
Untuk semua widget dialog di Flutter, termasuk cupertino, anda bisa menggunakan Get.overlayContext daripada context, dan membukanya darimanapun di kode anda.
Untuk widget yang tidak menggunakan Overlay, anda bisa menggunakan Get.context.
Kedua konteks akan bekerja dalam 99% kasus untuk me-replace konteks dari UI anda, kecuali untuk kasus dimana inheritedWidget digunakan tanpa konteks navigasi.
### BottomSheet
Get.bottomSheet sama seperti showModalBottomSheet, tapi tidak membutuhkan konteks.
```dart
Get.bottomSheet(
Container(
child: Wrap(
children: <Widget>[
ListTile(
leading: Icon(Icons.music_note),
title: Text('Music'),
onTap: () {}
),
ListTile(
leading: Icon(Icons.videocam),
title: Text('Video'),
onTap: () {},
),
],
),
)
);
```
## Navigasi Bersarang
Get membuat navigasi bersarang milik Flutter menjadi lebih mudah.
Anda tidak perlu konteks, dan anda akan menemukan stack navigasi melalui Id.
- CATATAN: Membuat stack navigasi parallel bisa jadi berbahaya. Sebaiknya hindari penggunaan Navigasi Bersarang, atau gunakan dengan bijak. Jika proyek anda membutuhkannya, silahkan, tapi mohon di ingat bahwa menyimpan lebih dari satu navigation stack didalam memori mungkin bukan ide yang bagus untuk konsumsi RAM.
Lihat betapa sederhananya ini:
```dart
Navigator(
key: Get.nestedKey(1), // buat sebuah key menggunakan index
initialRoute: '/',
onGenerateRoute: (settings) {
if (settings.name == '/') {
return GetPageRoute(
page: () => Scaffold(
appBar: AppBar(
title: Text("Main"),
),
body: Center(
child: TextButton(
color: Colors.blue,
onPressed: () {
Get.toNamed('/second', id:1); // pindah ke halaman bersarang anda menggunakan index
},
child: Text("Go to second"),
),
),
),
);
} else if (settings.name == '/second') {
return GetPageRoute(
page: () => Center(
child: Scaffold(
appBar: AppBar(
title: Text("Main"),
),
body: Center(
child: Text("second")
),
),
),
);
}
}
),
```

View File

@@ -0,0 +1,746 @@
- [State Management](#state-management)
- [Reactive State Manager](#reactive-state-manager)
- [Keuntungan](#keuntungan)
- [Performa Maksimum](#performa-maksimum)
- [Mendeklarasikan reactive variable](#mendeklarasikan-reactive-variable)
- [Memiliki sebuah reactive state itu, mudah](#memiliki-sebuah-reactive-state-itu-mudah)
- [Menggunakan value didalam view](#menggunakan-value-didalam-view)
- [Kondisi untuk rebuild](#kondisi-untuk-rebuild)
- [Dimana .obs bisa digunakan](#dimana-obs-bisa-digunakan)
- [Catatan mengenai List](#catatan-mengenai-list)
- [Mengapa harus menggunakan .value](#mengapa-harus-menggunakan-value)
- [Obx()](#obx)
- [Worker](#worker)
- [Simple State Manager](#simple-state-manager)
- [Keuntungan](#keuntungan-1)
- [Penggunaan](#penggunaan)
- [Bagaimana Get meng-handle controller](#bagaimana-get-meng-handle-controller)
- [Anda tidak membutuhkan StatefulWidget lagi](#anda-tidak-membutuhkan-statefulwidget-lagi)
- [Mengapa GetX ada](#mengapa-getx-ada)
- [Cara lain dalam menggunakannya](#cara-lain-dalam-menggunakannya)
- [Unique ID](#unique-id)
- [Mencampur 2 state manager](#mencampur-2-state-manager)
- [GetBuilder vs GetX vs Obx vs MixinBuilder](#getbuilder-vs-getx-vs-obx-vs-mixinbuilder)
# State Management
GetX tidak menggunakan Stream atau ChangeNotifier seperti state manager lainnya. Mengapa? Selain membangun aplikasi untuk Android, iOS, Web, Linux, MacOS, dan Linux, dengan GEtX anda bisa membangun aplikasi server dengan sintaks yang sama seperti Flutter/GetX. Untuk meningkatkan waktu response dan mengurangi konsumsi RAM, kami menciptakan GetValue dan GetStream, yang memiliki solusi latensi rendah yang dapat memberikan performa yang banyak dengan biaya operasi yang rendah. Kami menggunakan ini sebagai dasar untuk membangun semua resource kami, termasuk state management.
- _Kompleksitas_: Beberapa state manager yang ada bisa dibilang kompleks dan memiliki banyak boilerplate. Dengan GetX anda tidak perlu mendefinisikan sebuah kelas untuk setiap event, kode yang dibuat pun bersih dan jelas, dan anda bisa melakukan banyak hal dengan menulis lebih sedikit kode. Banyak orang menyerah menggunakan Flutter karena hal ini, dan pada akhirnya mereka mendapatkan solusi yang sangat sederhana untuk me-manage state.
- _Tidak ada code generator_: Anda menghabiskan separuh waktu development anda menuliskan logic dari aplikasi yang anda buat. Beberapa state manager bergantung pada code generator yang menghasilkan kode dengan keterbacaan yang rendah. Mengubah sebuah variabel dan perlu menjalankan run build_runner bisa jadi tidak produktif, dan terkadang waktu menunggu setelah sebuah flutter clean akan lama, dan anda harus meminum banyak kopi untuk itu.
Dengan GetX semuanya reactive, dan tidak bergantung pada code generator, meningkatkan produktifitas di segala aspek development anda.
- _Tidak bergantung pada konteks_: Anda mungkin pernah diharuskan untuk mengirim konteks dari view ke sebuah controller, membuat keterkaitan yang tinggi terhadap View yang anda buat dengan Business Logic. Anda mungkin pernah diharuskan untuk menggunakan dependensi untuk suatu tempat yang tidak memiliki konteks, dan harus mengirim koteks tersebut melalui berbagai macam kelas dan fungsi. Hal ini tidak ada di GetX. Anda memiliki akses terhadap controller anda dari controller yang anda buat tanpa konteks apapun. Anda tidak perlu mengirim konteks melalui parameter untuk hal yang secara harfiah tidak diperlukan.
- _Kontrol granular_: Kebanyakan state manager didasari oleh ChangeNotifier. ChangeNotifier akan memberi tahu semua widget yang bergantung padanya ketika notifyListener dipanggil. Jika anda memiliki 40 widget didalam satu halaman, yang mana memiliki variabel dari kelas ChangeNotifier anda, ketika anda memperbarui satu dari mereka, semuanya akan me-rebuild.
Dengan GetX, bahkan nested widget pun dihargai. Jika anda memiliki Obx membungkus ListView anda, dan yang lain membungkus sebuah checkbox didalam ListView tersebut, ketika nilai dari CheckBox berubah, hanya CheckBox tersebut yang akan diperbarui, ketika nilai dari List yang berubah, hanya ListView yang akan diperbarui.
- _Hanya merekonstruksi jika variabelnya BENAR-BENAR berubah_: GetX memiliki kontrol flow, yang artinya jika anda menampilkan Text dengan 'Paola', jika anda mengubah variabel observabel menjadi 'Paola' lagi, widget tidak akan direkonstruksi. Ini karena GetX tahu bahwa 'Paola' sudah ditampilkan di Text, dan tidak akan melakukan rekonstruksi yang tidak diperlukan.
Kebanyakan state manager (jika tidak semuanya) saat ini akan merebuild tampilan.
## Reactive State Manager
Reactive programming bisa meng-alienasi banya orang karena katanya, sulit dimengerti. GetX mengubah reactive programming menjadi sesuatu yang cukup sederhana:
- Anda tidak perlu membuat StreamController.
- Anda tidak perlu membuat StreamBuilder untuk setiap variabel.
- Anda tidak perlu membuat kelas untuk setiap state.
- Anda tidak perlu membuat get untuk sebuah value awal (initial value).
- Anda tidak perlu menggunakan generator kode.
Reactive programming dengan Get semudah menggunakan setState.
Bayangkan anda memiliki variabel nama, dan setiap kali anda mengubahnya, semua widget yang menggunakannya akan berubah secara otomatis.
Ini variabel count anda:
```dart
var name = 'Jonatas Borges';
```
Untuk membuatnya "observable", anda hanya perlu menambahkan ".obs" di belakangnya:
```dart
var name = 'Jonatas Borges'.obs;
```
Selesai! _Sesederhana_ itu.
Mulai saat ini, kami akan mereferensikan variabel reactive-".obs"(ervables) sebagai _Rx_.
Apa yang kami lakukan dibalik layar? Kami membuat sebuah `Stream` dari `String`, mengajukan value awal `"Jonatas Borges"`, kami memberi tahu semua widget yang menggunakan `"Jonatas Borges"` bahwa mereka sekarang "milik" variabel tersebut, dan ketika nilai _Rx_ berubah, mereka harus mengubahnya juga.
Ini adalah **keajaiban dari GetX**, terima kasih untuk kemampuan Dart.
Tapi, seperti yang kita ketahui, sebuah `Widget` hanya bisa dirubah jika lokasinya berada didalam sebuah fungsi, karena kelas static tidak memiliki kemampuan untuk "otomatis berubah"
Anda akan harus untuk membuat `StreamBuilder`, berlangganan ke variabel tersebut untuk "mendengar" perubahan, dan membuat sebuah "cascade" dari nested `StreamBuilder` jika anda ingin mengubah beberapa variabel didalam scope, benar kan?
Tidak, anda tidak perlu `StreamBuilder`, tapi anda benar tentang kelas static.
Yah, didalam view, kita biasanya memiliki banyak boilerplate ketika kita ingin mengubah sebuah Widget secara spesifik, itu adalah cara Flutter.
Dengan **GetX** anda juga bisa melupakan tentang boilerplate code ini.
`StreamBuilder( … )`? `initialValue: …`? `builder: …`? Tidak, anda hanya perlu meletakkan variabel kedalam Widget `Obx()`.
```dart
Obx (() => Text (controller.name));
```
_Apa yang perlu anda ingat?_ Hanya `Obx(() =>`.
Anda hanya mengirim Widget itu melalui sebuah arrow-function kedalam sebuah `Obx()` (sebuah "Pengamat" daripada _Rx_).
`Obx` cukup pintar, dan akan selalu berubah jika value dari `controller.name` berubah.
Jika `name` nilainya `"John"`, dan anda mengubahnya ke `"John"` (`name.value = "John"`), karena `value` nya sama seperti sebelumnya, tidak akan ada perubahan apapun di layar, dan `Obex`, untuk menghemat resource, akan secara sederhana mengabaikan value baru yang diberikan dan tidak akan merebuild Widget. **Keren kan?**
> Lalu, bagaimana jika Saya memiliki 5 variabel _Rx_ (observable) didalam `Obx`?
Dia hanya akan memperbarui ketika **semuanya** berubah.
> Dan jika Saya memiliki 30 variabel didalam sebuah kelas, ketika Saya memperbarui salah satu dari mereka, apakah akan memperbarui **semua** variabel didalam kelas tersebut?
Tidak, hanya **Widget tertentu* yang menggunakan variabel _Rx_ tersebut.
Jadi, **GetX** hanya memperbarui tampilan, ketika nilai dari variabel _Rx_ berubah.
```dart
final isOpen = false.obs;
// TIDAK AKAN terjadi apa apa... nilainya sama.
void onButtonTap() => isOpen.value=false;
```
### Keuntungan
**GetX()** membantu anda ketika anda membutuhkan kontrol **granular** atas apa yang sedang diperbarui.
Jika anda tidak membutuhkan `unique ID`, karena semua variabel anda akan di modifikasi ketika anda melakukan sebuah aksi, maka gunakanlah `GetBuilder`,
karena dia adalah Simple State Updater (didalam block, seperti `setState()`), dibuat hanya dengan beberapa baris kode.
Ini dibuat sederhana, untuk mendapatkan pengaruh CPU yang sedikit, dan hanya untuk memenuhi satu kebutuhan (sebuah _State_ rebuild) dan menggunakan resource se-minimal mungkin.
Jika anda perlu sebuah State Manager yang **powerful**, anda tidak akan salah dengan **GetX**.
Ini tidak bekerja dengan variabel, melainkan __aliran (flows)__, semuanya adalah `Streams` dibalik layar.
Anda bisa menggunakan _rxDart_ bersamaan dengannya, karena semuanya adalah `Streams`,
anda bisa me-listen sebuah `event` dari setiap "variabel _Rx_",
karena semua didalamnya adalah `Streams`.
Ini secara harfiah adalah cara yang digunakan _BLoC_, lebih mudah dari _MobX_, dan tanpa code generator atau decoration.
Anda bisa mengubah **apapun** menjadi sebuah _"Observable"_ hanya dengan `.obs`.
### Performa Maksimum
Selain memiliki sebuah algoritma pintar untuk minimal rebuild, **GetX** menggunakan pembanding
untuk memastikan State nya berubah.
Jika anda mengalami error di aplikasi anda, dan mengirim sebuah duplikat terhadap perubahan State,
**GetX** akan memastikan aplikasi anda tidak crash.
Dengan **GetX**, State hanya berubah ketika `value` nya berubah.
Itu adalah perbedaan utama diantara **GetX**, dan dengan menggunakan _`computed` dari MobX_.
Ketika menggabungkan kedua __observable__, dan salah satunya berubah; listener dari _observable_ itu akan berubah juga.
Dengan **GetX**, jika anda menggabungkan dua variabel, `GetX()` (mirip seperti `Observer()`) hanya akan merebuild jika State nya benar-benar berubah.
### Mendeklarasikan reactive variable
Anda memiliki 3 cara untuk mengubah variabel menjadi sebuah "observable".
1 - Yang pertama adalah dengan menggunakan **`Rx{Type}`**.
```dart
// value awal direkomendasikan, tetapi tidak wajib
final name = RxString('');
final isLogged = RxBool(false);
final count = RxInt(0);
final balance = RxDouble(0.0);
final items = RxList<String>([]);
final myMap = RxMap<String, int>({});
```
2 - Yang kedua adalah dengan menggunakan **`Rx`** dan Darts Generics, `Rx<Type>`
```dart
final name = Rx<String>('');
final isLogged = Rx<Bool>(false);
final count = Rx<Int>(0);
final balance = Rx<Double>(0.0);
final number = Rx<Num>(0)
final items = Rx<List<String>>([]);
final myMap = Rx<Map<String, int>>({});
// Kelas Kustom - ini bisa jadi kelas apapun, secara harfiah
final user = Rx<User>();
```
3 - Yang ketiga, cara yang lebih praktis, mudah dan lebih disukai, cukup tambahkan **`.obs`** sebagai properti dari `value` anda:
```dart
final name = ''.obs;
final isLogged = false.obs;
final count = 0.obs;
final balance = 0.0.obs;
final number = 0.obs;
final items = <String>[].obs;
final myMap = <String, int>{}.obs;
// Kelas Kustom - ini bisa jadi kelas apapun, secara harfiah
final user = User().obs;
```
#### Memiliki sebuah reactive state itu, mudah
Seperti yang kita ketahui, _Dart_ saat ini sedang mempersiapkan ke _null safety_.
Sebagai persiapan, mulai dari sekarang, anda harus selalu memulai variabel _Rx_ anda dengan sebuah **initial value** (nilai awal).
> Mengubah sebuah variabel menjadi sebuah _observable_ + _initial value_ dengan **GetX** adalah cara yang paling sederhana, dan sangat praktis.
Anda hanya cukup menambahkan sebuah "`.obs`" di akhir variabel anda, dan **selesai**, anda telah membuatnya observable,
dan untuk `.value` nya, yah, _initial value_ yang anda berikan.
### Menggunakan value didalam view
```dart
// file controller
final count1 = 0.obs;
final count2 = 0.obs;
int get sum => count1.value + count2.value;
```
```dart
// file view
GetX<Controller>(
builder: (controller) {
print("count 1 rebuild");
return Text('${controller.count1.value}');
},
),
GetX<Controller>(
builder: (controller) {
print("count 2 rebuild");
return Text('${controller.count2.value}');
},
),
GetX<Controller>(
builder: (controller) {
print("count 3 rebuild");
return Text('${controller.sum}');
},
),
```
Jika kita meng-increment `count1.value++`, ini akan mencetak:
- `count 1 rebuild`
- `count 3 rebuild`
karena `count1` memiliki nilai `1`, dan `1 + 0 = 1`, mengubah nilai dari getter `sum`.
Jika kita mengubah `count2.value++`, ini akan mencetak:
- `count 2 rebuild`
- `count 3 rebuild`
karena `count2.value` berubah, dan hasil dari `sum` sekarang adalah `2`.
- CATATAN: Secara default, event yang paling pertama akan merebuild widget, meskipun `value` nya sama.
perilaku ini hadir karena variabel Boolean.
Bayangkan anda melakukan ini:
```dart
var isLogged = false.obs;
```
Dan kemudian, anda melakukan pengecekan apakah user "sudah login" untuk men-trigger sebuah event didalam `ever`.
```dart
@override
onInit(){
ever(isLogged, fireRoute);
isLogged.value = await Preferences.hasToken();
}
fireRoute(logged) {
if (logged) {
Get.off(Home());
} else {
Get.off(Login());
}
}
```
jika `hasToken` nilainya `false`, tidak akan terjadi apa apa kepada `isLogged`, jadi `ever()` tidak akan dipanggil.
Untuk menghindari jenis perilaku ini, perubahan awal terhadap sebuah _observable_ harus selalu men-trigger sebuah event,
bahkan ketika mereka memiliki `.value` yang sama.
Anda bisa menghapus perilaku ini jika anda mau, menggunakan:
`isLogged.firstRebuild = false;`
### Kondisi untuk rebuild
Selain itu, Get menyediakan state kontrol yang telah dipoles. Anda bisa mengkondisikan sebuah event (seperti menambahkan sebuah object kedalam list), dalam suatu kondisi.
```dart
// Parameter pertama: kondisi, harus me-return true atau false
// Parameter kedua: nilai baru yang akan dimasukkan jika kondisinya true
list.addIf(item < limit, item);
```
Tanpa decoration, tanpa code generator, tanpa komplikasi :smile:
Anda tahu counter app milik Flutter? Controller anda mungkin terlihat seperti ini:
```dart
class CountController extends GetxController {
final count = 0.obs;
}
```
Hanya dengan:
```dart
controller.count.value++
```
Anda bisa memperbarui variabel counter di UI anda, tidak perduli dimanapun itu diletakkan.
### Dimana .obs bisa digunakan
Anda bisa mengubah apapun dengan obs. Berikut adalah dua cara untuk melakukannya:
* Anda bisa mengkonversi value kelas anda menjadi obs
```dart
class RxUser {
final name = "Camila".obs;
final age = 18.obs;
}
```
* atau anda bisa mengkonversi seluruh kelasnya menjadi observable
```dart
class User {
User({String name, int age});
var name;
var age;
}
// saat menginstansiasi:
final user = User(name: "Camila", age: 18).obs;
```
### Catatan mengenai List
List, dia observable secara menyeluruh, termasuk objek didalamnya.
Dengan itu, jika anda menambahkan value kedalam list, dia akan secara otomatis merebuild widget yang menggunakannya.
Anda juga tidak perlu menggunakan ".value" ketika mengakses list, API dart yang luar biasa mengizinkan kita untuk menghapusnya.
Sayangnya, tipe data primitif seperti String dan int tidak bisa di extend, membuat penggunaan .value menjadi suatu hal yang wajib, tapi itu bukan masalah jika anda bekerja menggunakan getter dan setter untuk mereka.
```dart
// Didalam Controller
final String title = 'User Info:'.obs
final list = List<User>().obs;
// Didalam View
Text(controller.title.value), // String harus memiliki .value didepannya
ListView.builder (
itemCount: controller.list.length // list tidak perlu itu
)
```
Ketika anda membuat kelas anda sendiri observable, ada berbagai cara untuk memperbaruinya:
```dart
// didalam file model
// kita akan membuat seluruh kelas menjadi observable daripada
// mengimplementasikannya pada masing-masing atribut.
class User() {
User({this.name = '', this.age = 0});
String name;
int age;
}
// didalam file controller
final user = User().obs;
// ketika anda perlu mengupdate variabel user:
user.update( (user) { // parameter ini adalah kelas itu sendiri yang akan anda update
user.name = 'Jonny';
user.age = 18;
});
// cara alternatif untuk melakukannya:
user(User(name: 'João', age: 35));
// didalam view:
Obx(()=> Text("Name ${user.value.name}: Age: ${user.value.age}"))
// anda juga bisa mengakses nilai model tanpa .value:
user().name; // perhatikan bahwa yang digunakan adalah variabel user, bukan kelasnya (variabel memiliki "u" kecil)
```
Anda tidak perlu bekerja dengan sets jika anda tidak mau. Anda bisa menggunakan API "assign" dan "assignAll".
API "assign" akan membersihkan list anda, dan memasukkan satu objek sebagai permulaan.
API "assignAll" akan membersihkan list yang sudah ada dan menambahkan objek iterable yang anda inject kedalamnya.
### Mengapa harus menggunakan .value
Kami bisa saja menghapus kebijakan untuk menggunakan 'value' pada `String` dan `int` dengan decoration sederhana dan code generator, tapi tujuan dari library ini adalah untuk menghindari dependensi eksternal. Kami ingin menawarkan sebuah environment yang siap untuk programming, melibatkan hal-hal penting (route, dependency, dan state management), dengan cara yang sederhana, ringan, dan cepat, tanpa membutuhkan package dari luar.
Anda bisa secara harfial menambahkan 3 huruf ke pubspec anda dan sebuah titik dua dan mulai membuat sebuah program. Semua solusi sudah termasuk secara default, mulai dari route management, hingga state management, menargetkan kemudahan, produktifitas dan performa.
Berat total dari library ini kurang dari satu state manager, meskipun ini adalah solusi komplit, dan inilah yang harus anda pahami.
Jika anda terganggu dengan `.value`, dan menyukai code generator, MobX adalah alternatif yang baik, dan anda bisa menggunakannya bersamaan dengan Get. Untuk kalian yang ingin menambahkan satu dependensi di pubspec dan mulai membuat sebuah program tanpa mengkhawatirkan versi dari sebuah package tidak kompatibel dengan yang lain, atau sebuah error dari perbaruan state datang dari state manager atau dependensi, atau semacamnya, dan tidak mau khawatir tentang ketersediaan controller, ataupun secara harfiah "hanya ingin membuat program", get sudah sangat sempurna.
Jika anda merasa tidak masalah dengan code generator MobX, atau tidak masalah dengan boilerplate dari BLoC, anda bisa menggunakan Get untuk route, dan lupakan bahwa dia memiliki state manager. Get SEM dan RSM lahir karena kebutuhan, perusahaan saya memiliki proyek dengan lebih dari 90 controller, dan code generator sederhananya memakan waktu 30 menit untuk menyelesaikan tugasnya setelah Flutter Clean di komputer yang secara masuk akal bagus, jika proyek anda memiliki 5, 10, hingga 15 controller, state manager apapun akan mensuplai anda dengan baik. Jika anda memiliki proyek yang secara absurd berskala besar, dan code generator adalah masalah untuk anda, maka anggap ini sebagai hadiah.
Jelas, jika seseorang ingin berkontribusi terhadap proyek dan membuat sebuah code generator, atau sejenisnya, Saya akan tautkan kedalam readme ini sebagai alternatif, kebutuhan saya bukan kebutuhan untuk semua developer, namun untuk saat ini Saya mengatakan, sudah ada solusi yang baik diluar sana yang sudah melakukan hal itu, seperti MobX.
### Obx()
Memberi tipe di Get saat menggunakan Binding tidak diperlukan. Anda bisa menggunakan widget Obx daripada GetX yang mana hanya menerima fungsi anonim yang membuat sebuah widget.
Jelas, jika anda tidak menggunakan sebuah tipe, anda harus memiliki sebuah instance dari controller anda untuk menggunakan variabel didalamnya, atau gunakan `Get.find<Controller>()` .value atau Controller.to.value untuk mengambil value.
### Worker
Worker akan membantu anda, men-trigger callback spesifik ketika sebuah event terjadi.
```dart
/// Terpangil setiap kali `count1` berubah.
ever(count1, (_) => print("$_ telah dirubah"));
/// Terpanggil hanya pada saat pertama kali variabel $_ dirubah.
once(count1, (_) => print("$_ telah dirubah sekali"));
/// Anti DDos - Terpangil setiap kali the user berhenti mengetik setelah 1 detik, contoh:
debounce(count1, (_) => print("debouce$_"), time: Duration(seconds: 1));
/// Abaikan semua perubahan selama 1 detik.
interval(count1, (_) => print("interval $_"), time: Duration(seconds: 1));
```
Semua worker (kecuali `debounce`) memiliki sebuah `condition` named parameter, yang bisa jadi sebuah `bool` atau sebuah callback yang mereturn `bool`
`condition` ini terdefinisikan ketika fungsi `callback` di eksekusi.
Semua worker mereturn sebuah instance `Worker`, yang bisa anda gunakan untuk membatalkan worker tersebut (melalui `dispose()`).
- **`ever`**\
terpanggil setiap kali variabel _Rx_ meng-emit value baru.
- **`everAll`**\
Mirip seperti `ever`, tapi menerima `List` sebagai nilai _Rx_, terpanggil setiap kali variabel berubah.
- **`once`**\
Terpanggil hanya sekali pada saat pertama kali variabel berubah.
- **`debounce`**\
'debounce' sangat berguna pada fungsi pencarian, dimana anda hanya ingin API dipanggil ketika user selesai mengetik. Jika user mengetik "Jonny", anda akan pendapatkan 5 pencarian di API, dengan huruf J, o, n, n, dan y. Dengan Get, ini tidak terjadi lagi, karena anda memiliki sebuah "debounce" worker, yang akan men-trigger pada akhir pengetikan.
- **`interval`**\
berbeda dengan debounce. untuk debounce, jika user melakukan 1000 perubahan terhadap sebuah variabel dalam 1 detik, dia akan mengirim hanya yang terakhir setelah waktu yang ditetapkan (defaultnya adalah 800 milisekon).
\
\
Interval bekerja sebaliknya, dia akan mengabaikan semua interaksi user dalam rentang waktu yang ditentukan. Jika anda mengirim event dalam 1 menit, 1000 per detik, debounce akan mengirimkan anda yang terakhir, ketika user berhenti melakukan spam terhadap event. Interval akan mengantar event setiap detik, dan jika diatur menjadi 3 detik, dia akan mengirimkan 20 event setiap menit.
\
\
Ini direkomendasikan untuk menghindari penyalahgunaan, dalam fungsi dimana user bisa secara cepat melakukan klik pada sesuatu untuk mendapatkan sebuah keuntungan (bayangkan jika user bisa mendapat koin dengan meng-klik pada sesuatu, jika dia mengklik 300 kali dalam 1 menit, dia akan mendapatkan 300 koin, menggunakan interval, anda bisa mengatur jangka waktu selama 3 detik, dan meskipun user meng-klik sebanyak 300 kali atau ribuan kali, maksimum koin yang bisa dia dapatkan dalam 1 menit akan selalu 20 koin, bahkan ketika dia meng-klik 1 juta kali sekalipun)
\
\
Debounce cocok sebagai anti-DDos, untuk fungsi seperti search dimana setiap perubahan terhadap onChange akan mengirim sebuah query ke API anda. Debounce akan menunggu user berhenti mengetik nama, untuk membuat sebuah request. Jika ini digunakan pada skenario koin diatas, user hanya akan mendapatkan 1 koin, karena hanya akan di eksekusi ketika user memberi jeda terhadap waktu yang ditentukan.
- CATATAN: Worker harus selalu digunakan ketika memulai sebuah Controller atau Class, jadi dia harus selalu diletakkan didalam onInit (direkomendasikan), Class constructor, atau initState dari StatefulWidget (praktik ini tidak direkomendasikan dalam kebanyakan kasus, dan tidak akan ada side effect).
## Simple State Manager
Get memiliki state manager yang sangat ringan dan mudah, dan tidak menggunakan ChangeNotifier, yang akan memenuhi kebutuhan khususnya untuk mereka yang baru di Flutter, dan tidak akan membuat masalah untuk aplikasi besar.
GetBuilder membidik dengan tepat pada state control multipel. Bayangkan anda menambahkan 30 produk kedalam sebuah keranjang belanja, anda meng-klik "delete" pada salah satu produk tersebut, di waktu yang sama, list, harga, dan badge diperbarui ke angka yang lebih kecil. Pendekatan ini membuat GetBuilder mematikan, karena mengelompokkan state dan mengubah semuanya secara bersamaan dalam satu waktu tanpa "computational logic" untuk itu. Getbuilder dibuat dengan mempertimbangkan situasi seperti ini, untuk perubahan state "ephemeral", anda bisa menggunakan setState dan anda tidak membutuhkan sebuah state manager untuk itu.
Dengan begitu, jika anda menginginkan controller yang bekerja secara individu, anda bisa mengajukan ID untuknya, atau gunakan GetX. Semuanya terserah anda, mengingat bahwa semakin banyak "individual" widget yang anda miliki, performa dari GetX akan semakin terlihat, sementara performa dari GetBuilder harusnya lebih superior, ketika terjadi perubahan state secara multipel.
### Keuntungan
1. Hanya memperbarui widget yang diperlukan
2. Tidak menggunakan changeNotifier, ini adalah state manager yang menggunakan memori lebih kecil (mendekati 0mb).
3. Lupakan StatefulWidget! Dengan Get, anda tidak akan pernah membutuhkannya. Dengan state manager lain, anda mungkin harus menggunakan StatefulWidget untuk mendapatkan sebuah instance milik anda dari Provider, BLoC, MobX Controller, dsb. Tapi pernahkah anda berhenti untuk berfikir bahwa appBar, scaffold, dan kebanyakan widget anda itu stateless? Lalu mengapa menyimpan state dari seluruh kelas, jika anda hanya bisa menyimpan state dari Widget yang stateful? Get menyelesaikan masalah itu, juga. Buat kelas Stateless, buat semuanya stateless. Jika anda butuh update pada satu komponen, bungkus komponen itu dengan GetBuilder, dan state-nya akan di-maintain.
4. Organisir proyek anda secara nyata! Controller tidak seharusnya ada di UI, letakkan TextEditingController anda, atau controller apapun didalam kelas Controller anda.
5. Apakah anda perlu men-trigger sebuah event untuk widget segera setelah dirender? GetBuilder memiliki properti "initState", sama seperti StatefulWidget, dan anda akan bisa memanggil event dari controller anda, langsung dari sana, tidak ada lagi event diletakkan didalam initState anda.
6. Apakah anda perlu men-trigger sebuah action seperti menutup stream, timer, dan semacamnya? GetBuilder juga memiliki properti dispose, dimana anda bisa memanggil event segera setelah widget dihancurkan.
7. Gunakan stream hanya jika dibutuhkan. Anda bisa menggunakan StreamController didalam controller anda secara normal, dan menggunakan StreamBuilder juga secara normal, namun perlu di ingat, sebuah stream cukup memakan memori, reactive programming itu indah, namun anda tidak boleh menyalahgunakannya. 30 stream terbuka secara simultan bisa lebih buruk daripada changeNotifier (dan changeNotifier itu sangat buruk).
8. Perbarui widget tanpa menghabiskan RAM untuk itu. Get menyimpan hanya creator ID milik GetBuilder, dan memperbarui GetBuilder tersebut ketika diperlukan. Konsumsi memori dari get ID storage sangat rendah bahkan untuk ribuan GetBuilder sekalipun. Ketika anda membuat GetBuilder baru, anda sebenarnya berbagi state dari GetBuilder yang memiliki creator ID. State baru tidak dibuat untuk setiap GetBuilder, yang mana menghemat SANGAT BANYAK RAM untuk aplikasi berskala besar. Pada dasarnya, aplikasi anda akan Stateless secara menyeluruh, dan semakin sedikit Widget yang akan Stateful (didalam GetBuilder) akan memiliki satu state, dan oleh karena itu, mengupdate salah satu dari mereka akan mengupdate semuanya. State nya hanya satu.
9. Get maha tahu, dan dalam kebanyakan kasus, dia mengetahui dengan tepat kapan harus menghapus sebuah controller dari memori. Anda tidak perlu khawatir tentang kapan harus men-dispose sebuah controller, Get tahu waktu terbaik untuk melakukannya.
### Penggunaan
```dart
// Buat kelas controller dan extends GetxController
class Controller extends GetxController {
int counter = 0;
void increment() {
counter++;
update(); // gunakan update() untuk mengupdate variabel counter di UI ketika increment dipanggil
}
}
// Pada Stateless/Stateful widget anda, gunakan GetBuilder untuk mengupdate Text ketika increment dipanggil
GetBuilder<Controller>(
init: Controller(), // INISIALISASIKAN CONTROLLER HANYA UNTUK PERTAMA KALI
builder: (_) => Text(
'${_.counter}',
),
)
// Inisialisasi controller anda hanya untuk pertama kali. Kedua kalinya anda menggunakan ReBuilder untuk controller yang sama, dan jangan gunakan itu lagi. Controller anda akan secara otomatis dihapus dari memori segera setelah widget yang ditandai sebagai 'init' di-deploy. Anda tidak perlu khawatir tentang itu, Get akan melakukannya secara otomatis, cukup pastikan anda tidak memulai controller yang sama dua kali.
```
**Selesai!**
- Anda sudah mempelajari bagaimana caranya memanage state menggunakan Get.
- Catatan: Anda mungkin menginginkan organisasi yang lebih besar, dan tidak menggunakan properti init. Untuk itu, anda bisa membuat kelas dan meng-extends kelas Bindings, dan didalamnya, sebutkan controller yang akan dibuat untuk route tersebut. Controller tidak akan dibuat pada waktu itu, sebaliknya, ini hanyalah sebuah statement, jadi pada saat pertama kali anda menggunakan sebuah Controller, Get akan tahu dimana harus mencarinya. Get akan melakukan lazyload, dan akan melanjutkan untuk men-dispose controller yang tidak lagi digunakan. Lihat contoh di pub.dev untuk melihat bagaimana cara kerjanya.
Jika anda bernavigasi ke banyak route dan membutuhkan data dari controller yang sebelumnya anda gunakan, anda hanya perlu menggunakan GetBuilder lagi (tanpa init):
```dart
class OtherClass extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: GetBuilder<Controller>(
builder: (s) => Text('${s.counter}'),
),
),
);
}
```
Jika anda perlu menggunakan controller anda di banyak tempat, dan diluar dari GetBuilder, cukup buat getter didalam controller anda dan anda akan mendapatkannya dengan mudah (atau gunakan `Get.find<Controller>()`)
```dart
class Controller extends GetxController {
/// Anda tidak membutuhkan itu. Saya menyarankan menggunakannya hanya untuk kemudahan sintaks.
/// dengan static method: Controller.to.increment();
/// tanpa static method: Get.find<Controller>().increment();
/// Tidak ada perbedaan dari segi performa, atau efek samping apapun dalam menggunakan kedua sintaks diatas. Yang berbeda hanyalah yang satu tidak memerlukan type, dan yang satu lagi akan di autocomplete oleh IDE.
static Controller get to => Get.find(); // Tambahkan baris ini
int counter = 0;
void increment() {
counter++;
update();
}
}
```
Dan anda bisa mengakses controller secara langsung, dengan cara itu:
```dart
FloatingActionButton(
onPressed: () {
Controller.to.increment(),
} // Ini luar biasa simpel!
child: Text("${Controller.to.counter}"),
),
```
Ketika anda menekan FloatingActionButton, semua widget yang me-listen variabel 'counter' akan diupdate secara otomatis.
### Bagaimana Get meng-handle controller
Anggaplah kita memiliki ini:
`Class A => Class B (memiliki controller X) => Class C (memiliki controller X)`
Di kelas A, controller belum ada di memori, karena anda belum menggunakannya (Get itu lazyload). Di kelas B anda menggunakan controller tersebut, dan controller itu masuk kedalam memori. Di kelas C, anda menggunakan controller yang sama seperti di kelas B, Get akan berbagi state dari controller B dengan controller C, dan controller yang sama akan tetap ada di memori.
Jika anda menutup screen C dan screen B, Get akan secara otomatis menghapus controller X dari memori dan membebaskan resource, karena kelas A tidak menggunakan controller. Jika anda bernavigasi ke kelas B lagi, controller X akan masuk ke memori lagi, jika daripada menuju ke kelas C, anda kembali ke kelas A lagi, Get akan menghapus controller dari memori dengan cara yang sama. Jika kelas C tidak menggunakan controller, dan anda mengeluarkan kelas B dari memori, tidak ada kelas yang menggunakan controller X dan demikian pula kelas itu akan dibuang.
Satu-satunya exception yang bisa mengacau dengan Get, adalah jika anda menghapus B dari route secara tidak sengaja, dan mencoba menggunakan controller di C. Dalam kasus ini, creator ID dari controller yang ada di B terhapus, dan Get diprogram untuk menghapusnya dari memori untuk setiap controller yang tidak memiliki creator ID. Jika anda berniat melakukan ini, tambahkan flag "autoRemove: false" ke GetBuilder di kelas B dan gunakan "adoptID = true;" di GetBuilder pada kelas C.
### Anda tidak membutuhkan StatefulWidget lagi
Menggunakan StatefulWidget berarti menyimpan sebuah state dari seluruh layar secara tidak perlu, meski karena anda perlu untuk merebuild sebuah widget secara minimal, anda akan menyematkannya kedalam Consumer/Observer/BlocProvider/GetBuilder/GetX/Obx, yang mana akan menjadi StatfulWidget yang lain.
StatefulWidget adalah sebuah kelas yang lebih besar daripada StatelessWidget, yang mana akan mengalokasi lebih banyak RAM, dan ini mungkin tidak akan membuat perbedaan secara signifikan antara satu atau dua kelas, tapi itu pasti akan terjadi ketika anda memiliki 100 dari mereka!
Kecuali anda perlu menggunakan mixin, seperti TickerProviderStateMixin, akan sangat tidak dibutuhkan menggunakan StatefulWidget dengan Get.
Anda bisa memanggil semua method dari StatefulWidget secara langsung melalui GetBuilder.
Jika anda perlu memanggil initState() atau dispose() method contohnya, anda bisa memanggilnya secara langsung;
```dart
GetBuilder<Controller>(
initState: (_) => Controller.to.fetchApi(),
dispose: (_) => Controller.to.closeStreams(),
builder: (s) => Text('${s.username}'),
),
```
Cara yang lebih baik daripada ini adalah dengan menggunakan onInit() dan onClose() method secara langsung dari controller anda.
```dart
@override
void onInit() {
fetchApi();
super.onInit();
}
```
- CATATAN: Jika anda ingin memulai sebuah method pada saat controller dipanggil pertama kali, anda TIDAK PERLU menggunakan constructor, faktanya, menggunakan package yang berorientasi pada performa seperti Get, ini berbatas pada bad practice, karena menyimpang dari logic dimana setiap controller dibuat atau dialokasikan (jika anda membuat sebuah instance dari controller, constructornya akan dipanggil dengan segera, anda akan menumpuk controller bahkan sebelum digunakan, anda mengalokasikan memori tanpa digunakan, hal ini benar-benar menyakiti prinsip dasar library ini). method onInit(); dan onClose(); dibuat untuk hal ini, mereka akan dipanggil ketika Controller dibuat, atau digunakan untuk pertama kali, bergantung pada kondisi apakah anda menggunakan Get.lazyPut atau tidak. Jika anda mau, sebagai contoh, membuat sebuah panggilan ke API anda untuk mempopulasikan data, anda bisa lupakan tentang metode lawas initState/dispose, cukup mulai memanggilnya didalam onInit, dan jika anda perlu meng-eksekusi perintah seperti menutup stream, gunakan onClose() untuk hal itu.
### Mengapa GetX ada
Tujuan dari package ini adalah secara tepat memberikan anda sebuah solusi komplit untuk navigasi route, dependency dan state management, menggunakan sedikit mungkin dependensi, dengan tingkat decoupling yang tinggi. Get melibatkan semua Flutter API baik dari level tertinggi dan terendah dalam dirinya sendiri, untuk memastikan bahwa anda bekerja dengan sedikit mungkin keterkaitan (coupling). Kami memusatkan semuanya kedalam satu package, untuk memastikan bahwa anda tidak memiliki keterkaitan (coupling) di proyek anda. Dengan begitu, anda bisa menaruh hanya widget di view anda, dan membiarkan bagian tim anda yang bekerja dengan business logic secara bebas, bekerja dengan business logic tanpa bergantung pada elemen apapun yang ada didalam View. Ini menyediakan lingkungan kerja yang lebih bersih, jadi bagian yang lain dari tim anda bisa bekerja hanya dengan widget, tanpa khawatir tentang mengirim data dari controller anda.
Jadi untuk menyederhanakannya:
Anda tidak perlu memanggil method di initState dan mengirim mereka menggunakan parameter ke controller anda, atau menggunakan constructor didalam controller anda untuk itu, anda memiliki method onInit() yang akan dipanggil di waktu yang tepat untuk memulai service anda.
Anda tidak perlu memanggil perangkatnya, anda memiliki method onClose() yang akan dipanggil di momen yang tepat ketika controller tidak lagi dibutuhkan dan akan dihapus dari memori. Dengan begitu, biarkan views hanya untuk widget, menghindari berbagai macam business logic darinya.
Jangan memanggil dispose method didalam GetxController, itu tidak akan melakukan apa-apa, ingat bahwa controller bukanlah sebuah Widget, anda tidak seharusnya men-"dispose" sebuah controller, dan dia akan secara otomatis dan secara pandai dihapus dari memori oleh Get. Jika anda menggunakan stream didalamnya dan ingin menutupnya, cukup masukkan itu kedalam close method. Contoh:
```dart
class Controller extends GetxController {
StreamController<User> user = StreamController<User>();
StreamController<String> name = StreamController<String>();
/// menutup stream = onClose method, bukan dispose.
@override
void onClose() {
user.close();
name.close();
super.onClose();
}
}
```
Controller life cycle (atau: siklus kehidupan controller):
- onInit() dimana ketika Controller dibuat.
- onClose() dimana ketika Controller ditutup untuk membuat segala jenis perubahan dalam rangka persiapan untuk metode penghapusan
- deleted: anda tidak lagi memiliki akses ke API ini karena secara harfiah menghapusnya dari memori, dan secara harfiah pula itu dihapus, tanpa meninggalkan jejak.
### Cara lain dalam menggunakannya
Anda bisa menggunakan Controller secara langsung didalam GetBuilder value:
```dart
GetBuilder<Controller>(
init: Controller(),
builder: (value) => Text(
'${value.counter}', // Disini
),
),
```
Anda juga mungkin membutuhkan sebuah instance dari controller anda diluar GetBuilder, dan anda bisa menggunakan pendekatan ini:
```dart
class Controller extends GetxController {
static Controller get to => Get.find();
[...]
}
// didalam View:
GetBuilder<Controller>(
init: Controller(), // Gunakan ini hanya untuk pertama kali pada setiap controller
builder: (_) => Text(
'${Controller.to.counter}', // Disini
)
),
```
atau
```dart
class Controller extends GetxController {
// static Controller get to => Get.find(); // tanpa static getter
[...]
}
// didalam stateful/stateless class
GetBuilder<Controller>(
init: Controller(), // Gunakan ini hanya untuk pertama kali pada setiap controller
builder: (_) => Text(
'${Get.find<Controller>().counter}', // Disini
),
),
```
- Anda bisa menggunakan cara "non-canonical" untuk hal ini. Jika anda menggunakan dependensi manager lain, seperti get_it, modular, etc., dan hanya ingin mengirimkan instance controller, anda bisa melakukan ini:
```dart
Controller controller = Controller();
[...]
GetBuilder<Controller>(
init: controller, // Disini
builder: (_) => Text(
'${controller.counter}', // Disini
),
),
```
### Unique ID
Jika anda ingin me-refine update control sebuah widget dengan GetBuilder, anda bisa mengajukan unique ID untuk mereka:
```dart
GetBuilder<Controller>(
id: 'text' // Disini
init: Controller(),
builder: (_) => Text(
'${Get.find<Controller>().counter}',
),
),
```
Dan mengupdatenya dalam bentuk ini:
```dart
update(['text']);
```
Anda juga bisa menerapkan kondisi untuk updatenya:
```dart
update(['text'], counter < 10);
```
GetX melakukan ini secara otomatis dan hanya merekonstruksi widget yang menggunakan variabel persis yang dirubah, jika anda mengubah sebuah variabel dengan hal yang sama seperti sebelumnya, dan tidak mengimplifikasikan sebuah perubahan sate , GetX tidak akan merebuild widget untuk menghemat memori dan siklus CPU.
## Mencampur 2 state manager
Beberapa orang membuka sebuah permintaan fitur, karena mereka hanya ingin menggunakan satu jenis variabel reaktif, dan mekanisme lain, dan diharuskan untuk memasukkan Obx kedalam GetBuilder untuk hal ini. Memikirkan hal ini, MixinBuilder dibuat. Ini mengizinkan kedua perubahan reaktif dengan mengubah variabel ".obs", dan mekanikal update via update(). Meski begitu, dari 4 widget dia adalah satu-satunya yang mengonsumsi resource paling banyak, karena selain memiliki langganan untuk menerima event perubahan dari children nya, dia juga berlangganan kepada metode update di controllernya.
Meng-extend GetxController sangatlah penting, karena mereka memiliki siklus dan bisa "memulai" dan "mengakhiri" event di method onInit() dan onClose() mereka. Anda bisa menggunakan kelas apapun untuk ini, tapi Saya sangat merekomendasikan anda untuk menggunakan kelas GetxController untuk menempatkan variabel anda, baik apakah mereka observable atau tidak.
## GetBuilder vs GetX vs Obx vs MixinBuilder
Dalam satu dekade bekerja dengan programming Saya mendapat beberapa pelajaran berharga.
Kontak pertama saya dengan reactive programming adalah seperti "waw, ini luar biasa" dan faktanya, reactive programming itu luar biasa.
Meski begitu, ini tidak cocok untuk segala situasi. Terkadang semua yang anda butuhkan adalah mengubah state dari 2 atau 3 widget dalam waktu yang sama, atau sebuah perubahan state sementara, yang mana reactive programming tidaklah buruk, tapi tidak cocok.
Reactive programming memiliki konsumsi lebih tinggi dalam penggunaan RAM yang bisa dikompensasi oleh alur kerja individu, dimana akan memastikan bahwa hanya satu widget direbuild dan jika dibutuhkan, namun membuat sebuah list dari 80 objek, masing masing dengan beberapa stream bukanlah ide yang bagus. Buka dart inspect dan cek berapa banyak yang dikonsumsi oleh StreamBuilder, dan anda akan memahami apa yang saya coba katakan kepada anda.
Dengan memikirkan hal itu, Saya membuat sebuah state manager yang simpel. Ini simpel, dan itulah yang harus anda tuntut darinya: mengupdate state dalam sebuah block dengan cara yang sederhana, dan dengan cara yang paling ekonomis.
GetBuilder sangat ekonomis di RAM, dan hampir tidak ada pendekatan yang lebih ekonomis darinya (setidaknya saya tidak bisa membayangkannya, jika itu ada, mohon beri tahu kami).
Meski begitu, GetBuilder tetaplah sebuah state manager mekanik, anda perlu memanggil update() seperti anda perlu memanggil notifyListeners() milik Provider.
Ada beberapa situasi lain dimana reactive programming benar benar menarik, dan tidak bekerja dengan itu sama saja seperti "reinventing the wheel". Dengan memikirkan hal itu, GetX dibuat untuk menyediakan semuanya yang paling modern dan advanced dalam sebuah state manager. Dia mengupdate hanya apa yang diperlukan dan ketika diperlukan, jika anda memiliki error dan mengirim 300 perubahan state secara beruntun, GetX akan memfilter dan mengupdate layar hanya jika state nya benar-benar berubah.
GetX masih lebih ekonomis dari reactive state manager yang lain, namun dia juga menkonsumsi sedikit lebih banyak RAM daripada GetBuilder. Memikirkan tentang hal itu dan menargetkan untuk memaksimalkan konsumsi resource yang dibuat oleh Obx. Tidak seperti GetX dan GetBuilder, anda tidak akan bisa menginisialisasi sebuah controller didalam Obx, itu hanyalah sebuah widget dengan StreamSubscription yang menerima event perubahan dari children anda, itu saja. Ini lebih ekonomis dari GetX, namun kalah dari GetBuilder, yang memang diharapkan, karena dia reactive dan GetBuilder memiliki pendekatan yang paling sederhana yang ada, untuk menyimpan hashCode milik sebuah widget dan StateSetter nya. Dengan Obx anda tidak perlu menulis tipe controller, dan anda bisa mendengar perubahan dari banyak controller yang berbeda, namun itu harus di inisialisasi sebelumnya, baik menggunakan contoh pendekatan di awal readme ini, atau menggunakan Binding class.

View File

@@ -0,0 +1,405 @@
# 依存オブジェクト管理
- [依存オブジェクト管理](#依存オブジェクト管理)
- [インスタンス化に使用するメソッド](#インスタンス化に使用するメソッド)
- [Get.put()](#getput)
- [Get.lazyPut()](#getlazyput)
- [Get.putAsync()](#getputasync)
- [Get.create()](#getcreate)
- [インスタンス化したクラスを使う](#インスタンス化したクラスを使う)
- [依存オブジェクトの置換](#依存オブジェクトの置換)
- [各メソッドの違い](#各メソッドの違い)
- [BindingsRouteと依存オブジェクトの結束](#BindingsRouteと依存オブジェクトの結束)
- [Bindingsクラス](#bindingsクラス)
- [BindingsBuilder](#bindingsbuilder)
- [SmartManagement](#smartmanagement)
- [設定の変更方法](#設定の変更方法)
- [SmartManagement.full](#smartmanagementfull)
- [SmartManagement.onlyBuilder](#smartmanagementonlybuilder)
- [SmartManagement.keepFactory](#smartmanagementkeepfactory)
- [Bindingsの仕組み](#Bindingsの仕組み)
- [補足](#補足)
Getにはシンプルで強力な依存オブジェクト管理機能があります。たった1行のコードで、Provider contextやinheritedWidgetを使うことなく、BlocもしくはControllerのインスタンスを取得することができます。
```dart
Controller controller = Get.put(Controller()); // Controller controller = Controller() の代わりに
```
UIクラスの中でControllerクラスをインスタンス化する代わりに、Getインスタンスの中でインスタンス化することで、アプリ全体でControllerを利用できるようになります。
- 注: Getの状態管理機能を使用する場合は、[Bindings](#bindings)の使用も検討してください。Bindingsを使うことでビューにControllerを結合させることができます。
- 注²: Getの依存オブジェクト管理機能は、パッケージの他の部分から独立しています。そのため、たとえばあなたのアプリが既に他の状態管理ライブラリを使用している場合どんなものでも、何の問題もなく2つを組み合わせることができます。
## インスタンス化に使用するメソッド
Controllerを初期化するのに使用するメソッドとその設定パラメーターについてご説明します。
### Get.put()
依存オブジェクトを注入するための最も基本のメソッド。たとえば、ビューで使用するControllerに使います。
```dart
Get.put<SomeClass>(SomeClass());
Get.put<LoginController>(LoginController(), permanent: true);
Get.put<ListItemController>(ListItemController, tag: "some unique string");
```
以下が put() を使用する際に設定できるパラメーターです。
```dart
Get.put<S>(
// 必須。インスタンスを保存しておきたいControllerなどを設定
// 注: "S" 型はジェネリクスで、どんな型でもOK
S dependency
// オプション。これは同じ型のControllerインスタンスが複数存在する場合に、
// タグIDにより識別したい場合に使用します。
// Get.find<Controller>(tag: ) でこのputで設定したインスタンスを探します。
// tag はユニークなStringである必要があります。
String tag,
// オプション。デフォルトでは使用されなくなったインスタンスは破棄されますが、
// (たとえばビューが閉じられた場合など) SharedPreferencesのインスタンスなど、
// アプリ全体を通して生かしておきたい場合があるかと思います。
// その場合はこれをtrueにしてください。デフォルトはfalseです。
bool permanent = false,
// オプション。テストで抽象クラスを使用した後、別クラスに置換してテストを追うことができます。
// デフォルトはfalseです。
bool overrideAbstract = false,
// オプション: 依存オブジェクトを関数を使って作ることができます。
// 使用頻度は低いと思います。
InstanceBuilderCallback<S> builder,
)
```
### Get.lazyPut()
依存オブジェクトをすぐにロードする代わりに、lazy遅延、消極的ロードすることができます。実際に使用されるときに初めてインスタンス化されます。計算量の多いクラスや、Bindingsを使って複数のControllerをまとめてインスタンス化したいが、その時点ではすぐにそれらを使用しないことがわかっている場合などに非常に便利です。
```dart
/// この場合のApiMockは Get.find<ApiMock> を使用した時点でインスタンス化されます。
Get.lazyPut<ApiMock>(() => ApiMock());
Get.lazyPut<FirebaseAuth>(
() {
// 必要ならここに何かのロジック
return FirebaseAuth();
},
tag: Math.random().toString(),
fenix: true
)
Get.lazyPut<Controller>( () => Controller() )
```
これが .lazyPut で設定できるパラメーターです。
```dart
Get.lazyPut<S>(
// 必須。クラスSが初めてfindの対象になったときに実行されるメソッド
InstanceBuilderCallback builder,
// オプション。Get.put()のtagと同様に同じクラスの異なるインスタンスが必要なときに使用
// ユニークな値を指定
String tag,
// オプション。"permanent" に似ていますが、使用されていないときはインスタンスが
// 破棄され、再び使用するときにGetがインスタンスを再び作成する点が異なります。
// Bindings APIの "SmartManagement.keepFactory " と同じです。
// デフォルトはfalse
bool fenix = false
)
```
### Get.putAsync()
SharedPreferencesなど、非同期のインスタンスを登録したいときに使います。
```dart
Get.putAsync<SharedPreferences>(() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setInt('counter', 12345);
return prefs;
});
Get.putAsync<YourAsyncClass>( () async => await YourAsyncClass() )
```
これが .putAsync で設定できるパラメーターです。
```dart
Get.putAsync<S>(
// 必須。クラスをインスタンス化するための非同期メソッドを指定
AsyncInstanceBuilderCallback<S> builder,
// オプション。Get.put() と同じです。同じクラスの異なるインスタンスを作りたいときに使用
// ユニークな値を指定
String tag,
// オプション。Get.put() と同じです。アプリ全体を通して生かしておきたい場合に使用
// デフォルトはfalse
bool permanent = false
)
```
### Get.create()
これは使いどころに迷うかもしれません。他のものとの違いなど詳細な説明は「[各メソッドの違い](#各メソッドの違い)」のセクションをご一読ください。
```dart
Get.Create<SomeClass>(() => SomeClass());
Get.Create<LoginController>(() => LoginController());
```
これが .create で設定できるパラメーターです。
```dart
Get.create<S>(
// 必須。Get.find() が呼ばれるたびにインスタンスがこの関数で新たに組み立てられる。
// 例: Get.create<YourClass>(() => YourClass())
FcBuilderFunc<S> builder,
// オプション。Get.put() のtagと同様で、同じクラスによる
// 複数インスタンスを扱うときに使用します。
// リストのアイテムにそれぞれコントローラが必要な場合に便利です。
// 文字列はユニークである必要があります。
String name,
// オプション。Get.put() と同様アプリ全体でインスタンスを維持するときに使用。
// 唯一の違いは Get.create() のpermanentはデフォルトでtrueということだけです。
bool permanent = true
```
## インスタンス化したクラスを使う
いくつかのRouteを渡り歩いた後、以前のControllerに残してきたデータが必要になったとしたら、Provider や Get_it と組み合わせる必要がありますよねGetの場合は違います。GetにControllerの「検索」を依頼するだけで追加の依存オブジェクトの注入は必要ありません。
```dart
final controller = Get.find<Controller>();
// もしくは
Controller controller = Get.find();
// マジックみたいですよね。でも実際にGetはControllerのインスタンスを探して届けてくれます。
// 100万ものControllerをインスタンス化していても、Getは常に正しいControllerを探してくれます。
```
そしてそのControllerが以前取得したデータをあなたは復元することができます。
```dart
Text(controller.textFromApi);
```
戻り値は通常のクラスなので、そのクラスで可能なことは何でもできます。
```dart
int count = Get.find<SharedPreferences>().getInt('counter');
print(count); // 出力: 12345
```
インスタンスを明示的に削除したい場合はこのようにしてください。
```dart
Get.delete<Controller>(); // 通常であれば、GetXは未使用Controllerを自動削除するので、この作業は必要ありません。
```
## 依存オブジェクトの置換
注入されている依存オブジェクトは `replace` または `lazyReplace` メソッドを使って、子クラスなど関連クラスのインスタンスに置き換えることができます。これは元の抽象クラスを型指定することで取得することができます。
```dart
abstract class BaseClass {}
class ParentClass extends BaseClass {}
class ChildClass extends ParentClass {
bool isChild = true;
}
Get.put<BaseClass>(ParentClass());
Get.replace<BaseClass>(ChildClass());
final instance = Get.find<BaseClass>();
print(instance is ChildClass); //true
class OtherClass extends BaseClass {}
Get.lazyReplace<BaseClass>(() => OtherClass());
final instance = Get.find<BaseClass>();
print(instance is ChildClass); // false
print(instance is OtherClass); //true
```
## Differences between methods
まずは Get.lazyPut の `fenix` プロパティと、他メソッドの `permanent` プロパティの違いについてご説明します。
`permanent``fenix` の根本的な違いは、インスタンスをどのように保持したいかという点に尽きます。
しつこいようですが、GetXでは使われていないインスタンスは削除されるのがデフォルトの動作です。
これはもし画面AがController A、画面BがController Bを持っている場合において、画面Bに遷移するときに画面Aをスタックから削除した場合`Get.off()``Get.offNamed()` を使うなどして、Controller Aは消えてなくなるということです。
しかし Get.put() する際に `permanent:true` としていれば、Controller Aはこの画面削除により失われることはありません。これはアプリケーション全体を通してControllerを残しておきたい場合に大変便利です。
一方の `fenix` は、画面削除に伴っていったんはControllerが消去されますが、再び使いたいと思ったときに復活させることができます。つまり基本的には未使用の Controller / サービス / その他クラス は消去されますが、必要なときは新しいインスタンスを「燃えカス」から作り直すことができるのです。
各メソッドを使用する際のプロセスの違いをご説明します。
- Get.put と Get.putAsync はインスタンスを作成して初期化するプロセスは同じですが、後者は非同期メソッドを使用するという違いがあります。この2つのメソッドは内部に保有するメソッド `insert``permanent: false``isSingleton: true` という引数を渡して、メモリに直接インスタンスを挿入します (この isSingleton が行っていることは、依存オブジェクトを `dependency``builder` プロパティのどちらから拝借するかを判断することだけです)。その後に `Get.find()` が呼ばれると、メモリ上にあるインスタンスを即座に初期化するというプロセスをたどります。
- Get.create はその名の通り、依存オブジェクトを「クリエイト」します。Get.put() と同様に内部メソッドである `insert` を呼び出してインスタンス化します。違いは `permanent: true``isSingleton: false` である点です (依存オブジェクトを「クリエイト」しているため、シングルトンにはなりません。それが false になっている理由です)。また `permanent: true` となっているので、デフォルトでは画面の破棄などでインスタンスを失わないというメリットがあります。また `Get.find()` はすぐに呼ばれず、画面内で実際に使用されてから呼ばれます。これは `permanent` の特性を活かすための設計ですが、それゆえ `Get.create()` は共有しないけど破棄もされないインスタンスを作成する目的で作られたと言えます。たとえば、ListViewの中のボタンアイテムに使うControllerインスタンスのように、そのリスト内でしか使わないけどリストアイテムごとに固有のインスタンスが必要なケースなどが考えられます。そのため、Get.create は GetWidget との併用がマストです。
- Get.lazyPut は初期化をlazy遅延、消極的に行います。実行されるとインスタンスは作成されますが、すぐに使用できるように初期化はされず、待機状態になります。また他のメソッドと異なり `insert` メソッドは呼び出されません。その代わり、インスタンスはメモリの別の部分に挿入されます。この部分を「ファクトリー」と呼ぶことにしましょう。「ファクトリー」は、そのインスタンスが再生成できるかどうかを決める役割を持っています。これは後で使う予定のものを、現在進行形で使われているものと混ざらないようにするための工夫です。ここで `fenix` によるマジックが登場します。デフォルトの `fenix: false` のままにしており、かつ `SmartManagement``keepFactory` ではない場合において `Get.find` を使用すると、インスタンスは「ファクトリー」から共有メモリ領域に移動します。その直後にインスタンスは「ファクトリー」から削除されます。しかし `fenix: true` としていた場合、インスタンスは「ファクトリー」に残るため、共有メモリ領域から削除されても再び呼び出すことができるのです。
## BindingsRouteと依存オブジェクトの結束
このパッケージの一番の差別化要素は、Route管理 / 状態管理 / 依存オブジェクト管理 を統合したことにあると思っています。
スタックからRouteが削除されれば、関係するController、変数、オブジェクトのインスタンスがすべてメモリから削除されます。たとえばStreamやTimerを使用している場合も同様ですので、開発者は何も心配する必要はありません。
Getはバージョン2.10からBindings APIをフル実装しました。
Bindingsを使用すれば init でControllerを起動する必要はありません。またControllerの型を指定する必要もありません。Controllerやサービスは各々適切な場所で起動することができるようになりました。
Bindingsは依存オブジェクトの注入をビューから切り離すことができるクラスです。それに加え、状態と依存オブジェクトの管理機能をRouteに「結束bind」してくれます。
これによりGetは、あるControllerが使用されたときにどの画面UIが表示されているかを知ることができます。つまり、そのControllerをどのタイミングでどう処分するかを判断することができるということです。
さらにBindingsでは SmartManager の制御により、依存オブジェクトをどのタイミングスタックからRouteを削除したときか、それに依存するWidgetを表示したときか、いずれでもないかで整理するかを設定することができます。インテリジェントな依存オブジェクトの自動管理機能を持ちつつ、自分の好きなように設定できるのです。
### Bindingsクラス
- Bindings機能を実装したクラスを作成することができます。
```dart
class HomeBinding implements Bindings {}
```
"dependencies" メソッドをオーバーライドするようIDEに自動で指摘されます。表示をクリックしてメソッドを override し、そのRoute内で使用するすべてのクラスを挿入してください。
```dart
class HomeBinding implements Bindings {
@override
void dependencies() {
Get.lazyPut<HomeController>(() => HomeController());
Get.put<Service>(()=> Api());
}
}
class DetailsBinding implements Bindings {
@override
void dependencies() {
Get.lazyPut<DetailsController>(() => DetailsController());
}
}
```
Bindingsを設定したら、このクラスが Route管理 / 依存オブジェクト管理 / 状態管理 を互いに接続する目的で使用されるものだということをRouteに知らせてあげます。
- 名前付きRouteを使う場合
```dart
getPages: [
GetPage(
name: '/',
page: () => HomeView(),
binding: HomeBinding(),
),
GetPage(
name: '/details',
page: () => DetailsView(),
binding: DetailsBinding(),
),
];
```
- 通常のRouteを使う場合
```dart
Get.to(Home(), binding: HomeBinding());
Get.to(DetailsView(), binding: DetailsBinding())
```
これでアプリケーションのメモリ管理を気にする必要がなくなります。Getがすべてやってくれます。
BindingsクラスはRouteの呼び出しと同時に呼び出されます。また、すべてに共通の依存オブジェクトを挿入するためには GetMaterialApp の initialBinding プロパティを使用してください。
```dart
GetMaterialApp(
initialBinding: SampleBind(),
home: Home(),
);
```
### BindingsBuilder
Bindingsを作成する一般的な方法は Bindings を実装したクラスを作成することですが、`BindingsBuilder` コールバックを使う方法もあります。
Example:
```dart
getPages: [
GetPage(
name: '/',
page: () => HomeView(),
binding: BindingsBuilder(() {
Get.lazyPut<ControllerX>(() => ControllerX());
Get.put<Service>(()=> Api());
}),
),
GetPage(
name: '/details',
page: () => DetailsView(),
binding: BindingsBuilder(() {
Get.lazyPut<DetailsController>(() => DetailsController());
}),
),
];
```
この方法ならRouteごとにBindingsクラスを作る必要はありません。
どちらの方法でも効果は変わりませんのでお好みの方法を使ってください。
### SmartManagement
エラーが発生してControllerを使用するWidgetが正しく破棄されなかった場合でも、Controllerが未使用になればGetXはデフォルトの動作通りそれをメモリから削除します。
これがいわゆる依存オブジェクト管理機能の `full` モードと呼ばれるものです。
しかしもしGetXによるオブジェクト破棄の方法をコントロールしたい場合は、`SmartManagement`クラスを使って設定してください。
#### 設定の変更方法
この設定は通常変更する必要はありませんが、変更されたい場合はこのようにしてください。
```dart
void main () {
runApp(
GetMaterialApp(
smartManagement: SmartManagement.onlyBuilder // ここで設定
home: Home(),
)
)
}
```
#### SmartManagement.full
これがデフォルトのモードです。使用されていない、かつ `permanent: true` が設定されていないオブジェクトを自動で破棄してくれます。特殊な事情がない限り、この設定は触らない方がいいでしょう。GetXを使って間がない場合は特に。
#### SmartManagement.onlyBuilder
`init:` もしくはBindings内で `Get.lazyPut()` により設定したビルダー製のオブジェクトだけを破棄するモードです。
もしそれが `Get.put()``Get.putAsync()` などのアプローチで生成したオブジェクトだとしたら、SmartManagement は勝手にメモリから除外することはできません。
それに対してデフォルトのモードでは `Get.put()` で生成したオブジェクトも破棄します。
#### SmartManagement.keepFactory
SmartManagement.full と同じように、オブジェクトが使用されていない状態になれば破棄します。ただし、前述の「ファクトリー」に存在するものだけは残します。つまりそのインスタンスが再び必要になった際は依存オブジェクトを再度生成するということです。
### Bindingsの仕組み
Bindingsは「一過性のファクトリー」のようなものを作成します。これはそのRouteに画面遷移した瞬間に作成され、そこから画面移動するアニメーションが発生した瞬間に破棄されます。
この動作は非常に高速で行われるので、アナライザーでは捕捉できないほどです。
再び元の画面に戻ると新しい「一過性のファクトリー」が呼び出されます。そのためこれは SmartManagement.keepFactory を使用するよりも多くの場合好ましいですが、Bindingsを作成したくない場合やすべての依存オブジェクトを同じBindingsに持っておきたい場合は SmartManagement.keepFactory を使うといいでしょう。
ファクトリーのメモリ使用量は少なく、インスタンスを保持することはありません。その代わりにそのクラスのインスタンスを形作る関数を保持します。
メモリコストは非常に低いのですが、最小リソースで最大パフォーマンスを得ることが目的のGetではデフォルトでファクトリーを削除します。
どちらか都合に合う方ををお使いいただければと思います。
## 補足
- 複数のBindingsを使う場合は SmartManagement.keepFactory は**使わない**でください。これは Bindings を使わないケースや、GetMaterialAppのinitialBindingに設定された単独のBindingと一緒に使うケースを想定されて作られました。
- Bindingsを使うことは必須ではありません。`Get.put()``Get.find()` だけでも全く問題ありません。
ただし、サービスやその他抽象度の高いクラスをアプリに取り入れる場合はコード整理のために使うことをおすすめします。

View File

@@ -0,0 +1,567 @@
- [Route管理](#Route管理)
- [使い方](#使い方)
- [通常Routeによるナビゲーション](#通常Routeによるナビゲーション)
- [名前付きRouteによるナビゲーション](#名前付きRouteによるナビゲーション)
- [名前付きRouteにデータを送る](#名前付きRouteにデータを送る)
- [動的URLの生成](#動的URLの生成)
- [ミドルウェアの使用](#ミドルウェアの使用)
- [contextを使わないナビゲーション](#contextを使わないナビゲーション)
- [SnackBar](#snackbar)
- [Dialog](#dialog)
- [BottomSheet](#bottomsheet)
- [ネスト構造のナビゲーション](#ネスト構造のナビゲーション)
# Route管理
このドキュメントではGetXにおけるRoute管理のすべてをご説明します。
## How to use
次の3文字を pubspec.yaml ファイルに追加してください。
```yaml
dependencies:
get:
```
Route / SnackBar / Dialog / BottomSheet をcontextなしで、あるいは高レベルのGet APIを使用するには MaterialApp の前に「Get」を追加してください。それだけで GetMaterialApp の機能が使用できます。
```dart
GetMaterialApp( // 変更前: MaterialApp(
home: MyHome(),
)
```
## 名前付きRouteによる画面遷移
次の画面に遷移するには Get.to を使ってください。
```dart
Get.to(NextScreen());
```
SnackBar / Dialog / BottomSheet など Navigator.pop(context) で閉じるものと同じものを閉じるには Get.back を使います。
```dart
Get.back();
```
次の画面に遷移しつつ、前の画面に戻れないようにするには Get.off を使います(スプラッシュスクリーンやログイン画面などで使用)。
```dart
Get.off(NextScreen());
```
次の画面に遷移して、それ以前のRouteはすべて破棄するには Get.offAll を使います(ショッピングカート、投票、テストなどで使用)
```dart
Get.offAll(NextScreen());
```
次の画面に遷移して、戻ったらデータを受け取る方法はこちら。
```dart
var data = await Get.to(Payment());
```
次の画面では、このようにデータを前の画面に送る必要があります。
```dart
Get.back(result: 'success');
```
そして使いましょう。
ex:
```dart
if(data == 'success') madeAnything();
```
どのようなシンタックスがあるかもっと知りたいですか?
いつもの Navigator ではなく navigator と入れてみてください。通常のNavigatorで使えるプロパティがcontextなしで使えるようになっているかと思います。
```dart
// 通常のFlutterによるNavigator
Navigator.of(context).push(
context,
MaterialPageRoute(
builder: (BuildContext context) {
return HomePage();
},
),
);
// GetではFlutterのシンタックスをcontextなしで使えます
navigator.push(
MaterialPageRoute(
builder: (_) {
return HomePage();
},
),
);
// Getのシンタックス上記よりかなり短いですね
Get.to(HomePage());
```
## 名前付きRouteによる画面遷移
- Getは名前付きRouteによる遷移もサポートしています。
次の画面への遷移はこう。
```dart
Get.toNamed("/NextScreen");
```
Get.off の名前付きRoute版。
```dart
Get.offNamed("/NextScreen");
```
Get.offAll の名前付きRoute版。
```dart
Get.offAllNamed("/NextScreen");
```
Routeを定義するにはGetMaterialAppを使ってください。
```dart
void main() {
runApp(
GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => MyHomePage()),
GetPage(name: '/second', page: () => Second()),
GetPage(
name: '/third',
page: () => Third(),
transition: Transition.zoom
),
],
)
);
}
```
未定義Route404エラーに遷移させるには、GetMaterialAppで unknownRoute を設定してください。
```dart
void main() {
runApp(
GetMaterialApp(
unknownRoute: GetPage(name: '/notfound', page: () => UnknownRoutePage()),
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => MyHomePage()),
GetPage(name: '/second', page: () => Second()),
],
)
);
}
```
### 名前付きRouteにデータを送る
次の画面に渡すデータは arguments で引数を設定します。Getでは引数にどんなものでも指定できます。StringでもMapでもListでも、クラスのインスタンスでも大丈夫です。
```dart
Get.toNamed("/NextScreen", arguments: 'Get is the best');
```
ビュー側のクラスやControllerで値を使うにはこうしてください。
```dart
print(Get.arguments);
// Get is the best が表示される
```
### 動的URLの生成
Getはウェブのような高度な動的URLを提供します。ウェブ開発者はFlutterにこの機能が提供されることを待ち望んでいたことでしょう。この機能の提供を謳うパッケージは存在しますが、ウェブ上のURLとは全く異なるシンタックスが表示されているのを見たことがあるかもしれません。Getはこの点も解決します。
```dart
Get.offAllNamed("/NextScreen?device=phone&id=354&name=Enzo");
```
ビュー側のクラスやControllerで値を使う方法。
```dart
print(Get.parameters['id']);
// 出力: 354
print(Get.parameters['name']);
// 出力: Enzo
```
この名前付きパラメーターはこのように簡単に受け取ることもできます。
```dart
void main() {
runApp(
GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(
name: '/',
page: () => MyHomePage(),
),
GetPage(
name: '/profile/',
page: () => MyProfile(),
),
// 引数userを使う場合と使わない場合で別ページを定義することが可能です。
// ただ、そのためにはスラッシュ '/' をベースのRoute名の後に入れる必要があります。
GetPage(
name: '/profile/:user',
page: () => UserProfile(),
),
GetPage(
name: '/third',
page: () => Third(),
transition: Transition.cupertino
),
],
)
);
}
```
Route名を使ってデータを送る方法。
```dart
Get.toNamed("/profile/34954");
```
次の画面でデータを受け取る方法。
```dart
print(Get.parameters['user']);
// out: 34954
```
複数のパラメーターを送信するにはこちら。
```dart
Get.toNamed("/profile/34954?flag=true&country=italy");
```
もしくは
```dart
var parameters = <String, String>{"flag": "true","country": "italy",};
Get.toNamed("/profile/34954", parameters: parameters);
```
次の画面でデータを受け取る方法。
```dart
print(Get.parameters['user']);
print(Get.parameters['flag']);
print(Get.parameters['country']);
// 出力: 34954 true italy
```
あとは Get.toNamed() を使い、名前付きRouteを指定するだけですcontextを使わないので BLoC や Controller から直接Routeを呼び出すことができます。ウェブアプリとしてコンパイルされると、Routeが正しくURLに表示されます。
### ミドルウェアの使用
何かのアクションのトリガーとなるイベントを取得したい場合は、routingCallbackを使用してください。
```dart
GetMaterialApp(
routingCallback: (routing) {
if(routing.current == '/second'){
openAds();
}
}
)
```
GetMaterialAppを使用しない場合は、手動のAPIを使ってミドルウェアオブザーバーを設定してください。
```dart
void main() {
runApp(
MaterialApp(
onGenerateRoute: Router.generateRoute,
initialRoute: "/",
navigatorKey: Get.key,
navigatorObservers: [
GetObserver(MiddleWare.observer), // ここ
],
),
);
}
```
ミドルウェアクラスを作成する
```dart
class MiddleWare {
static observer(Routing routing) {
/// Routeの他に SnackBar / Dialog / BottomSheet のイベントも監視することができます。
/// また、ここで直接この3つのいずれかを表示したい場合は、
/// イベント自身が「それではない」ことを事前にチェックする必要があります。
if (routing.current == '/second' && !routing.isSnackbar) {
Get.snackbar("Hi", "You are on second route");
} else if (routing.current =='/third'){
print('last route called');
}
}
}
```
それではGetをコードで使ってみましょう。
```dart
class First extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.add),
onPressed: () {
Get.snackbar("hi", "i am a modern snackbar");
},
),
title: Text('First Route'),
),
body: Center(
child: ElevatedButton(
child: Text('Open route'),
onPressed: () {
Get.toNamed("/second");
},
),
),
);
}
}
class Second extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.add),
onPressed: () {
Get.snackbar("hi", "i am a modern snackbar");
},
),
title: Text('second Route'),
),
body: Center(
child: ElevatedButton(
child: Text('Open route'),
onPressed: () {
Get.toNamed("/third");
},
),
),
);
}
}
class Third extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Third Route"),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Get.back();
},
child: Text('Go back!'),
),
),
);
}
}
```
## contextを使わないナビゲーション
### SnackBar
FlutterでシンプルなSnackBarを表示したいとき、Scaffoldのcontextか、GlobalKeyを取得する必要があります。
```dart
final snackBar = SnackBar(
content: Text('Hi!'),
action: SnackBarAction(
label: 'I am a old and ugly snackbar :(',
onPressed: (){}
),
);
// WidgetツリーでScaffoldを探し、それをSnackBar表示に使用します。
Scaffold.of(context).showSnackBar(snackBar);
```
Getならこうなります。
```dart
Get.snackbar('Hi', 'i am a modern snackbar');
```
コードのどこにいようと、Get.snackbar を呼ぶだけでいいのです。カスタマイズも自由自在です。
```dart
Get.snackbar(
"Hey i'm a Get SnackBar!", // タイトル
"It's unbelievable! I'm using SnackBar without context, without boilerplate, without Scaffold, it is something truly amazing!", // 本文
icon: Icon(Icons.alarm),
shouldIconPulse: true,
onTap:(){},
barBlur: 20,
isDismissible: true,
duration: Duration(seconds: 3),
);
////////// すべてのプロパティ //////////
// Color colorText,
// Duration duration,
// SnackPosition snackPosition,
// Widget titleText,
// Widget messageText,
// bool instantInit,
// Widget icon,
// bool shouldIconPulse,
// double maxWidth,
// EdgeInsets margin,
// EdgeInsets padding,
// double borderRadius,
// Color borderColor,
// double borderWidth,
// Color backgroundColor,
// Color leftBarIndicatorColor,
// List<BoxShadow> boxShadows,
// Gradient backgroundGradient,
// TextButton mainButton,
// OnTap onTap,
// bool isDismissible,
// bool showProgressIndicator,
// AnimationController progressIndicatorController,
// Color progressIndicatorBackgroundColor,
// Animation<Color> progressIndicatorValueColor,
// SnackStyle snackStyle,
// Curve forwardAnimationCurve,
// Curve reverseAnimationCurve,
// Duration animationDuration,
// double barBlur,
// double overlayBlur,
// Color overlayColor,
// Form userInputForm
///////////////////////////////////
```
従来の SnackBar がお好みの場合や、ゼロからカスタマイズしたい場合 (たとえば Get.snackbar ではタイトルと本文が必須項目となっています)は `Get.rawSnackbar();` を使ってください。SnackBarの元々のAPIを取得できます。
### Dialog
ダイアログを表示する方法。
```dart
Get.dialog(YourDialogWidget());
```
デフォルトのダイアログを表示する方法。
```dart
Get.defaultDialog(
onConfirm: () => print("Ok"),
middleText: "Dialog made in 3 lines of code"
);
```
また showGeneralDialog の代わりに Get.generalDialog が使えます。
Overlayを使用するCupertino含むその他のFlutterのダイアログについては、contextの代わりに Get.overlayContext を使うことでコードのどこでもダイアログを表示することができます。
Overlayを使わないWidgetについては、Get.context が使えます。
これら2つのcontextはほとんどのケースでUIのcontextを代替することができるでしょう。ただし、ナビゲーションのcontextを使用せずInheritedWidgetが使われているケースは例外です。
### BottomSheet
Get.bottomSheet は showModalBottomSheet に似ていますが、contextが不要です。
```dart
Get.bottomSheet(
Container(
child: Wrap(
children: <Widget>[
ListTile(
leading: Icon(Icons.music_note),
title: Text('Music'),
onTap: () {}
),
ListTile(
leading: Icon(Icons.videocam),
title: Text('Video'),
onTap: () {},
),
],
),
)
);
```
## ネスト構造のナビゲーション
GetはFlutterのネスト構造のナビゲーションの扱いも簡単にしてくれます。
contextを必要とせず、IDによりナビゲーションのスタックを見つけることができます。
- 注: 並列のナビゲーションスタックを作成することは危険です。ネスト構造のNavigatorを使用しないか、使用を控えめにするのが理想です。必要なら使っていただいても問題ありませんが、複数のナビゲーションのスタックを保持することはRAM消費の面で好ましくないということは覚えておいてください。
こんなに簡単にできます。
```dart
Navigator(
key: Get.nestedKey(1), // インデックス指定でkey作成
initialRoute: '/',
onGenerateRoute: (settings) {
if (settings.name == '/') {
return GetPageRoute(
page: () => Scaffold(
appBar: AppBar(
title: Text("Main"),
),
body: Center(
child: TextButton(
color: Colors.blue,
onPressed: () {
Get.toNamed('/second', id:1); // インデックス指定でネスト型Routeに遷移
},
child: Text("Go to second"),
),
),
),
);
} else if (settings.name == '/second') {
return GetPageRoute(
page: () => Center(
child: Scaffold(
appBar: AppBar(
title: Text("Main"),
),
body: Center(
child: Text("second")
),
),
),
);
}
}
),
```

View File

@@ -0,0 +1,787 @@
* [状態管理](#状態管理)
+ [リアクティブな状態管理](#リアクティブな状態管理)
- [利点](#利点)
- [パフォーマンスの最大化](#パフォーマンスの最大化)
- [リアクティブな変数の宣言](#リアクティブな変数の宣言)
- [初期値の設定](#初期値の設定)
- [Observableの値をビュー内で使う](#Observableの値をビュー内で使う)
- [更新条件を設定](#更新条件を設定)
- [.obsの使いどころ](#.obsの使いどころ)
- [List(Rx)に関する補足](#List(Rx)に関する補足)
- [なぜ「.value」を使う必要があるのか](#なぜ「.value」を使う必要があるのか)
- [Obx()](#obx)
- [Worker](#worker)
+ [非リアクティブな状態管理](#非リアクティブな状態管理)
- [利点](#利点)
- [使用例](#使用例)
- [Controllerインスタンスの扱い](#Controllerインスタンスの扱い)
- [StatefulWidgetsはもういらない](#StatefulWidgetsはもういらない)
- [Getの目的](#Getの目的)
- [Controllerの様々な使用方法](#Controllerの様々な使用方法)
- [ユニークIDの設定](#ユニークIDの設定)
+ [状態管理ソリューションを混在させる](#状態管理ソリューションを混在させる)
+ [StateMixin](#StateMixin)
+ [GetBuilder VS GetX VS Obx VS MixinBuilder](#GetBuilder-VS-GetX-VS-Obx-VS-MixinBuilder)
# 状態管理
GetXは他の状態管理ライブラリのように Stream や ChangeNotifier を使用する必要がありません。なぜか私たちは応答時間とRAM消費量を改善するために GetValueとGetStream という低遅延のソリューションを開発しましたが、状態管理機能を含むGetXのリソースはすべてこれをベースに作られているためです。このソリューションはより低い運用コストと高いパフォーマンスを実現します。GetXを使えばAndroid、iOS、Web、Linux、macOS用のアプリケーションを作成するだけでなく、Flutter/GetXと同じシンタックスでサーバーアプリケーションを作ることができます。
* _バカみたいにシンプル_: 他の状態管理アプローチの中には、複雑で多くのボイラープレートコードを書かなければいけないものもあります。この問題により少なくない人たちがFlutterを見限りましたが、今ようやく、バカみたいにシンプルなソリューションを手に入れることができました。GetXを使えば、非常にすっきりとした、記述量の少ないコードでより多くのことができるようになります。イベントごとにクラスを定義する必要もありません。
* _コード生成にサヨナラ_: 開発時間の半分はアプリケーションロジックの作成に費やします。それにも関わらず、状態管理ライブラリの中には、ミニマルなコードを作るためにコード生成ツールに依存しているものがあります。変数を変更して build_runner を実行するのは非生産的ですし、flutter clean 後の待ち時間もコーヒーをたくさん飲まなければならないほど長くなることもあります。
GetXはすべてがリアクティブであり、コード生成ツールに依存しないため、開発のあらゆる面において生産性が向上します。
* _context依存にサヨナラ_: ビューとビジネスロジックを連携させるため、ビューのcontextをControllerに送る必要に迫られた。contextがないところで依存オブジェクトの注入をする必要があり、contextを方々のクラスや関数からなんとか渡した。これらの経験、誰もが通ってきた道かと思います。しかし、GetXではこのような経験をすることはありません。contextなしで、Controllerの中から別のControllerにアクセスすることができます。パラメーターを通じて無駄にcontextを送る必要はもうありません。
* _細かいコントロール_: 多くの状態管理ソリューションは、ChangeNotifierをベースにしています。ChangeNotifierは、notifyListenerが呼ばれたときに、依存するすべてのWidgetに通知します。画面に40個のWidgetがあるとしましょう。それらがすべてChangeNotifierの変数に依存している場合、変数を1つでも更新すれば、すべてのWidgetが更新されます。
GetXを使えばネストされたWidgetさえも的確にビルドを処理することができます。ListViewを担当するObxと、ListViewの中のチェックボックスを担当するObxがあれば、チェックボックスの値を変更した場合はチェックボックスWidgetだけが更新され、Listの値を変更した場合はListViewだけが更新されます。
* _変数が本当に変わったときだけ更新する_: GetXはデータの流れをコントロールします。つまり、Textに紐づいたObservable(監視可能)変数の値 'Paola' を、同じ 'Paola' に変更してもWidgetは更新されません。これは、GetXがTextに'Paola'がすでに表示されていることをわかっているためです。
多くの状態管理ソリューションは、この場合更新を行います。
## リアクティブな状態管理
リアクティブプログラミングは複雑であると言われがちなためか、多くの人に敬遠されています。しかし、GetXはリアクティブプログラミングを非常にシンプルなものにしてくれます。
* StreamControllerを作る必要はありません。
* 変数ごとにStreamBuilderをセットする必要はありません。
* 状態ごとにクラスを作る必要はありません。
* 初期値のためにgetを準備する必要はありません。
Getによるリアクティブプログラミングは、setState並に簡単です。
たとえば name という変数があり、それを変更するたびに変数に依存するすべてのWidgetを自動更新したいとします。
これがその name 変数です。
``` dart
var name = 'Jonatas Borges';
```
これをObservable(監視可能)にするには、値の末尾に ".obs" を付け足すだけです。
``` dart
var name = 'Jonatas Borges'.obs;
```
これで終わりです。*こんなに* 簡単なんですよ。
(以後、このリアクティブな ".obs" 変数、Observable(監視可能)を _Rx_ と呼ぶことがあります。)
内部ではこのような処理を行っています: `String`の`Stream`を作成し、初期値`"Jonatas Borges"`を割り当て、`"Jonatas Borges"`に依存するすべてのWidgetに、あなたは今この変数の影響下にあるから、_Rx_の値が変更されたときには、あなたも同様に変更する必要がある旨を通知。
これがDartの機能のおかげで実現できた **GetX マジック** です。
しかし皆さんご存知の通り、`Widget` は関数の中にいなければ自らを更新できません。静的クラスには「自動更新」の機能がないからです。
それなら、同じスコープ内で複数の変数に依存してWidgetをビルドする場合は、複数の `StreamBuilder` をネストして変数の変化を監視する必要がありますね。
いいえ、**GetX** なら `StreamBuilder` すら不要です。
またWidgetを更新する際のボイラープレートコードについても、**GetX**では忘れてください。
`StreamBuilder( ... )` ? `initialValue: ...` ? `builder: ...` ? これらはすべて不要で、対象のWidgetを `Obx()` の中に入れるだけです。
``` dart
Obx (() => Text (controller.name));
```
_覚えること_ それは `Obx(() =>` だけです。
そのWidgetをアロー関数を通じて `Obx()`_Rx_のObserver(監視者))に渡すだけです。
`Obx` は非常に賢く、`controller.name` の値が本当に変わったときにのみ、Widgetの更新をかけます。
`name` が `"John"` だとして、それを `"John"` ( `name.value = "John"` ) に変更しても、以前と同じ `value` のため画面上では何も変化しません。`Obx` はリソースを節約するために値を無視し、Widgetを更新しません。**すごいでしょ?**
> では、もしも `Obx` の中に_Rx_Observable変数が5つあったらどうでしょう
5つの**いずれかに**値の変化があればWidgetは更新されます。
> また、1つのControllerクラスに30もの変数がある場合、1つの変数を更新したら変数に関わるWidgetが**すべて**更新されてしまうのでしょうか?
いいえ、_Rx_ 変数を使う特定のWidgetだけが更新されます。
言い換えるなら、**GetX**は _Rx_ 変数の値が変化したときだけ画面更新をしてくれるということです。
```dart
final isOpen = false.obs;
// 同じ値なので何も起きません。
void onButtonTap() => isOpen.value=false;
```
### 利点
**GetX()**は何を更新して何をしないのか、の**細かい**コントロールが可能です。
すべての更新するのでそのようなコントロールが不要な場合は、`GetBuilder` を検討してください。
これはわずか数行のコードで作られた、状態更新のためのシンプルなビルダーです。(`setState()`のようにブロックで)
CPUへの影響を最小限にするために作られており、単一の目的(_状態_ の再構築)を果たすため、可能な限りリソース消費を抑えました。
**強力な** 状態管理のソリューションを求めているなら、**GetX**で間違いはありません。
変数をそのまま扱うことはできませんが、内部では `Stream` としてデータが扱われています。
すべてが `Stream` なので、_RxDart_ を組み合わせることも可能ですし、
"_Rx_ 変数" のイベントや状態を監視することも可能です。
GetXは _MobX_ より簡単で、コード自動生成や記述量を減らした_BLoC_ 型アプローチと言えるかもしれません。
値の末尾に `.obs` を付けるだけで**なんでも** _"Observable(監視可能)"_ にできるのです。
### パフォーマンスの最大化
ビルドを最小限に抑えるための賢いアルゴリズムに加えて、
**GetX**はコンパレーターを使用して状態が変更されたことを確認します。
アプリでなにかしらのエラーが発生し、状態が変更された情報を
二重に送信してしまったとしても**GetX**はクラッシュを防いでくれます。
**GetX**では値が変化したときにはじめて「状態」が変化するためです。
これが **GetX** と _MobX の `computed`_ を使う際の主な違いです。
2つの __Observable__ を組み合わせて一つが変化したとき、それを監視しているオブジェクトも変化します。
これは `GetX()` (`Observer()`のようなもの) において2つの変数を組み合わせた場合においても、
それが本当に状態の変化を意味するときだけWidgetの更新が行われるということでもあります。
### リアクティブな変数の宣言
変数を "Observable" にする方法は3つあります。
1 - **`Rx{Type}`** を使用する
``` dart
// 初期値を入れることを推奨しますが、必須ではありません
final name = RxString('');
final isLogged = RxBool(false);
final count = RxInt(0);
final balance = RxDouble(0.0);
final items = RxList<String>([]);
final myMap = RxMap<String, int>({});
```
2 - **`Rx`** とジェネリクスによる型指定の組み合わせ
``` dart
final name = Rx<String>('');
final isLogged = Rx<Bool>(false);
final count = Rx<Int>(0);
final balance = Rx<Double>(0.0);
final number = Rx<Num>(0);
final items = Rx<List<String>>([]);
final myMap = Rx<Map<String, int>>({});
// 任意の型を指定可能 - どんなクラスでもOK
final user = Rx<User>();
```
3 - 最も実用的で簡単な方法として、**`.obs`** を値に付ける
``` dart
final name = ''.obs;
final isLogged = false.obs;
final count = 0.obs;
final balance = 0.0.obs;
final number = 0.obs;
final items = <String>[].obs;
final myMap = <String, int>{}.obs;
// カスタムクラスのインスタンスにも付けられます
final user = User().obs;
```
##### 初期値の設定
ご存知の通り、_Dart_ は現在 _null safety_ へ移行しているところです。
それに備えるために今後は _Rx_ 変数は常に**初期値**を設定してください。
> **GetX** で変数を _Observable_ にしつつ _初期値_ を設定するのはとても簡単です。
変数の末尾に `.obs` を付ける。**それだけ。**
めでたく Observable とそのプロパティ `.value` (つまり _初期値_)ができました。
### Observableの値をビュー内で使う
``` dart
// Controllerクラス
final count1 = 0.obs;
final count2 = 0.obs;
int get sum => count1.value + count2.value;
```
``` dart
// ビュークラス
GetX<Controller>(
builder: (controller) {
print("count 1 rebuild");
return Text('${controller.count1.value}');
},
),
GetX<Controller>(
builder: (controller) {
print("count 2 rebuild");
return Text('${controller.count2.value}');
},
),
GetX<Controller>(
builder: (controller) {
print("count 3 rebuild");
return Text('${controller.sum}');
},
),
```
`count1.value++` を実行すると、以下の通りprintされます。
* `count 1 rebuild`
* `count 3 rebuild`
なぜなら `count1` の値が `1` に変わり、それに伴ってgetter `sum` の値にも `1 + 0 = 1` と変化が起こるからです。
今度は `count2.value++` を実行してみましょう。
* `count 2 rebuild`
* `count 3 rebuild`
もうおわかりですね。これは `count2.value` が変わり、その結果 `sum` が `2` になったからです。
* 注: デフォルト仕様では、`value` に変化がなかったとしても、それが最初のイベントであればWidgetを更新します。
この仕様はbool変数の性質から来るものです。
たとえばこの場合を想像してみてください。
``` dart
var isLogged = false.obs;
```
そして、isLogged(ユーザーがログインしたかどうか)の変化をトリガーにever関数内のコールバックfireRouteを呼び出したいとします。
``` dart
@override
onInit() async {
// everは引数1が変化するたびに引数2を実行するリスナー
ever(isLogged, fireRoute);
isLogged.value = await Preferences.hasToken();
}
fireRoute(logged) {
if (logged) {
Get.off(Home());
} else {
Get.off(Login());
}
}
```
もし `hasToken` が `false` なら `isLogged` に変化はありません。すると `ever()` のコールバックは永遠に呼び出されないことになります。
このような挙動を防ぐために _Observable_ への最初の更新は、それがたとえ同じ `.value` だったとしても
常にイベントを引き起こすようにしています。
ご参考までに、この仕様は以下の設定で解除することができます。
`isLogged.firstRebuild = false;`
### 更新条件を設定
Getにはさらに洗練された「状態」のコントロール方法があります。イベント(Listへのオブジェクト追加など)に対して条件を付けることが可能です。
``` dart
// 引数1: Listにオブジェクトを加える条件。trueかfalseを返すこと
// 引数2: 条件がtrueの場合に加える新しいオブジェクト
list.addIf(item < limit, item);
```
最低限のコードで、コード生成ツールも使わず、とても簡単ですね :smile:
カウンターアプリもこのようにシンプルに実現できます。
``` dart
class CountController extends GetxController {
final count = 0.obs;
}
```
Controllerを設定して、下記を実行するだけ。
``` dart
controller.count.value++
```
UIの数字が置き換わりましたね。このようにアプリのどこであっても更新をかけることができます。
### .obsの使いどころ
.obs を使うことでどんなものもObservableにすることができます。方法は2つ。
* クラスのインスタンス変数をobsに変換する
``` dart
class RxUser {
final name = "Camila".obs;
final age = 18.obs;
}
```
* クラスのインスタンスを丸々obsに変換する
``` dart
class User {
User({String name, int age});
var name;
var age;
}
// インスタンス化の際
final user = User(name: "Camila", age: 18).obs;
```
### List(Rx)に関する補足
List(Rx)はその中のオブジェクトと同様、監視可能(Observable)です。そのためオブジェクトを追加すると、List(Rx)に依存するWidgetは自動更新されます。
またList(Rx)をListとするために ".value" を使う必要はありません。DartのAPIがこれを可能にしてくれました。ただ、残念ながら他のStringやintのようなプリミティブ型は拡張ができないため、.value を使う必要があります。getterやsetterを活用するのであればあまり問題になりませんが。
``` dart
// Controllerクラス
final String title = 'User Info:'.obs
final list = List<User>().obs;
// ビュークラス
Text(controller.title.value), // Stringの場合は .value が必要
ListView.builder (
itemCount: controller.list.length // Listの場合は不要
)
```
カスタムのクラスをObservableにした場合は、様々な方法で値を更新することができます。
``` dart
// モデルクラス
// 属性をobsにするやり方ではなく、クラス全体をobsにする方法を採ります
class User() {
User({this.name = '', this.age = 0});
String name;
int age;
}
// Controllerクラス
final user = User().obs;
// user変数を更新するときはこのようなメソッドを作ります
user.update( (user) { // このパラメーターは更新するオブジェクトそのもの
user.name = 'Jonny';
user.age = 18;
});
// あるいは、この方法でも。変数名は呼び出し可能です。
user(User(name: 'João', age: 35));
// ビュークラス
Obx(()=> Text("Name ${user.value.name}: Age: ${user.value.age}"))
// .value を使わずにモデルのプロパティにアクセスすることも可能です
user().name; // userがUserではないことに注目。user()でUserを受け取れます。
```
ListのsetAllやsetRangeメソッドの代わりに、"assign" "assignAll" APIを使っていただくことも可能です。
"assign" APIはListの内容をクリアした後に、指定した単独のオブジェクトを追加してくれます。
"assignAll" APIはそのIterable版です。
### なぜ「.value」を使う必要があるのか
ちょっとしたアノテーションとコード生成ツールを使って`String`や`int`で `.value` を使わなくて済むようにもすることはできますが、このライブラリの目的は「外部依存パッケージを減らす」ことです。私たちは、外部パッケージを必要としない、必須ツールRoute、依存オブジェクト、状態の管理が揃った開発環境を軽量かつシンプルな方法で提供したいと考えています。
まさに pubspecに3文字getとコロンを加えて、プログラミングを始めることができるのです。Route管理から状態管理まで、必要なソリューションが標準装備されています。GetXはシンプルさ、生産性、高いパフォーマンスを目指します。
これほど多機能であるにも関わらず、このライブラリの総容量は他の多くの状態管理ライブラリよりも少ないです。その点をご理解いただけるとうれしいです。
`.value` が嫌でコード生成ツールを使いたいという方には、MobXは素晴らしいライブラリだと思いますし、Getと併用することもできます。逆に多くの外部パッケージに依存したくない方、パッケージ間の互換性を気にしたくない方、状態管理ツールや依存オブジェクトから状態更新エラーが出ているかどうかを気にせずプログラミングをしたい方、依存するControllerクラスのインスタンスがあるかどうかを都度都度心配したくない方にとってはGetはまさに最適です。
MobXのコード生成や、BLoCのボイラープレートコードが気にならないのであれば、Route管理にだけでもGetをお使いいただけるとうれしいです。GetのSEMとRSMは必要に迫られて生まれたものです。私の会社で以前、90以上のControllerを持つプロジェクトがあり、それなりの性能のマシンでflutter cleanを行った後でさえ、コード生成ツールがタスクを完了するのに30分以上かかりました。もしあなたが大きなプロジェクトに関わっており、コード生成ツールが問題になっているのであれば、Getを検討してみてください。
もちろん、コード生成ツールをGetXに導入したい方が実際にツールを作成してプロジェクトに貢献した場合は、このReadMeに代替ソリューションとして掲載させていただきます。私はすべての開発者のニーズをかなえたいわけではありませんが、今はこの質問に対しては、「すでにMobXのように同様のことを実現してくれる良いソリューションがある」とだけ言わせてください。
### Obx()
GetX()の代わりにObx()を使用することもできます。ObxはWidgetを生成する匿名関数をパラメーターに持ちます。複数のControllerに対応することができますが、自身はControllerのインスタンスを持たず、型指定もできません。そのため別途Controllerのインスタンスを作るか、`Get.find<Controller>()` でインスタンスを探しておく必要があります。
### Worker
Worker はイベント発生に伴って指定したコールバックを呼び出すことができます。
``` dart
/// `count1` が更新されるたびに第2引数のコールバックが実行される
ever(count1, (_) => print("$_ has been changed"));
/// `count1` の最初の更新時のみ実行される
once(count1, (_) => print("$_ was changed once"));
/// DDoS攻撃対策に最適。たとえば、ユーザーが打鍵やクリックを止めて1秒後に実行など
debounce(count1, (_) => print("debouce$_"), time: Duration(seconds: 1));
/// 1秒以内の連続更新はすべて無視して実行しない
interval(count1, (_) => print("interval $_"), time: Duration(seconds: 1));
```
すべてのWorker(`debounce` 以外) は `condition` パラメーターを持ちます。`condition` は `bool` でも `bool` を返すコールバックでも構いません。
この `condition` が Worker のコールバックを実行するかどうかを決めています。
また Worker は `Worker` インスタンスを返します。これは `dispose()` などを通じて Worker を破棄するときに使用します。
* **`ever`**
は _Rx_ 変数が新しい値になるたびに呼ばれます。
* **`everAll`**
`ever` とほぼ同じですが、_Rx_ 変数の `List` を受け取ります。いずれかの値が更新されれば、その更新後の値を受け取ってコールバックが実行されます。
* **`once`**
変数が最初に更新されたときのみに呼ばれます。
* **`debounce`**
'debounce' は検索機能などで導入するととても有益です。たとえば、ユーザーがタイピングを止めたときにのみAPIを呼び出したいときに使います。ユーザーが "Jonny" と入れたときに 5回も APIに問い合わせを行うのは避けたいですよね。Getなら "debounce" があるので大丈夫です。
* **`interval`**
`interval` は debounce とは異なります。ユーザーが1秒間に1000回変数に変更を加えた場合、debounceが一定期間経過後デフォルトは800ミリ秒に最後の変更イベントだけ送信するのに対して、intervalは代わりに一定期間の間のユーザーによるアクションをすべて無視します。intervalは1秒ごとにイベントを送信しており、3秒に設定した場合は1分間に20個のイベントを送信します。これはユーザーがキーやマウスを連打することで何かしらの報酬を得られる場合に、その悪用を避けるために使用できます(ユーザーが何かをクリックしてコインを獲得できるとします。たとえ何秒かかったとしても300回クリックすれば300枚のコインを得ることができてしまいます。intervalを使用してインターバルを3秒に設定した場合は、何回クリックしようが1分間で得られるコインの上限は20枚になります)。一方のdebounceはアンチDDosや、検索のように変更を加えるたびにonChangeからAPI問い合わせが発生するような機能に適しています。ユーザーが入力し終わるのを待ってリクエストを送信するのです。debounceを前述のコイン獲得のケースで使用した場合、ユーザーはコインを1枚しか獲得できません。これは指定した期間、ユーザーが動作を「一時停止」したときにのみ実行されるからです。
* 注: Workerを使用する場合は、Controllerなどを起動するときに次のいずれかの方法で登録する必要があります。onInit推奨内、クラスのコンストラクタ、またはStatefulWidgetのinitState内この方法は推奨しませんが、副作用はないはずです
## 非リアクティブな状態管理
GetはChangeNotifierを使わない軽量かつシンプルな状態管理機能を有しています。特にFlutterに慣れていない方のニーズを満たし、大規模なアプリケーションでも問題を起こすことがないと信じています。
GetBuilderは複数の状態を扱う場面で使われることを想定して作られました。たとえばショッピングカートに30個の商品があるとします。そしてあなたが商品を一つ削除すると同時に、カートのリストが更新され、合計金額が更新され、アイテム数を示すバッジが更新されます。GetBuilderはこのようなユースケースに最適です。というのも、GetBuilderは状態をControllerで束ねてそのControllerに依存するすべてのWidgetを一度に更新させることができるからです。
それらとは独立したControllerが必要な場合は、GetBuilderのidプロパティに専用IDを割り当てるか、GetXを使ってください。ケースバイケースですが、そのような「独立した」Widetが多いほど GetX() のパフォーマンスが際立ち、複数の状態変化がありそれに伴うWidgetの更新が多いほど GetBuilder() のパフォーマンスが勝ることを覚えておいてください。
### 利点
1. 必要なWidgetのみ更新される。
2. ChangeNotifierを使わず、メモリ使用量が少ない。
3. StatefulWidgetのことはもう忘れましょう。Getでは必要ありません。他の状態管理ライブラリではStatefulWidgetを使用することがあるでしょう。しかしAppBarやScaffoldなどクラス内のほとんどのWidgetがStatelessであるにも関わらず、StatefulなWidgetの状態だけを保持する代わりに、クラス全体の状態を保持しているのはなぜでしょうかGetならクラス全体をStatelessにすることができます。更新が必要なコンポーネントは GetBuilder などで囲むことで「状態」が保持されます。
4. プロジェクトを整理しましょうControllerはUIの中にあってはいけません。TextEditControllerなどの類はすべてControllerクラスに配置してしまいましょう。
5. Widgetのレンダリングが開始されると同時にイベントを実行してWidgetを更新させる必要がありますかGetBuilderにはStatefulWidgetと同様の「initState」プロパティがあり、そこからControllerのイベントを直接呼び出すことができます。initStateを使用する必要はもうありません。
6. StreamやTimerのインスタンスを破棄したい場合はGetBuilderのdisposeプロパティを利用してください。Widgetが破棄されると同時にイベントを呼び出すことができます。
7. GetXとStreamController / StreamBuilderを組み合わせるなどしてStreamを普通に使っていただいても問題ありませんが、必要なときに限って使うことをおすすめします。Streamのメモリ消費は適度であり、リアクティブプログラミングは美しいですが、たとえば30ものStreamを同時に立ち上げることを考えてみてください。これはChangeNotifierを使うよりもよくないことのように思います。
8. 必要以上にRAMを使わずWidgetを更新します。GetはGetBuilderのクリエーターIDのみを保存し、必要に応じてGetBuilderを更新します。何千ものGetBuilderを作成したとしても、ID保存のためのメモリ消費量は非常に少ないです。GetBuilderを新規に作成するということは、実際にはクリエーターIDを持つ GetBuilder の状態を共有しているに過ぎないためです。GetBuilderごとに状態が新たに作成されるわけではないため、特に大規模なアプリケーションでは多くのRAMを節約できます。基本的にGetXで作成するアプリケーションは全体的にStatelessであり、いくつかのStatefulなWidget(GetBuilder内のWidget)は単一の状態を持っているため、一つを更新すればすべてが更新されます。
9. Getはアプリ全体の流れをよく把握しており、Controllerをメモリから破棄するタイミングを正確に知っています。実際の破棄はGetがやってくれるため、開発者が心配する必要はありません。
### 使用例
``` dart
// Controllerクラスを作成してGetxControllerを継承しましょう
class Controller extends GetxController {
int counter = 0;
void increment() {
counter++;
update();
// increment 実行時にcounter変数に依存するUIを更新。
// GetBuilderを使うWidgetの場合はupdate()が必要。
}
}
// ビュー側のクラスでGetBuilderを使ってcounter変数を組み込む
GetBuilder<Controller>(
init: Controller(), // 最初に使用するときのみ初期化
builder: (_) => Text(
'${_.counter}',
),
)
// Controllerの初期化は最初の1回だけ行ってください。同じControllerを再度 GetBuilder / GetX で使用する場合は初期化する必要はありません。コントローラは、それを「init」とマークしたウィジェットがデプロイされると同時に、自動的にメモリから削除されます。Getがすべて自動で行ってくれるので、何も心配することはありません。同じControllerを2つ立ち上げることがないよう、それだけご注意ください。
```
**最後に**
* 以上、Getを使った状態管理の手法をご説明させていただきました。
* 注: もっと柔軟に管理する手法として、initプロパティを使わない方法もあります。Bindingsを継承したクラスを作成し、dependenciesメソッドをoverrideしてその中でGet.put()でControllerを注入してください(複数可)。このクラスとUI側のクラスを紐づけることでControllerをそのRoute内で使用できます。そしてそのControllerを初めて使用するとき、Getはdependencies内を見て初期化を実行してくれます。このlazy(遅延、消極的)ロードを維持しつつ、不要になったControllerは破棄し続けます。具体的な仕組みについてはpub.devの例をご参照ください。
Routeを移動して以前使用したControllerのデータが必要になった場合は、再度GetBuilderを使用してください。initする必要はありません。
``` dart
class OtherClass extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: GetBuilder<Controller>(
builder: (s) => Text('${s.counter}'),
),
),
);
}
```
GetBuilderの外でControllerを使用する場合は、Controller内にgetterを作成しておくと便利です。Controller.to で呼び出しましょう。(もしくは `Get.find<Controller>()` を使うのもありです)
``` dart
class Controller extends GetxController {
/// 記述量を省くためにstaticメソッドにすることをおすすめします。
/// staticメソッド使う場合 → Controller.to.increment();
/// 使わない場合 → Get.find<Controller>().increment();
/// どちらを使ってもパフォーマンスに影響があったり副作用が出たりはしません。前者は型の指定が不要という違いがあるだけです
static Controller get to => Get.find(); // これを追加
int counter = 0;
void increment() {
counter++;
update();
}
}
```
これで以下のようにControllerに直接アクセスできます。
``` dart
FloatingActionButton(
onPressed: () {
Controller.to.increment(),
} // とっても簡単ですね!
child: Text("${Controller.to.counter}"),
),
```
FloatingActionButton を押すと counter変数 に依存するWidgetがすべて自動的に更新されます。
### Controllerインスタンスの扱い
次のような画面の流れがあるとします。
`画面A => 画面B (Controller X を使用) => 画面C (Controller X を使用)`
画面Aの段階ではまだ未使用なので、Controllerはメモリにありません(Getは基本lazyロードなので)。画面Bに遷移すると、Controllerがメモリ内に保存されます。画面Cでは画面Bと同じControllerを使用しているため、GetはBとCでControllerの状態を共有し、同じControllerがメモリ内に引き続きいることになります。画面Cを閉じてさらに画面Bを閉じ、画面Aに戻ったとしましょう。そこではControllerが使われていないため、GetはControllerをメモリから出してリソースを解放します。そこで再度画面Bに遷移すると、Controllerは再度メモリに保存されます。そして今度は画面Cに行かずに画面Aに戻ります。Getは同様にControllerをメモリから破棄してくれます。また、仮に画面CがControllerを使っておらず画面Cにいたとして、画面BをRouteスタックから削除したとしましょう。するとControllerを使用している画面(クラス)はなくなりますので、同様にメモリから破棄されます。Getが正常動作しないと考えられる唯一の例外は、画面Cにいるときに画面Bを誤ってRouteスタックから削除してしまい、Controllerの使用を試みたときです。この場合は、画面Bで作成されたControllerのクリエーターIDが削除されてしまったことが原因です(GetはクリエーターIDのないControllerはメモリから破棄するようプログラムされています)。もし意図があってこの事例に対応したい場合は、画面BのGetBuilderに "autoRemove: false" フラグを追加した上で、CクラスのGetBuilderに "adoptID: true" を追加してください。
### StatefulWidgetsはもういらない
StatefulWidgetsを使用すると、画面全体の状態を不必要に保存することになります。ウィジェットを最小限に再構築する必要がある場合は、Consumer/Observer/BlocProvider/GetBuilder/GetX/Obxの中に埋め込むことになりますが、それは別のStatefulWidgetになります。
StatefulWidgetはStatelessWidgetよりも多くのRAMが割り当てられます。これは1つや2つのStatefulWidgetでは大きな違いは産まないかもしれませんが、それが100もあった場合は確実に違いが出ます。
TickerProviderStateMixinのようなMixinを使用する必要がない限り、GetでStatefulWidgetを使用する必要はありません。
たとえばinitState()やdispose()メソッドなど、StatefulWidgetのメソッドをGetBuilderから直接呼び出すことも可能です。
``` dart
GetBuilder<Controller>(
initState: (_) => Controller.to.fetchApi(),
dispose: (_) => Controller.to.closeStreams(),
builder: (s) => Text('${s.username}'),
),
```
しかし、これよりもベターなアプローチはControllerの中で直接 onInit() や onClose() メソッドを呼び出すことです。
``` dart
@override
void onInit() {
fetchApi();
super.onInit();
}
```
* 注: コンストラクタを通じてControllerを立ち上げる必要はありません。このようなプラクティスは、パフォーマンス重視であるGetのControllerの作成や割り当ての原理、考え方から外れてしまいますコンストラクタ経由でインスタンスを作成すれば、実際に使用される前の段階でControllerを生成し、メモリを割り当てることになります。onInit() と onClose() メソッドはこのために作られたもので、Controllerのインスタンスが作成されたとき、または初めて使用されたときに呼び出されますGet.lazyPutを使用しているか否か次第。たとえば、データを取得するためにAPIを呼び出したい場合は initState/dispose の代わりに onInit() を使用し、Streamを閉じるなどのコマンドを実行する必要がある場合は onClose() を使用してください。
### Getの目的
このパッケージの目的は、Routeのナビゲーション、依存オブジェクトと状態の管理のための完全なソリューションを、開発者が外部パッケージに極力依存せずに済むような形で提供し、高度なコード分離性デカップリングを実現することです。それを確実なものとするため、Getはあらゆる高レベルおよび低レベルのFlutter APIを取り込んでいます。これによりビューとロジックを切り分けることが容易になり、UIチームにはWidgetの構築に集中してもらい、ビジネスロジック担当チームにはロジックに集中してもらうことができます。Getを使うことでよりクリーンな作業環境を構築することができるのです。
要するに、initState内でメソッドを呼び出してパラメーターを通じてControllerにデータを送信する必要も、そのためにControllerのコンストラクタを使用する必要もありません。Getには必要なタイミングでサービスを呼び出してくれう onInit() メソッドがあります。
Controllerが不要になれば、onClose() メソッドがジャストなタイミングでメモリから破棄してくれます。これにより、ビューとビジネスロジックを分離することができるのです。
GetxController 内に dispose() メソッドがあっても何も起こらないので記述しないでください。ControllerはWidgetではないので「dispose」できません。たとえばController内でStreamを使用していてそれを閉じたい場合は、以下のように onClose() メソッドにコードを記述してください。
``` dart
class Controller extends GetxController {
StreamController<User> user = StreamController<User>();
StreamController<String> name = StreamController<String>();
/// Streamを閉じる場合は dispose() ではなく onClose()
@override
void onClose() {
user.close();
name.close();
super.onClose();
}
}
```
Controllerのライフサイクルについて。
* onInit() はControllerが作成されたタイミングで実行されます。
* onClose() は onDelete() メソッドが実行される直前のタイミングで実行されます。
* Controllerが削除されるとそのAPIにアクセスすることはできません。文字通りメモリからの削除だからです。削除のトレースログも残りません。
### Controllerの様々な使用方法
ControllerインスタンスはGetBuilderのvalueを通じて使用することができます。
``` dart
GetBuilder<Controller>(
init: Controller(),
builder: (value) => Text(
'${value.counter}', // ここ
),
),
```
GetBuilderの外でControllerインスタンスを使う場合は、このアプローチをおすすめします。
``` dart
class Controller extends GetxController {
static Controller get to => Get.find();
[...]
}
// ビュー側で
GetBuilder<Controller>(
init: Controller(), // 最初に使うときだけ必要
builder: (_) => Text(
'${Controller.to.counter}', // ここ
)
),
```
もしくは
``` dart
class Controller extends GetxController {
// static get を省き、
[...]
}
// ビュー側で
GetBuilder<Controller>(
init: Controller(), // 最初に使うときだけ必要
builder: (_) => Text(
'${Get.find<Controller>().counter}', // ここ
),
),
```
* get_it や modular など他の依存オブジェクト管理ライブラリを使用しているため、単にControllerのインスタンスを渡したいだけの場合は、このような「非正規」な方法もあります。
``` dart
Controller controller = Controller();
[...]
GetBuilder<Controller>(
init: controller, // ここ
builder: (_) => Text(
'${controller.counter}', // ここ
),
),
```
### ユニークIDの設定
GetBuilderを使ってWidgetの更新をコントロールしたい場合は、このようにユニークIDを振ってください。
``` dart
GetBuilder<Controller>(
id: 'text'
init: Controller(), // 最初に使うときだけ必要
builder: (_) => Text(
'${Get.find<Controller>().counter}', // ここ
),
),
```
そして以下のようにWidgetを更新します。
``` dart
update(['text']);
```
さらに更新に条件を設けることができます。
``` dart
update(['text'], counter < 10);
```
GetXはこの更新を自動で行ってくれます。指定したIDを持ち、その変数に依存するWidgetのみを更新します。また変数を前の値と同じ値に変更しても、それが状態の変化を意味しない場合はメモリとCPUサイクルを節約するためにWidgetを更新しません (画面に 3 が表示されているときに、変数を再び 3 に変更したとします。このような場合にWidgetを更新する状態管理ソリューションも存在しますが、GetXでは実際に状態が変更された場合にのみ更新されます
## 状態管理ソリューションを混在させる
MixinBuilderはObxとGetBuilderを併用したいというリクエストから発想して作られました。これは ".obs" 変数の変更によるリアクティブな更新と、update() メソッドによるメカニカルな更新の両方を混在可能にします。ただし、GetBuiler / GetX / Obx / MixinBuilder の4つの中で最もリソースを消費するWidgetです。というのも、Widgetからのイベントを検知するためのSubscriptionに加えて、Controller自身のupdateメソッドも購読する必要があるからです。
MixinBuilderに使用するControllerクラスには、変数を `.obs`Observableとするかどうかに関わらず、GetxControllerを継承したものを使用してください。GetxControllerにはライフサイクルがあり、onInit() および onClose() メソッドでイベントを「開始」したり「終了」したりすることができます。
## StateMixin
`StateMixin<T>` を使うことでさらに `UI` の「状態」を便利に扱うことができます。
`with` を使って `StateMixin<T>` をControllerにミックスインしてください。Tにはモデルのクラス名が入ります。
``` dart
class Controller extends GetController with StateMixin<User>{}
```
状態を変更するには `change()` メソッドを使ってください。
パラメーターにはビューに渡すデータと「状態」をセットします。
```dart
change(data, status: RxStatus.success());
```
RxStatus の「状態」は以下の4つです。
``` dart
RxStatus.loading(); // ロード中
RxStatus.success(); // ロード成功
RxStatus.empty(); // データが空
RxStatus.error('message'); // エラー
```
それぞれの「状態」をUIで表すには以下のようにします。
```dart
class OtherClass extends GetView<Controller> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: controller.obx(
(state)=>Text(state.name),
// ここはカスタムのロードインジケーターでも可能ですが、
// デフォルトは Center(child:CircularProgressIndicator())
onLoading: CustomLoadingIndicator(),
onEmpty: Text('No data found'),
// ここもカスタムのエラーWidgetでも構いませんが、
// デフォルトは Center(child:Text(error))
onError: (error)=>Text(error),
),
);
}
```
## GetBuilder VS GetX VS Obx VS MixinBuilder
私は10年間プログラミングに携わってきて、いくつか貴重な教訓を得ることができました。
リアクティブプログラミングに初めて触れたとき、「おお、これはすごい」と感嘆せずにはいられませんでしたし、実際にすごいものでした。
しかし、リアクティブプログラミングはすべての状況に適しているわけではありません。多くの場合、必要なのは2,3のWidgetの状態を同時に更新すること、またはローカルでの一時的な状態の変更であり、これらの場合はリアクティブである必要はありません。
リアクティブプログラミングのRAM消費量の多さは、必要なときだけ、かつ1つのWidgetだけ同時に更新するようすることである程度補うことができます。ただ、たとえば80ものオブジェクトを持つリストがあったとして、それぞれが複数のStreamを持つのは得策ではありません。Dartのインスペクターを開いて、StreamBuilderがどれだけRAMを消費しているか見てみてください。私が伝えたいことを理解してもらえると思います。
そのことを念頭に私はシンプルな状態管理ソリューションを作りました。このシンプルさに期待することは、リソース面で経済的である点、Widget単位ではなくブロック単位で状態を更新できる点であるべきです。
GetBuilderはRAM消費の面で最も経済的なソリューションだと信じていますもしあれば、ぜひ教えてください
しかし、GetBuilderは依然として update() により更新がかかるスタイルのメカニカルな状態管理ソリューションであり、notifyListeners() と呼び出し回数は変わりありません。
一方で、ここでリアクティブプログラミングを使わないのは車輪の再発明なんじゃないかと思えるような状況もあります。この点を考慮して、GetX() は先進的な状態管理の手法を提供するために作られました。必要なものを必要なときだけ更新し、エラーが発生してユーザーが300ものイベントを同時に送信したとしても、GetX() は状態の変化をフィルタリングして画面を更新してくれます。
GetX() は他のリアクティブな状態管理ソリューションに比べて経済的であることに変わりはありませんが、GetBuilder() よりは少しだけ多くのRAMを消費します。その点を考慮し、リソース消費を最大限活かすことを目指して Obx() は開発されました。GetX() や GetBuilder() と異なり、Obx() の中でControllerを初期化することはできません。Obx() は、子Widgetからの更新イベントを受け取る Stream購読Widgetでしかありません。GetX() よりは経済的ですが、GetBuilder() には負けます。GetBuilder() はWidgetのハッシュ値と状態のsetterを保持しているだけなので、これはある意味当然です。Obx() はControllerの型を指定する必要がなく、複数の異なるコントローラからの変更を聞くことができます。ただし、Obx() の外かBindingsで事前にControllerを初期化しておく必要があります。

View File

@@ -0,0 +1,378 @@
# 종속성 관리
- [종속성 관리](#종속성-관리)
- [인스턴스 메서드](#인스턴스-메서드)
- [Get.put()](#getput)
- [Get.lazyPut](#getlazyput)
- [Get.putAsync](#getputasync)
- [Get.create](#getcreate)
- [인스턴스화 된 메서드/클래스 사용](#인스턴스화-된-메서드/클래스-사용)
- [메서드간의 차이점](#메서드간의-차이점)
- [바인딩](#바인딩)
- [사용 방법](#사용-방법)
- [BindingsBuilder](#bindingsbuilder)
- [SmartManagement](#smartmanagement)
- [변경하는 방법](#변경하는-방법)
- [SmartManagement.full](#smartmanagementfull)
- [SmartManagement.onlyBuilders](#smartmanagementonlybuilders)
- [SmartManagement.keepFactory](#smartmanagementkeepfactory)
- [바인딩이 작동하는 자세한 설명](#바인딩이-작동하는-자세한-설명)
- [주석](#주석)
Get은 Provider context, inheritedWidget 없이 단 1줄의 코드로 Bloc 나 Controller 같은 클래스를 찾을수 있는 간단하고 강력한 종속성 관리자가 있습니다.
```dart
Controller controller = Get.put(Controller()); // Controller controller = Controller(); 대체
```
사용중인 클래스 내에서 클래스를 인스턴스화하는 대신 Get 인스턴스 내에서 인스턴스화하여 앱 전체에서 사용할 수 있습니다.
그러고나면 컨트롤러 (또는 Bloc 클래스)를 정상적으로 사용할 수 있습니다.
- 주석: Get의 상태 관리자를 사용하는 경우 [바인딩](#바인딩) api에 더 많은 주의를 기울여야합니다. 그러면 뷰를 컨트롤러에 더 쉽게 연결할 수 있습니다.
- 주석²: Get의 종속성 관리는 패키지의 다른 부분에서 분리되므로 예를 들어 앱이 이미 상태 관리자를 사용하고있는 경우라도(하나라도 상관 없음) 변경할 필요가 없습니다. 아무 문제 없이 종속성 주입 관리자를 사용할 수 있습니다.
## 인스턴스 메서드
메서드와 구성 파라미터는 다음과 같습니다:
### Get.put()
종속성 인스턴스화의 가장 흔한 방법 입니다. 예를 들어 뷰의 controller들에 좋습니다.
```dart
Get.put<SomeClass>(SomeClass());
Get.put<LoginController>(LoginController(), permanent: true);
Get.put<ListItemController>(ListItemController, tag: "some unique string");
```
put 사용시 설정 가능한 모든 사항:
```dart
Get.put<S>(
// 필수: cotroller나 어떤것이든 get에 저장하려는 클래스
// 주석: "S"는 모든 유형의 클래스가 가능합니다.
S dependency
// 선택: 동일한 유형의 여러 클래스를 사용하기를 원하면
// 일반적으로 Get.find<Controller>() 로 클래스를 가져오므로
// 어떤 인스턴스인지 구분을 위해 tag를 사용해야합니다.
// 고유한 string 이여야 합니다.
String tag,
// 선택: 기본적으로 get은 더이상 사용하지 않는 인스턴스는 dispose 합니다. by default, get will dispose instances after they are not used anymore (example,
// (예를 들어 뷰의 controller가 닫힌 경우) 하지만 sharedPreferences와 같은 인스턴스는 앱 전체에서 유지되어야 할 필요가 있습니다.
// 이런 경우 사용합니다.
// 기본값은 false
bool permanent = false,
// 선택: 테스트에서 추상 클래스를 사용한 후에 다른 클래스로 교체하고 테스트를 수행합니다.
// 기본값은 false
bool overrideAbstract = false,
// 선택: 자체 종속성 대신에 함수로 종속성을 생성합니다.
// 이것은 일반적으로 사용되지 않습니다.
InstanceBuilderCallback<S> builder,
)
```
### Get.lazyPut
인스턴스하게 사용하는 경우에만 의존성을 lazyLoad 할 수 있습니다. 계산 비용이 많이 드는 클래스나 한곳에서 다양한 클래스를 당장 사용하지 않으면서 인스턴스화 하기를 원한다면(Bindings 클래스처럼) 매우 유용합니다.
```dart
/// ApiMock은 처음으로 Get.find<ApiMock>을 사용하는 경우에만 호출됩니다.
Get.lazyPut<ApiMock>(() => ApiMock());
Get.lazyPut<FirebaseAuth>(
() {
// 어떤 로직이 필요하다면 ...
return FirebaseAuth();
},
tag: Math.random().toString(),
fenix: true
)
Get.lazyPut<Controller>( () => Controller() )
```
lazyPut을 사용시 설정 가능한 모든 사항:
```dart
Get.lazyPut<S>(
// 필수: 이 메서드는 처음으로 클래스가 호출할 때 실행될 것입니다
InstanceBuilderCallback builder,
// 선택: Get.put()과 같이 같은 클래스를 다중으로 인스턴스할 경우 사용합니다.
// 고유값이어야 합니다.
String tag,
// 선택: "permanent"와 유사합니다. 차이점은 인스턴스가 사용되지 않으면 폐기되지만
// 다시 사용할 때 Get이 바인딩 api의 "SmartManagement.keepFactory"와 동일하게 인스턴스를 재생성한다는 것입니다.
// 기본값은 false
bool fenix = false
)
```
### Get.putAsync
만약 비동기로 인스턴스를 등록하길 원하면 `Get.putAsync`를 사용할 수 있습니다.:
```dart
Get.putAsync<SharedPreferences>(() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setInt('counter', 12345);
return prefs;
});
Get.putAsync<YourAsyncClass>( () async => await YourAsyncClass() )
```
putAsync 사용시 설정 가능한 모든 사항:
```dart
Get.putAsync<S>(
// 필수: 클래스를 인스턴스화 하기 위해 실행되는 비동기 메서드입니다.
AsyncInstanceBuilderCallback<S> builder,
// 선택: Get.put()과 같이 같은 클래스를 다중으로 인스턴스할 경우 사용합니다.
// 고유값이어야 합니다.
String tag,
// 선택: Get.put()과 같이 앱 유지중에 인스턴스가 활성되어야 하는 경우 사용합니다.
// 기본값은 false
bool permanent = false
)
```
### Get.create
이것은 까다롭습니다. 이것이 무엇인지 상세한 설명과 다른것과의 차이점에 대해서는 [메서드간의 차이점:](#메서드간의-차이점) 섹션에서 확인할 수 있습니다.
```dart
Get.Create<SomeClass>(() => SomeClass());
Get.Create<LoginController>(() => LoginController());
```
create 사용시 설정 가능한 모든 사항:
```dart
Get.create<S>(
// 필수: `Get.find()`가 호출 될 때마다 만들어진 클래스를 반환하는 메서드입니다.
// 예시: Get.create<YourClass>(() => YourClass())
FcBuilderFunc<S> builder,
// 선택: Get.put()과 거의 동일하지만 동일 클래스의 여러 인스턴스가 필요할 때 사용합니다.
// 개개의 리스트별로 controller가 필요한 경우 유용합니다.
// 고유값이어야 합니다. 단지 tag에서 name으로 변경되었습니다.
String name,
// 선택: Get.put()과 같이 앱 유지중에 인스턴스가 활성되어야 하는 경우 사용합니다.
// Get.create에서 다른점은
// permanent의 기본값이 true 입니다.
bool permanent = true
```
## 인스턴스화 된 메서드/클래스 사용
여러 경로를 탐색했고 controller에 남겨진 데이터가 필요하다고 상상해보세요. Provider 또는 Get_it과 결합된 상태 관리자가 필요합니다. 맞습니까? Get은 아닙니다.
어떤 종속적인 추가가 필요 없고 단지 Get에게 controller를 "find"하라고 하면 됩니다:
```dart
final controller = Get.find<Controller>();
// OR
Controller controller = Get.find();
// 그렇습니다. 마법 같아요. Get은 controller를 찾고 배달해 줍니다.
// Get은 백만개의 contrller를 인스턴스화해서 가질수 있고 항상 올바르게 전달해 줍니다.
```
그리고 나서 얻어낸 controller에서 데이터를 가져올 수 있습니다:
```dart
Text(controller.textFromApi);
```
반환된 값은 일반 클래스라서 무엇이든 할 수 있습니다:
```dart
int count = Get.find<SharedPreferences>().getInt('counter');
print(count); // out: 12345
```
Get의 인스턴스에서 삭제합니다:
```dart
Get.delete<Controller>(); // 보통 GetX는 미사용 controller를 삭제하기 때문에 수행할 필요가 없습니다
```
## 메서드간의 차이점
첫째, Get.lazyPut의 `fenix`와 다른 메서드들의 `permanent`을 살펴보겠습니다.
`permanent``fenix` 사이의 근본적인 차이점은 인스턴스를 저장하는 방법입니다.
추가 내용: 기본적으로 GetX는 사용하지 않을때 인스턴스를 삭제합니다.
이것은 다음을 의미합니다: 만약 화면 1이 컨트롤러 1을 가지고 있고 화면 2가 컨트롤러 2를 가졌을때 스택에서 첫번째 경로가 제거되면(`Get.off()``Get.offNamed()`를 사용했을 때처럼) 컨트롤러 1은 더이상 사용되지 않기 때문에 지워질 것입니다.
하지만 `permanent:true`를 설정하면 컨트롤러가 화면이 바뀌는동안 사라지지 않을 것입니다. 즉, 전체 어플리케이션이 실행되는 동안에 계속 유지하려고 하는 서비스에 매우 유용합니다.
반면 `fenix`는 화면이 바뀌는동안 컨트롤러가 사라지는 것은 상관없지만, 컨트롤러가 필요한 시점에 살아있어야 하는 서비스를 위해 존재합니다. 그래서 기본적으로는 사용하지 않는 컨트롤러/서비스/클래스를 폐기하지만, 필요한 경우 새 인스턴스를 "남아있는 흔적으로부터 다시 생성"합니다.
메서드간 차이점에 대해:
- Get.put과 Get.putAsync는 동일한 생성 명령을 따르지만 두번째가 비동기 메서드를 사용하는 것이 차이점입니다: 두 메서드는 인스턴스를 생성하고 초기화 합니다. 이것은 `permanent: false``isSingleton: true` 파라미터들과 내부 `insert` 메서드를 사용하여 메모리에 직접 추가됩니다.(여기의 isSingleton 파라미터의 목적은 `dependency`에 의한 종속성을 사용할 것인지 `FcBuilderFunc`에 의한 종속성을 사용할 것인지 알려주는 것입니다.) 이후에 `Get.find()`는 즉시 초기화한 메모리안의 인스턴스를 호출합니다.
- Get.create: 이름 그대로 종속성을 "생성"합니다! `Get.put()`과 마찬가지로 내부 메서드 `insert`를 호출하여 인스턴스화 합니다. 그러나 `permanent`가 true가 되고 `isSingleton`이 false가 됩니다(종속성을 "생성중"인 상태라 싱글톤 인스턴스가 될 방법이 없어서 false 입니다.) 그리고 `permanent: true`이기 때문에 기본적으로 화면 전환간에 손실되지 않는 장점이 있습니다! 또한 `Get.find()`는 즉시 호출되지 않으며 호출될 화면에서 사용되기를 기다립니다. `permanent` 파라미터를 사용하기 위한 방법으로 만들어졌습니다. 다음의 가치를 가지고 있습니다. 생성을 위한 목적으로 `Get.create()`는 공유되는 인스턴스가 아니지만 폐기되지 않습니다. 예를들어 리스트뷰 안의 버튼은 리스트를 위한 고유한 인스턴스입니다. - 이때문에 Get.create는 GetWidget과 함께 사용되어야만 합니다.
- Get.lazyPut: 이름 그대로 lazy 처리됩니다. 인스턴스가 만들어지나 즉시 사용되도록 호출되지 않고 호출되기를 기다립니다. 다른 메서드와 다르게 `insert`가 여기에서 호출되지 않습니다. 대신 인스턴스는 메모리의 다른 부분에 추가됩니다. 인스턴스가 재생성 가능한지 아닌지를 책임지는 부분으로 "factory"라고 부릅니다. 나중에 사용할 어떤 것을 생성하기 원한다면 지금 사용했던 것과 섞이지 않아야 할 것입니다. 그리고 여기에서 `fenix` 마법이 시작됩니다: `fenix: false`를 그대로두고 `smartManagement``keepFactory`가 아니면 `Get.find`를 사용할 때 인스턴스는 "factory"에서 공통 인스턴스 메모리 영역으로 위치가 변경됩니다. 바로 그뒤에 기본적으로 "factory"에서 제거됩니다. 이제 `fenix: true`로 설정하면 인스턴스는 전용부분에서 계속 존재하며 공통 영역으로 이동하여 미래에 다시 호출됩니다.
## 바인딩
이 패키지의 가장 큰 특이한점 중 하나는 아마도 라우트, 상태 관리자, 종속성 관리자의 완전한 통합의 가능성 일 것입니다.
스택에서 라우트가 삭제되면 모든 컨트롤러, 변수 및 관련된 인스턴스 오브젝트가 메모리에서 제거됩니다. 스트림이나 타이머를 사용중이면 자동적으로 종료되고 이것에 대한 어떤 걱정도 할 필요가 없습니다.
Get의 2.10 버전에는 Bindings API를 완전히 구현했습니다.
이제 init 메서드는 더 이상 사용할 필요가 없습니다. 원하지 않으며 컨트롤러 타입도 필요 없습니다. 컨트롤러와 서비스를 위한 적절한 위치에서 시작할 수 있습니다.
바인딩 클래스는 상태 관리자와 종속성 관리자에 라우트를 "결합"하는 동시에 종속성 주입을 분리하는 클래스입니다.
이를 통해 Get은 특정 컨트롤러가 사용될때 표시되는 스크린을 알고 어디서 어떻게 이것이 제거 되는지 알수 있습니다.
추가로 Binding 클래스로 SmartManager 구성을 제어 할 수 있습니다. 스택에서 경로를 제거하거나 경로를 사용한 위젯이 배치되거나 둘 다 배치되지 않을 때 정렬되도록 종속성을 설정 할 수 있습니다. 지능적으로 종속성 관리가 동작하지만 원하는대로 구성 할 수 있습니다.
### 사용 방법
- class를 생성하고 Binding을 implement 합니다.
```dart
class HomeBinding implements Bindings {}
```
IDE가 자동적으로 "종속적인" 메서드를 오버라이딩할지 요청하며 램프를 클릭하기만 하면 됩니다. 그리고 메서드를 오버라이딩하고 해당 경로에 사용할 모든 클래스들을 추가하면 됩니다:
```dart
class HomeBinding implements Bindings {
@override
void dependencies() {
Get.lazyPut<HomeController>(() => HomeController());
Get.put<Service>(()=> Api());
}
}
class DetailsBinding implements Bindings {
@override
void dependencies() {
Get.lazyPut<DetailsController>(() => DetailsController());
}
}
```
이제 라우트에 라우트, 종속성, 상태 관리자 사이를 연결하는데 해당 바인딩을 사용할 것이라고 알리기만 하면 됩니다.
- 명명된 라우트 사용법:
```dart
getPages: [
GetPage(
name: '/',
page: () => HomeView(),
binding: HomeBinding(),
),
GetPage(
name: '/details',
page: () => DetailsView(),
binding: DetailsBinding(),
),
];
```
- 일반 라우트 사용법:
```dart
Get.to(Home(), binding: HomeBinding());
Get.to(DetailsView(), binding: DetailsBinding())
```
이제 어플리케이션의 어디서도 메모리 관리에 대해서 걱정하지 않아도 됩니다. Get이 그것을 처리 할 것입니다.
Binding 클래스는 라우트가 호출될 때 불려집니다. GetMaterialApp의 "initialBinding"에서 모든 종속성을 추가하여 생성할 수 있습니다.
```dart
GetMaterialApp(
initialBinding: SampleBind(),
home: Home(),
);
```
### BindingsBuilder
바인딩을 생성하는 기본 방법은 바인딩을 구현한 클래스를 만드는 것 입니다.
하지만 대안으로 `BindingsBuilder` 콜백을 사용하여 간단하게 원하는 것을 인스턴스화하는 함수를 사용할 수 있습니다.
예시:
```dart
getPages: [
GetPage(
name: '/',
page: () => HomeView(),
binding: BindingsBuilder(() {
Get.lazyPut<ControllerX>(() => ControllerX());
Get.put<Service>(()=> Api());
}),
),
GetPage(
name: '/details',
page: () => DetailsView(),
binding: BindingsBuilder(() {
Get.lazyPut<DetailsController>(() => DetailsController());
}),
),
];
```
이 방법은 각 라우트에 대해 한개의 바인딩 클래스 만드는 것을 피할수 있어서 더 간단하게 할 수 있습니다.
두 방법 모두 완벽하게 작동하며 취향에 맞춰서 사용하시면 됩니다.
### SmartManagement
GetX는 기본적으로 메모리에서 사용되지 않는 컨트롤러들을 제거합니다. 문제가 생기거나 적절히 사용하지 않는 위젯도 마찬가지 입니다.
이것이 종속성 관리의 `full`모드 입니다.
하지만 GetX 클래스의 제거를 제어하는 방식을 변경하기 원한다면 `SmartManagement` 클래스로 다른 행동을 설정할 수 있습니다.
#### 변경하는 방법
구성을 변경(보통은 필요 없습니다)하려면 이렇게 하세요:
```dart
void main () {
runApp(
GetMaterialApp(
smartManagement: SmartManagement.onlyBuilders // 이곳
home: Home(),
)
)
}
```
#### SmartManagement.full
이것이 기본입니다. permanent가 설정되지 않으면 사용되지 않는 클래스를 제거합니다. 대부분의 경우 이 구성을 변경하지 않고 유지하십시오. GetX가 처음이라면 바꾸지 마십시오.
#### SmartManagement.onlyBuilders
이 옵션은 오직 컨트롤러가 `init:`에서 시작되었거나 `Get.lazyPut()`으로 바인딩되어 로드되면 제거됩니다.
`Get.put()`이나 `Get.putAsync()` 또는 다른 접근을 를 사용하면 SmartManagement는 종속성 제거를 위한 권한을 가지지 못합니다.
기본 동작은 SmartManagement.onlyBuilders와 다르게 "Get.put"으로 인스턴스화된 위젯들도 삭제됩니다.
#### SmartManagement.keepFactory
SmartManagement.full과 같이 더이상 사용되지 않으면 종속성이 제거됩니다. 하지만 factory가 유지되어 다시 인스턴스가 필요하면 종속성이 재생성됩니다.
### 바인딩이 작동하는 자세한 설명
바인딩은 다른 화면으로 이동하려고 클릭하는 순간에 임시 factory를 생성합니다. 그리고 화면 전환 애니메이션이 발생하는 즉시 제거됩니다.
이 행위는 매우 빨라 분석기에 등록도 되지 않습니다.
다시 화면으로 이동하면 새로운 임시 factory를 호출하므로 SmartManagement.keepFactory 사용을 선택할 수도 있습니다. 그러나 바인딩을 생성하지 않거나 동일한 바인딩에 대해 모든 종속성을 유지하고 싶다면 도움이 됩니다.
Factory는 적은 메모리만 사용하고 인스턴스를 가지지 않지만 원하는 클래스의 "형태"를 가진 함수를 가집니다.
메모리에서 매우 적은 비용을 가지지만 이 라이브러리의 목적이 최소 리소스를 사용하여 가능한 최대 성능을 가지는 것이라 GetX는 기본적으로 factory를 제거합니다.
가장 편한방법을 취하십시오.
## 주석
- 다중 바인딩을 사용한다면 SmartManagement.keepFactory를 사용하지 마세요. 바인딩을 사용하지 않거나 GetMaterialApp의 initialBinding에 연결한 1개의 바인딩만 설계하세요.
- 바인딩의 사용은 완전히 선택사항입니다. 클래스에서 `Get.put()``Get.find()`를 사용하면 아무 문제없이 컨트롤러가 주어집니다.
그러나 서비스나 다른 추상적인 동작을 원하면 더 나은 구성의 바인딩을 추천합니다.

View File

@@ -0,0 +1,564 @@
- [라우트 관리](#라우트-관리)
- [사용하는 방법](#사용하는-방법)
- [이름없는 라우트 탐색](#이름없는-라우트-탐색)
- [이름있는 라우트 탐색](#이름있는-라우트-탐색)
- [이름있는 라우트에 데이터 보내기](#이름있는-라우트에-데이터-보내기)
- [동적 url 링크](#동적-url-링크)
- [미들웨어](#미들웨어)
- [context 없이 탐색](#context-없이-탐색)
- [SnackBars](#snackbars)
- [Dialogs](#dialogs)
- [BottomSheets](#bottomsheets)
- [중첩된 탐색](#중첩된-탐색)
# 라우트 관리
라우트 관리가 문제 있는 경우 GetX가 모든 것을 완벽히 설명해줍니다.
## 사용하는 방법
pubspec.yaml 파일에 추가:
```yaml
dependencies:
get:
```
context 없이 routes/snackbars/dialogs/bottomsheets을 사용하거나 고급 GetX API를 사용하려면 MaterialApp 앞에 "Get"만 추가하여 GetMaterialApp으로 바꿔서 이용하세요!
```dart
GetMaterialApp( // 이전: MaterialApp(
home: MyHome(),
)
```
## 이름없는 라우트 탐색
새 화면으로 이동:
```dart
Get.to(NextScreen());
```
snackbars, dialogs, bottomsheets 또는 Navigator.pop(context);로 보통 닫았던 것들을 닫기
```dart
Get.back();
```
다음 화면으로 이동하고 이전 화면에서 돌아오지 않는 경우 (스플래시나 로그인 화면 등을 사용하는 경우)
```dart
Get.off(NextScreen());
```
다음 화면으로 이동하고 이전 화면이 모두 닫히는 경우 (장바구니, 투표, 테스트에 유용함)
```dart
Get.offAll(NextScreen());
```
다음 화면으로 이동하고 돌아올때 바로 데이터를 받거나 업데이트할 경우:
```dart
var data = await Get.to(Payment());
```
다른 화면에서 이전화면으로 데이터를 전달할때:
```dart
Get.back(result: 'success');
```
그리고 사용방법:
예시:
```dart
if(data == 'success') madeAnything();
```
우리의 문법을 배우고 싶지 않습니까?
Navigator를 navigator로 바꾸시면 됩니다. 그리고 context를 사용하지 않아도 표준 navigator의 모든 기능이 가능합니다.
예시:
```dart
// 기본 Flutter navigator
Navigator.of(context).push(
context,
MaterialPageRoute(
builder: (BuildContext context) {
return HomePage();
},
),
);
// GetX는 context 필요 없이 Flutter 문법을 사용
navigator.push(
MaterialPageRoute(
builder: (_) {
return HomePage();
},
),
);
// GetX 문법 (이것은 동의하지 않겠지만 더 좋습니다)
Get.to(HomePage());
```
## 이름있는 라우트 탐색
- namedRoutes로 탐색하기를 선호하면 GetX도 지원합니다.
nextScreen으로 이동
```dart
Get.toNamed("/NextScreen");
```
다음으로 이동하고 트리에서 이전 화면을 지웁니다.
```dart
Get.offNamed("/NextScreen");
```
다음으로 이동하고 트리에서 이전 화면 전체를 지웁니다.
```dart
Get.offAllNamed("/NextScreen");
```
GetMaterialApp를 사용하여 라우트들을 정의:
```dart
void main() {
runApp(
GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => MyHomePage()),
GetPage(name: '/second', page: () => Second()),
GetPage(
name: '/third',
page: () => Third(),
transition: Transition.zoom
),
],
)
);
}
```
정의 안된 라우트로 이동시 제어 (404 에러), GetMaterialApp에 unknownRoute를 정의할 수 있습니다.
```dart
void main() {
runApp(
GetMaterialApp(
unknownRoute: GetPage(name: '/notfound', page: () => UnknownRoutePage()),
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => MyHomePage()),
GetPage(name: '/second', page: () => Second()),
],
)
);
}
```
### 이름있는 라우트에 데이터 보내기
무엇이든 인수를 통해 전달합니다. GetX는 String, Map, List, 클래스 인스턴스등 모든 것을 허용합니다.
```dart
Get.toNamed("/NextScreen", arguments: 'Get is the best');
```
클래스 또는 컨트롤러에서:
```dart
print(Get.arguments);
// 출력: Get is the best
```
### 동적 url 링크
GetX는 웹과 같이 향상된 동적 url을 제공합니다. 웹 개발자들은 아마 Flutter에서 이미 이 기능을 원하고 있을 것 입니다. 대부분의 경우 패키지가 이 기능을 약속하고 URL이 웹에서 제공하는 것과 완전히 다른 구문을 제공하는 것을 보았을 것입니다. 하지만 GetX는 이 기능을 해결합니다.
```dart
Get.offAllNamed("/NextScreen?device=phone&id=354&name=Enzo");
```
controller/bloc/stateful/stateless 클래스에서:
```dart
print(Get.parameters['id']);
// 출력: 354
print(Get.parameters['name']);
// 출력: Enzo
```
GetX는 쉽게 NamedParameters 전달을 할 수 있습니다:
```dart
void main() {
runApp(
GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(
name: '/',
page: () => MyHomePage(),
),
GetPage(
name: '/profile/',
page: () => MyProfile(),
),
//You can define a different page for routes with arguments, and another without arguments, but for that you must use the slash '/' on the route that will not receive arguments as above.
GetPage(
name: '/profile/:user',
page: () => UserProfile(),
),
GetPage(
name: '/third',
page: () => Third(),
transition: Transition.cupertino
),
],
)
);
}
```
경로 명으로 데이터 보냄
```dart
Get.toNamed("/profile/34954");
```
다음 화면에서 파라미터로 데이터를 가져옴
```dart
print(Get.parameters['user']);
// 출력: 34954
```
또는 이와 같은 여러 매개 변수를 보냅니다.
```dart
Get.toNamed("/profile/34954?flag=true");
```
두 번째 화면에서 일반적으로 매개 변수별로 데이터를 가져옵니다.
```dart
print(Get.parameters['user']);
print(Get.parameters['flag']);
// 출력: 34954 true
```
이제 Get.toNamed()를 사용하여 어떤 context도 없이 명명된 라우트를 탐색하고 (BLoC 또는 Controller 클래스로 부터 직접 라우트를 호출할 수 있음) 앱이 웹으로 컴파일되면 경로는 url에 표시됩니다. <3
### 미들웨어
만약 GetX 이벤트를 받아서 행동을 트리거 하려면 routingCallback을 사용하면 가능합니다.
```dart
GetMaterialApp(
routingCallback: (routing) {
if(routing.current == '/second'){
openAds();
}
}
)
```
GetMaterialApp을 사용하지 않는다면 수동 API를 사용해서 Middleware observer를 추가할 수 있습니다.
```dart
void main() {
runApp(
MaterialApp(
onGenerateRoute: Router.generateRoute,
initialRoute: "/",
navigatorKey: Get.key,
navigatorObservers: [
GetObserver(MiddleWare.observer), // 여기 !!!
],
),
);
}
```
MiddleWare class 생성
```dart
class MiddleWare {
static observer(Routing routing) {
/// 각 화면의 routes, snackbars, dialogs와 bottomsheets에서 추가하여 받을 수 있습니다.
/// If you need to enter any of these 3 events directly here,
/// you must specify that the event is != Than you are trying to do.
if (routing.current == '/second' && !routing.isSnackbar) {
Get.snackbar("Hi", "You are on second route");
} else if (routing.current == '/third'){
print('last route called');
}
}
}
```
이제, 코드에서 Get을 사용하세요:
```dart
class First extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.add),
onPressed: () {
Get.snackbar("hi", "i am a modern snackbar");
},
),
title: Text('First Route'),
),
body: Center(
child: ElevatedButton(
child: Text('Open route'),
onPressed: () {
Get.toNamed("/second");
},
),
),
);
}
}
class Second extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.add),
onPressed: () {
Get.snackbar("hi", "i am a modern snackbar");
},
),
title: Text('second Route'),
),
body: Center(
child: ElevatedButton(
child: Text('Open route'),
onPressed: () {
Get.toNamed("/third");
},
),
),
);
}
}
class Third extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Third Route"),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Get.back();
},
child: Text('Go back!'),
),
),
);
}
}
```
## context 없이 탐색
### SnackBars
Flutter로 간단한 SnackBar를 사용하려면 Scaffold의 context가 반드시 주어지거나 Scaffold에 GlobalKey를 추가해서 사용해야만 합니다.
```dart
final snackBar = SnackBar(
content: Text('Hi!'),
action: SnackBarAction(
label: 'I am a old and ugly snackbar :(',
onPressed: (){}
),
);
// 위젯 트리에서 Scaffold를 찾아서 사용하면
// SnackBar가 보여집니다.
Scaffold.of(context).showSnackBar(snackBar);
```
Get을 사용할때:
```dart
Get.snackbar('Hi', 'i am a modern snackbar');
```
Get을 사용하면 코드의 어디에서든지 Get.snackbar를 호출하거나 원하는데로 수정하기만 하면 됩니다!
```dart
Get.snackbar(
"Hey i'm a Get SnackBar!", // title
"It's unbelievable! I'm using SnackBar without context, without boilerplate, without Scaffold, it is something truly amazing!", // message
icon: Icon(Icons.alarm),
shouldIconPulse: true,
onTap:(){},
barBlur: 20,
isDismissible: true,
duration: Duration(seconds: 3),
);
////////// ALL FEATURES //////////
// Color colorText,
// Duration duration,
// SnackPosition snackPosition,
// Widget titleText,
// Widget messageText,
// bool instantInit,
// Widget icon,
// bool shouldIconPulse,
// double maxWidth,
// EdgeInsets margin,
// EdgeInsets padding,
// double borderRadius,
// Color borderColor,
// double borderWidth,
// Color backgroundColor,
// Color leftBarIndicatorColor,
// List<BoxShadow> boxShadows,
// Gradient backgroundGradient,
// TextButton mainButton,
// OnTap onTap,
// bool isDismissible,
// bool showProgressIndicator,
// AnimationController progressIndicatorController,
// Color progressIndicatorBackgroundColor,
// Animation<Color> progressIndicatorValueColor,
// SnackStyle snackStyle,
// Curve forwardAnimationCurve,
// Curve reverseAnimationCurve,
// Duration animationDuration,
// double barBlur,
// double overlayBlur,
// Color overlayColor,
// Form userInputForm
///////////////////////////////////
```
기존 스낵바를 선호하거나 한 줄만 추가하는 것을 포함하여 처음부터 커스텀하려는 경우(Get.snackbar는 필수로 제목과 메시지를 사용함) 다음을 사용할 수 있습니다.
Get.snackbar가 빌드된 RAW API를 제공하는`Get.rawSnackbar ();`.
### Dialogs
dialog 열기:
```dart
Get.dialog(YourDialogWidget());
```
default dialog 열기:
```dart
Get.defaultDialog(
onConfirm: () => print("Ok"),
middleText: "Dialog made in 3 lines of code"
);
```
showGeneralDialog 대신에 Get.generalDialog를 사용할 수 있습니다.
cupertinos를 포함한 다른 모든 Flutter 대화 상자 위젯의 경우 context 대신 Get.overlayContext를 사용하고 코드의 어느 곳에서나 열 수 있습니다.
오버레이를 사용하지 않는 위젯의 경우 Get.context를 사용할 수 있습니다.
이 두 context는 탐색 context 없이 inheritedWidget이 사용되는 경우를 제외하고 99%의 경우에 UI의 context를 대체하여 동작합니다.
### BottomSheets
Get.bottomSheet는 showModalBottomSheet와 같지만 context가 필요 없습니다.
```dart
Get.bottomSheet(
Container(
child: Wrap(
children: <Widget>[
ListTile(
leading: Icon(Icons.music_note),
title: Text('Music'),
onTap: () {}
),
ListTile(
leading: Icon(Icons.videocam),
title: Text('Video'),
onTap: () {},
),
],
),
)
);
```
## 중첩된 탐색
GetX는 Fultter의 중첩된 탐색을 더 쉽게 만듭니다.
context가 필요 없고 Id로 탐색 스택을 찾을 수 있습니다.
- 주석: 병렬 탐색 스택을 만드는 것은 위험 할 수 있습니다. 이상적인 것은 NestedNavigators를 사용하지 않거나 아껴서 사용하는 것입니다. 프로젝트에 필요한 경우 여러 탐색 스택을 메모리에 유지하는 것이 RAM 소비에 좋지 않을 수 있음을 명심하십시오.
간단합니다:
```dart
Navigator(
key: Get.nestedKey(1), // index로 key를 생성
initialRoute: '/',
onGenerateRoute: (settings) {
if (settings.name == '/') {
return GetPageRoute(
page: () => Scaffold(
appBar: AppBar(
title: Text("Main"),
),
body: Center(
child: TextButton(
color: Colors.blue,
onPressed: () {
Get.toNamed('/second', id:1); // index로 중첩된 경로를 탐색
},
child: Text("Go to second"),
),
),
),
);
} else if (settings.name == '/second') {
return GetPageRoute(
page: () => Center(
child: Scaffold(
appBar: AppBar(
title: Text("Main"),
),
body: Center(
child: Text("second")
),
),
),
);
}
}
),
```

View File

@@ -0,0 +1,717 @@
- [상태 관리자](#상태-관리자)
- [반응형 상태 관리자](#반응형-상태-관리자)
- [장점](#장점)
- [최대의 성능](#최대의-성능)
- [반응형 변수 선언하기](#반응형-변수-선언하기)
- [반응형 상태를 갖는 간단한 방법](#반응형-상태를-갖는-간단한-방법)
- [변수를 화면에 적용하기](#변수를-화면에-적용하기)
- [재빌드에 조건 걸기](#재빌드에-조건-걸기)
- [.obs를 사용하는 방법](#obs를-사용하는-방법)
- [List를 사용할 때](#list를-사용할-때)
- [어째서 .value를 사용하는가](#어째서-value를-사용하는가)
- [Obx()](#obx)
- [Workers](#workers)
- [간단한 상태 관리자](#간단한-상태-관리자)
- [장점](#장점-1)
- [사용법](#사용법)
- [controller의 동작 방식](#controller의-동작-방식)
- [StatefulWidget을 더 이상 사용할 필요없습니다](#statefulwidget을-더-이상-사용할-필요없습니다)
- [이 패키지의 목표](#이-패키지의-목표)
- [다른 사용법](#다른-사용법)
- [고유 ID](#고유-id)
- [2개의 상태 관리자 섞어쓰기](#2개의-상태-관리자-섞어쓰기)
- [GetBuilder vs GetX vs Obx vs MixinBuilder](#getbuilder-vs-getx-vs-obx-vs-mixinbuilder)
# 상태 관리자
GetX는 다른 상태 관리자처럼 Streams나 ChangeNotifier를 사용하지 않습니다. 어째서일까요? GetX를 사용하면 Android, iOS, 웹, Linux, macOS, Linux 용 어플리케이션뿐만 아니라, 서버 어플리케이션도 Flutter/GetX의 문법으로 만들 수 있습니다. 반응 시간을 줄이고, RAM을 효율적으로 사용하기 위해, 우리는 GetValue와 GetStream을 만들었습니다. GetValue와 GetStream은 적은 연산 자원으로 낮은 레이턴시와 높은 퍼포먼스를 보여줍니다. 우리는 이를 토대로 상태관리를 포함해 모든 리소스를 빌드합니다.
- _복잡도_: 어떤 상태관리자들은 복잡하고 매우 비슷한 형태를 띕니다. GetX를 이용하면 모든 이벤트를 위한 클래스를 정의할 필요가 없습니다. 이는 당신의 코드를 매우 깔끔하게 만들어주며, 적을 코드들을 줄여줍니다. 많은 Flutter 개발자들이 이런 이유로 개발을 포기해왔지만, 드디어 상태관리를 해결해줄 엄청나게 간단한 방법이 나왔습니다.
- _code generators에 의존하지 않음_: 당신은 어플리케이션 개발을 위한 로직을 작성하는데 개발시간의 절반을 할애했을 것입니다. 어떤 상태관리자들은 code generator에 의존하여 읽기 쉬운 코드를 작성했을 것입니다. 변수를 바꾸고 build_runner를 실행해야 하는 것은 비생산적일 수 있으며, 심지어 Flutter가 이를 반영되기를 기다리면서 커피 한 잔을 즐겨야 할 정도로 오래 기다릴 수 있습니다. GetX는 모든것을 즉각적으로 반응합니다. code generator에 의존하지 않고, 모든 면에서 당신의 생산성을 높여줍니다.
- _필요없는 context_: 아마 당신은 비즈니스 로직을 UI에 반영하기 위해, 여러 위젯 컨트롤러에 context를 넘겨주었을 것입니다. context를 이용한 위젯을 사용하기 위해, context를 다양한 클래스와 함수들을 이용하여 전달하였을 것입니다. GetX를 이용하면 그럴 필요가 없습니다. context없이 controller만으로 접근하여 사용할 수 있습니다. 말 그대로 아무 의미 없이 context를 파라미터로 넘겨줄 필요가 없습니다.
- _세분화된 컨트롤_: 대부분의 상태관리자들은 ChangeNotifier을 기반으로 동작합니다. ChangeNotifier는 notifyListeners가 호출되면 모든 위젯들에게 알릴 것입니다. 만약 당신 스크린에 수많은 ChangeNotifier 클래스를 갖는 40개의 위젯이 있다면, 한 번 업데이트 할 때마다 모든 위젯들이 다시 빌드될 것입니다. GetX를 이용하면 위젯이 중첩되더라도, 변경된 위젯만 다시 빌드됩니다. 한 Obx가 ListView를 보고있고, 다른 Obx가 ListView 안의 checkbox를 보고있을 때, checkBox 값이 변경되면, checkBox만 업데이트 되고, ListView 값이 변경되면 ListView만 업데이트 됩니다.
- _**정말** 바뀌었을 때만 재구성_: GetX는 흐름제어를 합니다. '진탁'이라는 Text를 화면에 보여준다고 해봅시다. 만약 당신이 obserable 변수인 '진탁'을 다시 한 번 '진탁'으로 변경한다면, 그 위젯은 재구성되지 않습니다. 왜냐하면 GetX는 '진탁'이 이미 Text로 보여주고 있다는 것을 알고 있기 때문에, 불필요한 재구성을 하지 않습니다. 대부분(모두일 수도 있는) 지금까지의 대부분의 상태관리자들은 스크린에 다시 빌드하여 보여줍니다.
## 반응형 상태 관리자
반응형 프로그래밍은 복잡하다고 말해지기 때문에 많은 사람들이 접하기 힘들게 합니다. GetX는 반응형 프로그래밍을 꽤 간단한 것으로 만들어줍니다:
- StreamControllers를 생성할 필요가 없습니다.
- StreamBuilder를 각 변수마다 생성할 필요가 없습니다.
- 각 상태마다 클래스를 만들어줄 필요가 없습니다.
- 초기값을 위한 get을 만들어줄 필요가 없습니다.
GetX와 함께하는 반응형 프로그래밍은 setState를 사용하는 것만큼 쉽습니다.
이름 변수가 하나 있고, 이 변수가 변경될 때마다 해당 변수를 사용하는 모든 위젯들이 자동적으로 변경된다고 해봅시다.
여기 이름 변수가 있습니다.
```dart
var name = 'Jonatas Borges';
```
이 변수를 observable로 만들고 싶다면, 맨 뒤에 ".obs"만 붙이면 됩니다.
```dart
var name = 'Jonatas Borges'.obs;
```
이게 끝입니다. 정말 쉽죠?
지금부터, 우리는 이런 반응형-".obs"(ervables) 변수들을 _Rx_ 라고 부르겠습니다.
우리가 내부에서 무엇을 했나요? 우리는 `String``Stream`에 초기값인 `"Jonatas Borges"`를 할당했습니다. 우리는 `"Jonatas Borges"`을 사용하는 위젯들에게, 이제 이 변수에 "속한다"고 알리며 이 Rx 변수가 바뀔 때마다, 그 위젯들도 바뀌어야 한다고 알립니다.
이것이 Dart 언어의 기능에 기반한, **GetX의 마법**입니다.
하지만, 알다시피 static 클래스들은 "자동 변경"할 힘이 없기 때문에, `위젯`은 함수 안에 있는 경우에만 변경이 가능합니다.
몇몇 변수를 동일한 범위 내에서 변화시키고 싶을 때, 당신은 `StreamBuilder`를 생성하여 이 변수를 지켜보면서 변화를 감지하고, "연쇄적으로" 중첩된 `StreamBuilder`를 만들 것입니다, 맞죠?
아뇨, 더 이상 `StreamBuilder`를 만들 필요 없습니다. 하지만 static 클래스를 사용한다는 점은 맞아요.
특정한 위젯을 변경하고 싶을 때, 우리는 모양이 비슷한 코드들을 봐야 했습니다. 그건 Flutter 방식이죠. **GetX**를 이용하면 이런 비슷한 모양의 코드는 잊어버릴 수 있습니다.
`StreamBuilder( … )`? `initialValue: …`? `builder: …`? 아뇨, 당신은 그저 `Obx()` 위젯 안에 변수를 넣기만 하면 됩니다.
```dart
Obx (() => Text (controller.name));
```
_당신이 기억해야할 것은?_ `Obx(() =>`만 기억하세요.
당신은 화살표함수를 통해 그 위젯을 `Obx()`으로 전달하는 것입니다. (그 _Rx_ 의 "Observer")
`Obx`는 꽤 스마트하며 `controller.name`이 바뀔 경우에만 바뀔 것입니다.
만약 `name``"John"`이고, 당신이 이를 `"John"` (`name.value = "John"`)으로 바꾼다면, 기존 `value`와 동일하므로 화면상으로 바뀌는 것이 없습니다. 그리고 `Obx`는 리소스를 아끼기 위해 새 값을 무시하고 재빌드하지 않습니다. **놀랍지 않나요?**
> 그래서, 제가 만약 5개의 _Rx_ (observable) 변수를 `Obx`안에 가지고 있다면 어떻게 되나요?
그 변수들 중 **아무거나** 변경이 되었을 때 업데이트 됩니다.
> 그리고 제가 만약 클래스 안에 30개의 변수를 갖고 있고 하나만 업데이트 했다면, 클래스 안의 **모든** 변수가 업데이트 되나요?
아뇨, 단지 그 _Rx_ 변수를 사용하는 **특정한 위젯만** 업데이트 됩니다.
그래서 **GetX**는 _Rx_ 변수의 변경이 있을 때만 화면에 업데이트 합니다.
```
final isOpen = false.obs;
// 아무일도 일어나지 않습니다. 동일한 값이기 때문입니다.
void onButtonTap() => isOpen.value=false;
```
### 장점
**GetX()** 는 업데이트된 것들을 **세부적으로** 제어해야할 때 유용합니다.
어떤 동작을 수행할 때 모든 변수가 수정되어 `고유 ID`가 필요없을 때 `GetBuilder`를 사용하세요. `GetBuilder`는 단 몇줄의 코드로 상태를 변경시켜줍니다(`setState()`처럼). 이것은 단순하면서 CPU에 최소한의 부담을 주며, State 재빌드라는 하나의 목적을 수행하기 위해 가능한 한 최소의 리소스를 사용합니다.
**강력한** 상택 관리자가 필요하다면, **GetX**와 함께하세요.
GetX는 변수를 이용하지 않고, 내부에서 모든 것이 `Streams`로 구성된 __flow__ 를 이용합니다.
모든 것이 `Streams`이기 때문에, 접속사로써 _rxDart_ 를 이용합니다.
모든 것이 `Streams`이기 때문에, 각 "_Rx_ 변수"의 `event`를 주시할 수 있습니다.
말 그대로 _BLoC_ 의 접근 법이며, generator와 decoration 없이 _MobX_ 보다 쉽습니다.
**모든 것들을** `.obs`를 붙임으로써 _"Observable"_ 하게 만들 수 있습니다.
### 최대의 성능
최소의 재빌드를 위해 똑똑한 알고리즘을 적용하기 위해, **GetX**는 상태가 변했는지 확인하는 comparator를 사용합니다.
당신의 앱에서 에러가 발생하고 상태 변경을 중복하여 보내면, **GetX**는 충돌하지 않도록 보장해줍니다.
**GetX**를 사용하면 `value`가 변경된 경우만 상태가 변경됩니다.
이 점이 **GetX**와 _`computed`를 사용하는 MobX_ 와의 주요 차이점입니다.
2개의 __observable__ 변수를 결합하고 하나만 변경되는 경우, 그 _observable_ 를 참조하는 것 또한 변경됩니다.
**GetX**를 사용하면, 2개의 변수를 결합한 경우 (`Oberver()`와 비슷한)`GetX()`는 정말 상태가 변경된 경우만 재빌드됩니다.
### 반응형 변수 선언하기
변수를 "observable"하게 만드는 방법은 3가지가 있습니다.
1 - 첫 번째 방법: **`Rx{Type}`**.
```dart
// 초기값을 설정하는 것을 추천하지만, 필수는 아닙니다.
final name = RxString('');
final isLogged = RxBool(false);
final count = RxInt(0);
final balance = RxDouble(0.0);
final items = RxList<String>([]);
final myMap = RxMap<String, int>({});
```
2 - 두 번째 방법: **`Rx`**와 Dart의 제너릭을 이용 `Rx<Type>`
```dart
final name = Rx<String>('');
final isLogged = Rx<Bool>(false);
final count = Rx<Int>(0);
final balance = Rx<Double>(0.0);
final number = Rx<Num>(0);
final items = Rx<List<String>>([]);
final myMap = Rx<Map<String, int>>({});
// 커스텀 클래스 - 그 어떤 클래스도 가능합니다
final user = Rx<User>();
```
3 - The third, more practical, easier and preferred approach, just add **`.obs`** as a property of your `value`:
3 - 세 번째 방법: 실용적이며 쉽고 선호되는 방법으로, 단순히 **`.obs`**를 `value`의 속성으로 덧붙이는 방법
```dart
final name = ''.obs;
final isLogged = false.obs;
final count = 0.obs;
final balance = 0.0.obs;
final number = 0.obs;
final items = <String>[].obs;
final myMap = <String, int>{}.obs;
// 커스텀 클래스 - 그 어떤 클래스도 가능합니다
final user = User().obs;
```
##### 반응형 상태를 갖는 간단한 방법
알다시피 _Dart__null safety_ 가 곧 도입될 것입니다.
이를 대비하기 위해 지금부터, _Rx_ 변수를 항상 **초기값**으로 초기화해주세요.
> 변수를 **GetX** 를 이용하여 _observable_ + _초기값_ 으로 바꾸는 것은 매우 쉽고 실용적입니다.
변수의 맨 뒤에 "`.obs`" 글자를 붙이기만 하면 되고, **이게 다입니다**.
당신은 변수를 observable 하게 만들었으며 `.value`를 이용하여 _초기값_ 에 접근할 수 있습니다.
### 변수를 화면에 적용하기
```dart
// controller file
final count1 = 0.obs;
final count2 = 0.obs;
int get sum => count1.value + count2.value;
```
```dart
// view file
GetX<Controller>(
builder: (controller) {
print("count 1 rebuild");
return Text('${controller.count1.value}');
},
),
GetX<Controller>(
builder: (controller) {
print("count 2 rebuild");
return Text('${controller.count2.value}');
},
),
GetX<Controller>(
builder: (controller) {
print("sum rebuild");
return Text('${controller.sum}');
},
),
```
`count1.value++`를 하면, 다음이 출력됩니다:
- `count 1 rebuild`
- `sum rebuild`
because `count1` has a value of `1`, and `1 + 0 = 1`, changing the `sum` getter value.
왜냐하면 `count1``1`을 갖고 있고, `1 + 0 = 1`이며 `sum` getter의 값을 변경하기 때문입니다.
`count2.value++`를 하면, 다음이 출력됩니다:
- `count 2 rebuild`
- `sum rebuild`
왜냐하면 `count2.value`가 바뀌었고 `sum` 결과는 이제 `2`이기 때문입니다.
- 참고: 기본적으로, 동일한 `value`로 변경되더라도, 첫 번째 이벤트는 위젯을 재빌드합니다. 이 동작은 Boolean 변수로 인해 일어납니다.
다음과 같이 했다고 생각해봅시다:
```dart
var isLogged = false.obs;
```
그리고 `ever`에서 이벤트를 발생시키기 위해, 사용자가 "로그인" 되어있는지 확인한다고 해봅시다.
```dart
@override
onInit(){
ever(isLogged, fireRoute);
isLogged.value = await Preferences.hasToken();
}
fireRoute(logged) {
if (logged) {
Get.off(Home());
} else {
Get.off(Login());
}
}
```
만약 `hasToken``false`라면, `isLogged`에 변화는 없을 것입니다. 그러면 `ever()`는 호출되지 않을 것입니다.
이런 동작을 피하기 위해서, `.value`가 동일한 값으로 변경되더라도 _observable_ 의 첫 변경은 이벤트를 발생시킬 것입니다.
이런 동작을 원하지 않는다면, 다음으로 막을 수 있습니다:
`isLogged.firstRebuild = false;`
### 재빌드에 조건 걸기
또한, Get은 정교한 상태 관리 기능을 제공합니다. 특정 조건에서 이벤트를 조건화할 수 있습니다.(리스트에 요소를 추가하는 등)
```dart
// 첫 번째 parameter: 조건, 반드시 true 혹은 false를 return
// 두 번째 parameter: 조건이 true 일 경우 적용할 새 value
list.addIf(item < limit, item);
```
decoration 없이, code generator 없이, 복잡함 없이 :smile:
Flutter의 counter 앱을 아시나요? 당신의 Controller 클래스는 아마 다음과 같을 것입니다.
```dart
class CountController extends GetxController {
final count = 0.obs;
}
```
간단합니다:
```dart
controller.count.value++
```
어디에서 변경되든지 간에 관계없이, counter 변수를 당신의 UI 내에서 업데이트 할 수 있습니다.
### .obs를 사용하는 방법
그 어떠한 것도 obs로 바꿀 수 있습니다. 2가지 방법이 있습니다.
* 클래스 값들을 obs로 바꿀 수 있습니다.
```dart
class RxUser {
final name = "Camila".obs;
final age = 18.obs;
}
```
* 또는 클래스 전체를 observable로 만들 수 있습니다.
```dart
class User {
User({String name, int age});
var name;
var age;
}
// 인스턴스화 할때
final user = User(name: "Camila", age: 18).obs;
```
### List를 사용할 때
리스트내 요소들과 마찬가지로 리스트 또한 완벽하게 observable로 만들 수 있습니다. 이렇게 하면, 리스트에 요소를 추가하였을 때 자동적으로 그 리스트를 사용하는 위젯들을 리빌드할 수 있습니다.
리스트는 ".value"를 이용할 필요가 없습니다. 놀랍게도 dart api가 ".value" 없이도 사용할 수 있게 만들어줍니다.
불행히도 String, int와 같은 primitive type들은 확장할 수 없기때문에 ".value"가 반드시 필요합니다만, getter와 setter를 이용하면 이러한 문제는 해결됩니다.
```dart
// On the controller
final String title = 'User Info:'.obs
final list = List<User>().obs;
// on the view
Text(controller.title.value), // String은 .value가 필요합니다
ListView.builder (
itemCount: controller.list.length // 리스트는 .value가 필요없습니다.
)
```
당신이 만든 observable 클래스를 만들었을 때, 업데이트를 하는 다른 방법이 있습니다.
```dart
// model 파일에서
// 각 field들을 observable로 만드는 대신, 클래스 전체를 observable로 만들 것입니다.
class User() {
User({this.name = '', this.age = 0});
String name;
int age;
}
// controller 파일에서
final user = User().obs;
// when you need to update the user variable:
// user의 변수를 업데이트해야할 때
user.update( (user) { // 이 parameter는 업데이트 하길 원하는 인스턴스 자체입니다.
user.name = 'Jonny';
user.age = 18;
});
// user 인스턴스를 업데이트하는 또다른 방법
user(User(name: 'João', age: 35));
// on view:
Obx(()=> Text("Name ${user.value.name}: Age: ${user.value.age}"))
// .value 없이 model의 value에 접근할 수 있습니다.
user().name; // User 클래스가 아니라, user 변수임을 주의하세요 (변수는 소문자 u를 갖고 있습니다.)
```
원하지 않으면 set을 이용하지 않아도 됩니다. "assign"과 "assignAll" api를 이용할 수 있습니다.
"assign" api는 당신의 리스트를 비우고, 채우고 싶은 하나의 요소를 넣을 수 있습니다.
"assignAll" api는 존재하는 리스트를 비우고, 삽입하길 원하는 iterable 객체들을 추가할 수 있습니다.
### 어째서 .value를 사용하는가
code generator와 decoration을 이용하면 `String``int`와 같은 타입에도 '.value'를 이용하지 않아도 되었겠지만, 이 라이브러리의 목표는 외부 종속성을 피하는 것입니다. 우리는 외부 패키지를 이용하지 않고, 간단하고 가벼우며 성능좋은 필수요소들(라우트 관리, 종속성 관리, 상태 관리)을 제공하고, 쾌적한 프로그래밍 환경을 제공하고 싶었습니다.
단 3글자(get)와 콜론(;)만 pubspec에 적고 프로그래밍을 시작하세요. 모든 솔루션들이 기본적으로 제공되며, 쉽고 생산성과 성능 좋게 라우트와 상태 관리를 할 수 있습니다.
이 라이브러리는 완벽한 솔루션임에도 불구하고 단일 상태 관리 패키지보다 가볍습니다. 이 점을 꼭 아셔야 합니다.
만약 `.value`가 code generator처럼 당신을 괴롭힌다면, MobX가 훌륭한 대안으로 Get과 함께 활용할 수 있습니다. pubspec에서 단일 종속성을 원하고, 호환되지 않는 버전의 패키지들을 걱정하지 않고 프로그래밍을 시작하길 원하거나, 상태 업데이트 오류가 상태 관리자나 패키지에서 비롯되는 경우, controller에 사용가능성에 대한 걱정하기 원하지 않고 말그대로 "프로그래밍만"을 하고 싶은 경우, get은 완벽한 방안입니다.
당신이 MobX의 code generator를 이용하는데 문제가 없었거나 BloC의 boilerplate를 이용하는데 문제가 없었다면, Get을 라우트하는데 쉽게 사용할 수 있을 것이며 상태 관리자를 갖고 있다는 사실을 잊을 것입니다. Get의 SEM과 RSM은 필수적으로 탄생했습니다. 저희 회사에는 90개의 controller가 넘는 프로젝트가 있었는데, 충분히 좋은 디바이스에서도 flutter clean 이후, code generator는 작업을 끝내는데 30분 이상이 걸렸습니다. 당신의 프로젝트에 5, 10, 15개 정도의 controller들만 있다면 어떤 상태 관리자들도 충분히 좋겠지만, 엄청 큰 프로젝트를 진행하고 있다면 code generator는 문제를 일으킬 것입니다. get은 매우 훌륭한 해결책입니다.
누군가 이 프로젝트에 기여하고자 code generator나 비슷한 것을 만들고 있다면, 저는 이 readme로 링크시키겠습니다. 저한테 필요한 것들이 모든 개발자에게 필요한 것들은 아니겠지만, 지금으로써는 MobX와 같은 좋은 솔루션들이 있다고 말할 수 있습니다.
### Obx()
바인딩을 이용해 Get을 입력하는 것은 불필요합니다. 익명 함수만 받는 GetX 대신 Obx 위젯을 이용할 수 있습니다. 타입을 이용하지 않는다면, 변수를 사용하기 위한 controller 객체를 이용하거나, `Get.find<Controller>().value` 혹은 `Controller.to.value`를 이용하여 value에 접근하면 됩니다.
### Workers
Worker는 이벤트가 일어났을 때, 특정 콜백함수들을 호출하는 것을 도와줍니다.
```dart
/// 'count1'이 변경될 때마다 호출
ever(count1, (_) => print("$_ has been changed"));
/// 'count1'이 처음으로 변경될 때 호출
once(count1, (_) => print("$_ was changed once"));
/// Anti DDos - 'count1'이 변경되고 1초간 변화가 없을 때 호출
debounce(count1, (_) => print("debouce$_"), time: Duration(seconds: 1));
/// 'count1'이 변경되고 있는 동안 1초 간격으로 호출
interval(count1, (_) => print("interval $_"), time: Duration(seconds: 1));
```
(`debounce`를 제외한)모든 worker들은 `condition` parameter를 가집니다. 이 parameter는 `bool` 이거나 `bool`을 return 하는 콜백함수입니다. 이 `condition``callback` 함수가 언제 실행될지 정의합니다.
모든 worker들은 `Worker` 객체를 return하며, `dispose()`를 이용하여 worker 동작을 취소시킬 수 있습니다.
- **`ever`**
_Rx_ 변수가 바뀔 때마다 항상 호출됩니다.
- **`everAll`**
`ever`처럼, `List` _Rx_ 변수가 주어지고 변경될 때마다 호출됩니다.
- **`once`**
변수가 최초로 변경될 때(한 번만) 호출됩니다.
- **`debounce`**
'debounce'는 검색 함수를 구현하는 데에 매우 유용합니다. API 호출을 타이핑이 모두 끝났을 때만 호출 시킬 수 있습니다. 만약 사용자가 "진탁"을 타이핑한다면, 당신은 'ㅈ,ㅣ,ㄴ,ㅌ,ㅏ,ㄱ'에 해당하는 6번의 검색을 해야할 것입니다. Get을 이용하면 이런 일은 일어나지 않을 것입니다. 왜냐하면 "debounce" Worker는 타이핑이 끝났을 때에만 검색하도록 만들어주기 때문입니다.
- **`interval`**
'interval'은 debounce와 다릅니다. 사용자가 1초에 1000번의 변화를 주는 행동을 한다고 해봅시다. debounce는 마지막 변화가 있은 후, 정해진 시간(기본적으로는 800ms)이 지나면 한 번만 호출됩니다. interval은 정해진 시간동안 사용자의 행동들을 무시합니다. 사용자가 1분동안 1초에 1000번의 변화를 주는 행동을 지속한다면, debounce는 사용자가 행동을 멈춘 후 한 번만 호출됩니다. 1초로 time이 설정된 interval은 매 초마다 1번씩 총 60번 호출되며, 3초로 time이 설정된 interval은 3초마다 1번씩 총 20번 호출 될 것입니다. interval은 엄청 빠른 터치(클릭)를 이용한 어뷰징(abusing)을 막는데 사용하는 데에 사용하기를 추천합니다.(예를 들어 특정 버튼을 눌러 코인을 얻는다고 해봅시다. 1분에 300번의 터치를 한다면, 사용자는 1분에 300코인을 얻을 것입니다. 하지만 interval를 이용하여 3초를 time으로 설정하면 사용자가 300번을 터치하든, 수 천번을 터치하든지 간에 20코인밖에 얻지 못할 것입니다.) 검색과 같이 변화가 api를 통해 쿼리를 호출해야 하는 경우, debounce는 DDos 공격을 막는데 효과적입니다. debounce는 사용자가 타이핑을 멈추길 기다리고, 멈추면 호출되기 때문입니다. 위쪽의 코인 시나리오에 대입해서 생각해보면, 터치를 "멈춘" 때, 코인 1개만 얻을 수 있을 것입니다.
- 참고: Worker는 Controller 혹은 클래스를 시작할 때만 사용할 수 있습니다. 그래서 항상 onInit 내에 있거나(권장사항), 클래스 생성자, StatefulWidget의 initState 안에(권장하지는 않지만 부작용은 없습니다.) 있어야 합니다.
## 간단한 상태 관리자
Get은 ChangeNotifier를 사용하지 않고, 엄청나게 가볍고 사용하기 쉬운 상태 관리자를 제공합니다. 이 상태 관리자는 Flutter가 처음인 사람들의 요구를 충족하며 대규모 어플리케이션에서도 문제를 발생시키지 않습니다.
GetBuilder는 여러 상태 컨트롤을 정확하게 해내는 것을 목표로 합니다. 장바구니에 30개의 상품이 있고, 사용자가 하나를 삭제하기 위해 터치(클릭)하면 상품 목록이 업데이트 되어 가격과 품목 수가 줄어든다고 해봅시다. 이런 상황에서 GetBuilder는 매우 유용합니다. 왜냐하면 상태들을 그룹화하여 "연산 로직"없이 한 번에 변경하기 때문입니다. GetBuilder는 이런 상황을 고려하여 만들어졌습니다. 일시적인 상태 변화를 위해 setState를 사용하면 되므로 상태 관리자가 필요없기 때문입니다.
이렇게 하면 개별적 controller를 원하는 경우, 각 ID를 할당해주거나 GetX를 사용할 수 있습니다. 어떤 것을 선택할지 당신에게 달려있지만 이 점을 기억하세요. "개별" 위젯이 많은 경우에는 GetX의 성능이 뛰어나고, 상태 변화가 여러 번 일어나는 경우에는 GetBuilder의 성능이 뛰어납니다.
### 장점
1. 필요한 위젯만 업데이트 해줍니다.
2. ChangeNotifier를 사용하지 않고, 적은 메모리(거의 0mb)를 이용하여 상태 관리를 해줍니다.
3. StatfulWidget을 이용 안 해도 됩니다! Get을 이용하면 더 이상 StatefulWidget이 필요 없습니다. 다른 상태 관리자들을 사용하면, Provider, BLoC, MobX Controller 등의 객체를 갖기 위해 StatefulWidget을 사용해야 합니다. appBar, Scaffold, 그리고 대부분의 위젯들이 StatelessWidget로 구성된 것을 생각해본 적이 있나요? Get은 이 부분도 해결해줍니다. 모든 것들을 Stateless로 만드세요. 하나의 위젯만 업데이트할 필요가 있다면, GetBuilder로 감싸면 해당 상태를 가질 수 있습니다.
4. 당신의 프로젝트를 잘 정돈할 수 있습니다! Controller들은 UI내에 두지 않고, TextEditController나 다른 controller 들을 당신의 Controller 클래스 내에 두세요!
5. 렌더링 된 직후, 위젯을 업데이트하기 위해 이벤트를 발생시킬 필요가 있나요? GetBuilder는 StatefulWidget처럼 "initState"를 갖고 있습니다. 그리고 controller로부터 직접적으로 이벤트를 호출하고, 더 이상 이벤트를 initState내에 배치할 필요가 없습니다.
6. timers 등과 같은 streams를 닫는 행동을 트리거해야 하나요? GetBuilder는 dispose 또한 갖고 있어서, 위젯이 없어지자마자 dispose를 호출할 수 있습니다.
7. streams는 정말 필요할 때만 사용하세요. StreamController와 StreamBuilder는 controller 안에 정상적으로 사용할 수 있습니다만! 기억하세요. stream은 메모리를 적당히 사용하지만, 남용해서는 안됩니다. 30개의 stream이 동시에 열려있다면 ChangeNotifier보다 안좋습니다(ChangeNotifier는 매우 나쁩니다).
8. RAM의 소모 없이 위젯을 업데이트 하세요. Get은 GetBuilder의 creator ID만 저장하고 필요한 경우에만 해당 GetBuilder만 업데이트 합니다. 수천개의 GetBuilder가 있어도 get은 ID를 저장하는데 매우 적은 메모리를 사용합니다. 새 GetBuilder를 생성한다면, creator ID를 갖고 있는 GetBuilder의 상태를 공유합니다. 각 GetBuilder의 새 상태는 생성 되지 않기 때문에 큰 규모의 어플리케이션에서도 **많은** RAM 자원을 절약합니다. 기본적으로 당신의 어플리케이션은 전반적으로 Stateless 이고, 극히 일부의 위젯만 단일 상태를 가진 (GetBuilder를 포함한)Stateful일 것이기 때문에, 하나의 업데이트만으로 그것들을 전부 업데이트 합니다. 상태는 단 하나뿐입니다.
9. Get은 전지적으로 대부분의 경우 어느 타이밍에 메모리에서 제거해야할 지 알 고 있습니다. 당신은 언제 컨트롤러를 dispose해야하는지만 걱정하세요.
### 사용법
```dart
// GetxController를 상속(extends)하는 controller 클래스를 만드세요
class Controller extends GetxController {
int counter = 0;
void increment() {
counter++;
update(); // increment()가 호출되었을 때, counter 변수가 변경되어 UI에 반영되어야 한다는 것을 update()로 알려주세요
}
}
// 당신의 Stateless/Stateful 클래스에서, increment()가 호출되었을 때 GetBuilder를 이용해 Text를 업데이트 하세요
GetBuilder<Controller>(
init: Controller(), // 맨 처음만! 초기화(init)해주세요
builder: (_) => Text(
'${_.counter}',
),
)
// controller는 처음만 초기화하면 됩니다. 같은 controller로 GetBuilder를 또 사용하려는 경우에는 init을 하지 마세요.
// 중복으로 'init'이 있는 위젯이 배치되자마자, controller는 자동적으로 메모리에서 제거될 것입니다.
// 걱정하실 필요 없이, Get은 자동적으로 controller를 찾아서 해줄겁니다. 그냥 2번 init하지 않는 것만 하시면 됩니다.
```
**끝입니다!**
- 당신은 이미 Get을 이용하여 상태관리를 어떻게 하는지 다 배우셨어요!
- 참고: 큰 규모의 프로젝트를 진행하면서 init 속성을 사용하지 않을 수도 있습니다. 그럴 때에는, Binding 클래스를 상속(extends)한 클래스를 만들고, 그 클래스 내에서 해당 라우트에 생성되어야 하는 controller를 선언하세요. controller가 즉각적으로 만들어지지는 않지만, controller가 처음 사용될 때 Get이 알아서 잘 만들어줄 것입니다. Get은 lazyLoad를 지원하며, controller가 더 이상 필요하지 않을 때 dispose를 해줍니다. 사용예제는 pub.dev에서 확인하세요.
수많은 라우트를 진행하면서 예전에 사용하였던 controller의 데이터가 필요하면, GetBuilder를 다시 사용하시면 됩니다 (init 없이):
```dart
class OtherClass extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: GetBuilder<Controller>(
builder: (s) => Text('${s.counter}'),
),
),
);
}
```
GetBuilder 밖의 여러 곳에서 controller를 사용해야 하는 경우, 간단하게 Controller 클래스 안의 getter로 접근할 수 있습니다. (아니면 `Get.find<Controller>()`를 사용하세요)
```dart
class Controller extends GetxController {
/// You do not need that. I recommend using it just for ease of syntax.
/// with static method: Controller.to.increment();
/// with no static method: Get.find<Controller>().increment();
/// There is no difference in performance, nor any side effect of using either syntax. Only one does not need the type, and the other the IDE will autocomplete it.
static Controller get to => Get.find(); // add this line
int counter = 0;
void increment() {
counter++;
update();
}
}
```
그리고 아래와 같은 방법으로, controller에 바로 접근하세요:
```dart
FloatingActionButton(
onPressed: () {
Controller.to.increment(),
} // This is incredibly simple!
child: Text("${Controller.to.counter}"),
),
```
FloatingActionButton을 눌렀을 때, counter 변수를 주시(listen)하고 있는 위젯은 자동적으로 업데이트됩니다.
### controller의 동작 방식
아래와 같은 상황을 가정해보겠습니다:
`Class A => Class B (has controller X) => Class C (has controller X)`
A 클래스에서 controller를 아직 사용하지 않았기 때문에, controller는 메모리에 없습니다(Get은 lazyLoad를 지원합니다). B 클래스에서는 controller를 사용하기 때문에 메모리에 로드됩니다. C 클래스에서는 B 클래스에서 사용한 controller와 같은 controller를 사용하기 때문에, Get은 B의 controller와 C의 controller의 상태를 공유하며, 그 동일한 controller는 여전히 메모리에 있습니다. 그리고 C 화면과 B 화면을 닫으면, Get은 자동적으로 controller X (B와 C에서 쓰인 controller)를 메모리에서 해제해줄 것입니다. 왜냐하면 A 클래스에서는 controller X를 사용하지 않기 때문이죠. 만약 B 화면으로 라우팅한다면 controller X는 다시 메모리에 로드됩니다. 그리고 C 화면으로 라우팅되는 대신, A 화면으로 되돌아간다면 같은 방식으로 controller X는 메모리에서 해제됩니다. 만약 클래스 C에서 controller를 사용하지 않고, 클래스 B가 메모리에서 해제된다면, controller를 사용하는 클래스가 없기 때문에 같은 방식으로 controller는 메모리에서 해제됩니다. Get이 에러날 수 있는 유일한 예외 상황은 B가 예기치않게 라우트 상에서 제거되고, C에서 controller를 사용하려고 하는 경우입니다. 이 경우, B에 있던 controller의 creator ID가 제거되는데, Get은 creator ID를 갖고 있지 않은 controller는 메모리에서 제거하도록 프로그래밍되어 있습니다. 이런 일을 원하지 않으신다면, "authoRemove: false" 플래그를 B 클래스의 GetBuilder에 추가하고, "assignId: true"를 C 클래스의 GetBuilder에 추가해주세요.
### StatefulWidget을 더 이상 사용할 필요없습니다
SatefullWidget을 사용한다는 것은 위젯을 최소한으로 재빌드해야하는 경우에도, 위젯을 Consumer / Oberver / BlocProvider / GetBuilder / GetX / Obx 안에 넣어줄 것이기 때문에, 또다른 StatefulWidget을 사용하는 것과 마찬가지이므로 화면 전체의 상태를 불필요하게 저장합니다. StatefulWidget 클래스는 StatelessWidget 클래스보다 더 많은 RAM 할당이 필요한 큰 규모의 클래스입니다. 1개나 2개 정도의 클래스라면 별 차이가 없겠지만, 100개 이상부터는 차이가 있을 것입니다! TickerProviderStateMixin과 같은 mixin이 필요없는 경우, Get을 사용하면 StatefulWidget은 필요 없습니다.
StatefulWidget에서 메소드를 직접적으로 호출하는 것처럼, Getbuilder를 통해 메소드를 호출할 수 있습니다.
initState()나 dispose()를 호출할 필요가 있을 때에도, 직접적으로 호출할 수 있습니다.
```dart
GetBuilder<Controller>(
initState: (_) => Controller.to.fetchApi(),
dispose: (_) => Controller.to.closeStreams(),
builder: (s) => Text('${s.username}'),
),
```
위 방법보다 더 좋은 방법은 Controller에서 onInit()과 onClose()를 이용하는 것입니다.
```dart
@override
void onInit() {
fetchApi();
super.onInit();
}
```
- 참고: controller가 처음 불려졌을 때 어떤 메소드가 호출되길 원하는 경우, (좋은 성능을 목표로하는 Get 패키지를 이용하면)이를 위해서 생성자를 **사용할 필요가 없습니다**. 생성자를 사용한다는 것은 controller가 생성되거나 할당되었을 때의 로직에서 벗어나는 일이기 때문에 좋지 않습니다. (controller 객체를 생성하려하면 생성자는 즉시 호출되며, controller를 사용하기 이전부터 메모리에 로드됩니다. 이러한 동작은 이 라이브러리의 성능을 저해합니다.) onInit()과 onClose()는 이를 위해 만들어졌습니다. Get.lazyPut하는지 여부에 따라, controller가 생성되거나 처음 사용될 때 onInit()과 onClose()가 호출됩니다. API를 호출하기 위한 데이터를 초기화 등을 위해 구식 방식의 initState/dispose를 사용하는 대신 onInit()을 사용하고, stream을 닫는 등의 동작이 필요하면 onClose()를 사용하세요.
### 이 패키지의 목표
이 패키지의 목표는 당신에게 최소의 종속성(pubspec의 dependencies)으로 라우트/상태/종속성 관리를 위한 완전한 솔루션을 제공하는 것입니다. Get은 어떤 종속성에서도, 어떤 버전의 Flutter API를 사용하더라도 동작하도록 보장해줍니다. 어떤 종속성에서도 당신의 프로젝트가 동작하도록 단일 패키지로 모든 것을 집약하였습니다. 이런 방법으로 당신은 화면상 위젯들만 신경쓰고, 팀원 중 일부는 비즈니스 로직에만 신경쓸 수 있도록 하였습니다. 이 점은 당신에게 더 나은 작업환경을 제공합니다. 팀원 중 일부는 controller에 보내지는 데이터에 신경 쓰지 않고 위젯에만 집중하고, 다른 팀원들은 위젯 배치에는 신경쓰지 않고 비즈니스 로직에만 집중하여 작업할 수 있습니다.
간단히 말하면: 여러 메소드들을 initState에서 호출할 필요도 없고, 메소드들을 parameter로 controller에 넘겨줄 필요 없고, controller 생성자를 호출할 필요도 없습니다. 여러분의 서비스가 시작하고 호출되는 onInit()를 이용하면 됩니다. 그리고 controller가 더 이상 필요 없을 때 메모리에서 제거 될 때 호출되는 onClose()를 이용하세요. 이 방법으로 화면 구성은 위젯 배치만 신경써도 되게 해줍니다.
GetxController 안에서 dispose를 호출하지 마세요. 아무동작도 하지 않을 뿐더러 controller는 위젯이 아니기 때문에 "dispose"할 수 없다는 점을 기억하세요. Get에 의해 자동적으로 똑똑하게 메모리에서 해제 될 것입니다. 만약 stream들을 닫고 싶다면 onClose() 메소드 안에서 닫아주세요. 예를 들어:
```dart
class Controller extends GetxController {
StreamController<User> user = StreamController<User>();
StreamController<String> name = StreamController<String>();
/// dispose가 아니라 onClose()에서 stream을 닫으세요
@override
void onClose() {
user.close();
name.close();
super.onClose();
}
}
```
Controller 생명 주기:
- onInit()은 생성되었을 때 호출
- onClose()는 delete 메소드를 준비하기 위해 닫히는 경우
- deleted: controller가 메모리에서 해제되어 더 이상 API에 접근할 수 없을 때. 말 그대로 삭제되어 추적할 수 없습니다.
### 다른 사용법
Controller 객체를 GetBuilder 안에서 value로 직접 접근할 수 있습니다:
```dart
GetBuilder<Controller>(
init: Controller(),
builder: (value) => Text(
'${value.counter}', // 여기!
),
),
```
GetBuilder 바깥에서도 controller 객체가 필요하면, 다음과 같이 접근할 수 있습니다:
```dart
class Controller extends GetxController {
static Controller get to => Get.find();
[...]
}
// 화면상
GetBuilder<Controller>(
init: Controller(), // 각 controller를 처음 사용할 때 init 하세요
builder: (_) => Text(
'${Controller.to.counter}', // 여기!
)
),
```
아니면
```dart
class Controller extends GetxController {
// static Controller get to => Get.find(); // static get 없이
[...]
}
// stateless/stateful 클래스에서
GetBuilder<Controller>(
init: Controller(), // 각 controller를 처음 사용할 때 init 하세요
builder: (_) => Text(
'${Get.find<Controller>().counter}', // 여기!
),
),
```
- "비표준"적인 방식의 접근도 다음과 같이 가능합니다. get_it, modular 등과 같은 다른 종속성 관리자를 사용한다면, 아래와 같이 controller 객체를 전달해줄 수 있습니다:
```dart
Controller controller = Controller();
[...]
GetBuilder<Controller>(
init: controller, // 여기!
builder: (_) => Text(
'${controller.counter}', // 여기!
),
),
```
### 고유 ID
GetBuilder로 위젯의 업데이트를 좀 더 세분화하여 다루고 싶다면, 고유 ID를 부여하세요:
```dart
GetBuilder<Controller>(
id: 'text'
init: Controller(), // 각 controller를 처음 사용할 때 init 하세요
builder: (_) => Text(
'${Get.find<Controller>().counter}', // 여기!
),
),
```
그리고 다음과 같이 update 하세요:
```dart
update(['text']);
```
또, update하는데 조건도 줄 수 있습니다:
```dart
update(['text'], counter < 10);
```
GetX는 위젯이 정말 값이 변경되었을 때에만 재빌드합니다. 만약 변수의 값이 전과 같은 값으로 변경되었다면, GetX는 메모리와 CPU 자원을 아끼기 위해 재빌드하지 않습니다.(화면에 3이라는 숫자가 보여지고 있고, 그 숫자가 다시 3으로 변경된 경우를 가정하겠습니다. 대부분의 상태 관리자들은 이러한 경우에 재빌드를 합니다만, GetX를 사용하면 정말 값이 변경되었을 때에만 재빌드를 합니다.)
## 2개의 상태 관리자 섞어쓰기
단 하나의 반응변수(.obs)와 다른 메커니즘(update())가 모두 필요해서 Getbuilder 안에 Obx를 넣어야 하는 경우가 필요했습니다. 이 경우를 위해 MixinBuilder가 만들어졌습니다. ".obs"변수의 값이 바뀔 때 즉각적으로 바뀌는 것과 update()를 통해 바뀌는 것 모두를 지원합니다. 하지만, 이 위젯이 GetBuilder, GetX, Obx 보다 더 많은 리소스를 필요로합니다. 왜냐하면 controller의 update() 메소드와 자식으로부터의 .obs 변수의 변화를 주시하고 있어야 하기 때문입니다.
GetxController를 상속(extends)하는 것은 중요합니다. onInit()과 onClose()에서 "시작"과 "종료" 이벤트를 수행할 수 있는 생명 주기를 갖고 있기 때문입니다. 이를 위해서 어떤 클래스를 사용해도 괜찮지만, obervable한 변수든 아니든 간에 GetXController를 사용해 변수를 다루길 적극 권장합니다.
## GetBuilder vs GetX vs Obx vs MixinBuilder
10년간 프로그래밍을 하며 일해오면서, 귀중한 교훈을 얻을 수 있었습니다.
반응형 프로그래밍을 처음 접했을 때 "와, 진짜 굉장한데!"라고 느꼈고, 실제로도 반응형 프로그래밍은 정말 놀랍습니다. 하지만, 모든 상황에서 올바르지는 않습니다. 많은 경우 2개내지 3개의 위젯의 상태를 동시에 변경하거나 일시적으로 변경해야하는데, 반응형 프로그래밍은 나쁘지 않지만 적적하지 않습니다.
반응형 프로그래밍은 각각의 워크플로우를 위한 RAM 자원을 많이 필요로 합니다. 하나의 위젯만이 재빌드 되어야 하는 경우는 괜찮지만, 리스트에 80개 가량의 요소가 있고, 각각 stream이 있을 경우에는 좋지 않습니다. dart inspect 창을 열고 StreamBuilder가 얼마나 많은 리소스를 사용하는지 보신다면, 제 말이 이해가 가실겁니다.
이런 생각에, 저는 간단한 상태 관리자를 만들었습니다. 간단합니다. 그리고 이것이 여러분이 정말 요구하는 것이에요: 블록 단위로 매우 경제적이고 간단하게 상태를 업데이트합니다.
`GetBuilder`는 RAM의 측면에서 가장 경제적입니다. 이보다 더 경제적인 방법은 없을 겁니다(만약 그런 방법을 고안하신다면, 꼭 저희에게 알려주세요!)
하지만 `GetBuilder`는 수동적인 상태 관리자입니다. Provider의 notifyListeners()를 호출하는 것처럼 update()를 호출해야만 합니다.
반응형 프로그래밍이 정말 도움이 되는 상황들이 많습니다. 이를 활용하지 않는건 바퀴를 다시 만드는 것과 마찬가지입니다. 이런 생각에, `GetX`는 상태 관리자로 가장 현대적이고 높은 수준의 기능들을 제공하기 위해 만들어졌습니다. 이것은 필요한 것들을 필요한 때에 업데이트합니다. 만약 에러가 있고 300개의 상태가 동시에 변경되면, `GetX`는 화면상에 반영되어야할 것들만 필터링하여 업데이트 합니다.
`GetX`는 다른 반응형 상태 관리자들보다 경제적이지만, `GetBuiler`보다 조금 더 RAM을 소모합니다. 능동적이면서 RAM의 최대한 효율적으로 사용하기 위해 `Obx`가 만들어졌습니다. `GetX``GetBuilder`와 다르게 `Obx`안에서 controller를 초기화할 수 없습니다. 단지 자식으로부터 변화를 감지하는 StreamSubscription을 갖고 있는 위젯일 뿐입니다. `Obx``GetX`보다는 경제적이지만 `GetBuilder`보다는 덜 경제적입니다. 왜냐하면 `Obx`는 반응적이고, `GetBuilder`는 위젯의 해시코드와 StateSetter만 저장하는 최적의 접근 방식을 갖고 있기 때문입니다. `Obx`를 이용하면 controller의 타입을 적어줄 필요가 없고, 여러 개의 다른 controller 변화를 감지합니다. 하지만 사용 전에 초기화거나, readme에 있는 예제처럼 사용하거나, Binding 클래스를 사용해야 합니다.

View File

@@ -0,0 +1,382 @@
# Gerenciamento de dependência
- [Gerenciamento de dependência](#gerenciamento-de-dependência)
- [Gerenciamento de dependências simples](#gerenciamento-de-dependências-simples)
- [Métodos de criar instâncias](#métodos-de-criar-instâncias)
- [Get.put()](#getput)
- [Get.lazyPut](#getlazyput)
- [Get.putAsync](#getputasync)
- [Get.create](#getcreate)
- [Usando as classes/dependências instanciadas](#usando-as-classesdependências-instanciadas)
- [Diferenças entre os métodos](#diferenças-entre-os-métodos)
- [Bindings](#bindings)
- [Classe Bindings](#classe-bindings)
- [BindingsBuilder](#bindingsbuilder)
- [SmartManagement](#smartmanagement)
- [Como alterar](#como-alterar)
- [SmartManagement.full](#smartmanagementfull)
- [SmartManagement.onlyBuilders](#smartmanagementonlybuilders)
- [SmartManagement.keepFactory](#smartmanagementkeepfactory)
- [Como os Bindings funcionam](#como-os-bindings-funcionam)
- [Notas](#notas)
Get tem um gerenciador de dependência simples e poderoso que permite você pegar a mesma classe que seu Bloc ou Controller com apenas uma linha de código, sem Provider context, sem inheritedWidget:
```dart
Controller controller = Get.put(Controller()); // Em vez de Controller controller = Controller();
```
Em vez de instanciar sua classe dentro da classe que você está usando, você está instanciando ele dentro da instância do Get, que vai fazer ele ficar disponível por todo o App
Para que então você possa usar seu controller (ou uma classe Bloc) normalmente
- Nota: Se você está usando o gerenciado de estado do Get, você não precisa se preocupar com isso, só leia a documentação, mas dê uma atenção a api [Bindings](#bindings), que vai fazer tudo isso automaticamente para você.
- Nota²: O gerenciamento de dependência do get é desacoplado de outras partes do package, então se por exemplo seu aplicativo já está usando um outro gerenciador de estado (qualquer um, não importa), você não precisa de reescrever tudo, pode simplesmente usar só a injeção de dependência sem problemas
## Métodos de criar instâncias
Todos os métodos e seus parâmetros configuráveis são:
### Get.put()
A forma mais comum de instanciar uma dependência. Bom para os controllers das views por exemplo.
```dart
Get.put<Classe>(Classe());
Get.put<LoginController>(LoginController(), permanent: true);
Get.put<ListItemController>(
ListItemController,
tag: "uma string única",
);
```
E essas são todas as opções que você pode definir:
```dart
Get.put<S>(
// obrigatório: a classe que você quer salvar, como um controller ou qualquer outra coisa
// obs: Esse "S" significa que pode ser qualquer coisa
S dependency
// opcional: isso é pra quando você quer múltiplas classess que são do mesmo tipo
// já que você normalmente pega usando "Get.find<Controller>()",
// você precisa usar uma tag para dizer qual das instâncias vc precisa
// precisa ser uma string única
String tag,
// opcional: por padrão, get vai descartar as instâncias quando elas não são mais usadas (exemplo,
// o controller de uma view que foi fechada) // Mas talvez você precisa quea instância seja mantida por todo o app, como a instância do SharedPreferences por exemplo
// então vc usa isso
// padrão: false
bool permanent = false,
// opcional: permite criar a dependência usando uma função em vez da dependênia em si
InstanceBuilderCallback<S> builder,
)
```
### Get.lazyPut
É possível que você vá inserir essa instância, mas sabe que não vai usá-la imediatamente no app.
Nesses casos pode ser usado o lazyPut que só cria a instância no momento que ela for necessária pela primeira vez.
É útil também caso seja uma classe que é muito pesada e você não quer carregar ela junto com tudo quando o app abre.
```dart
/// ApiMock só será instanciado quando Get.find<ApiMock> for usado pela primeira vez
Get.lazyPut<ApiMock>(() => ApiMock());
Get.lazyPut<FirebaseAuth>(
() {
// ... alguma lógica se necessário
return FirebaseAuth();
},
tag: Math.random().toString(),
fenix: true
)
Get.lazyPut<Controller>( () => Controller() )
```
E essas são todas as opções que você pode definir:
```dart
Get.lazyPut<S>(
// obrigatório: um método que vai ser executado quando sua classe é chamada pela primeira vez
InstanceBuilderCallback builder,
// opcional: igual ao Get.put(), é usado quando você precisa de múltiplas instâncias de uma mesma classe
// precisa ser uma string única
String tag,
// opcional: é similar a "permanent", mas a instância é descartada quando
// não é mais usada e é refeita quando precisa ser usada novamente
// Assim como a opção SmartManagement.keepFactory na api Bindings
// padrão: false
bool fenix = false
)
```
### Get.putAsync
Se você quiser criar uma instância assíncrona, você pode usar `Get.putAsync`:
```dart
Get.putAsync<SharedPreferences>(() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setInt('counter', 12345);
return prefs;
});
Get.putAsync<SuaClasseAssincrona>( () async => await SuaClasseAssincrona() )
```
E essas são todas as opções que você pode definir:
```dart
Get.putAsync<S>(
// Obrigatório: um método assíncrono que vai ser executado para instanciar sua classe
AsyncInstanceBuilderCallback<S> builder,
// opcional: igual ao Get.put(), é usado quando você precisa de múltiplas instâncias de uma mesma classe
// precisa ser uma string única
String tag,
// opcional: igual ao Get.put(), usado quando você precisa manter a instância ativa no app inteiro.
// padrão: false
bool permanent = false
```
### Get.create
Esse é mais específico. Uma explicação detalhada do que esse método é e as diferenças dele para os outros podem ser encontradas em [Diferenças entre os métodos](#diferenças-entre-os-métodos)
```dart
Get.Create<SomeClass>(() => SomeClass());
Get.Create<LoginController>(() => LoginController());
```
E essas são todas as opções que você pode definir:
```dart
Get.create<S>(
// Obrigatório: Uma função que retorna uma classe que será "fabricada" toda vez que Get.find() for chamado
InstanceBuilderCallback<S> builder,
// opcional: igual ao Get.put(), mas é usado quando você precisa de múltiplas instâncias de uma mesma classe.
// Útil caso você tenha uma lista em que cada item precise de um controller próprio
// precisa ser uma string única. Apenas mudou o nome de tag para name.
String name,
// opcional: igual ao Get.put(), usado quando você precisa manter a instância ativa no app inteiro. A diferença
// é que com Get.create o permanent está habilitado por padrão
bool permanent = true
```
## Usando as classes/dependências instanciadas
Agora, imagine que você navegou por inúmeras rotas e precisa de dados que foram deixados para trás em seu controlador. Você precisaria de um gerenciador de estado combinado com o Provider ou Get_it, correto? Não com Get. Você só precisa pedir ao Get para "procurar" pelo seu controlador, você não precisa de nenhuma dependência adicional para isso:
```dart
final controller = Get.find<Controller>();
// OU
Controller controller = Get.find();
// Sim, parece Magia, o Get irá descobrir qual é seu controller, e irá te entregar.
// Você pode ter 1 milhão de controllers instanciados, o Get sempre te entregará o controller correto.
// Apenas se lembre de Tipar seu controller, final controller = Get.find(); por exemplo, não irá funcionar.
```
E então você será capaz de recuperar os dados do seu controller que foram obtidos anteriormente:
```dart
Text(controller.textFromApi);
```
Já que o valor retornado é uma classe normal, você pode fazer o que quiser com ela:
```dart
int valor = Get.find<SharedPreferences>().getInt('contador');
print(valor); // Imprime: 123456
```
Para remover a instância do Get:
```dart
Get.delete<Controller>();
```
## Diferenças entre os métodos
Primeiro, vamos falar do `fenix` do Get.lazyPut e o `permanent` dos outros métodos.
A diferença fundamental entre `permanent` e `fenix` está em como você quer armazenar as suas instâncias.
Reforçando: por padrão, o Get apaga as instâncias quando elas não estão em uso.
Isso significa que: Se a tela 1 tem o controller 1 e a tela 2 tem o controller 2 e você remove a primeira rota da stack (usando `Get.off()` ou `Get.offNamed`), o controller 1 perdeu seu uso portanto será apagado.
Mas se você optar por usar `permanent: true`, então ela não se perde nessa transição - o que é muito útil para serviços que você quer manter rodando na aplicação inteira.
Já o `fenix`, é para serviços que você não se preocupa em perder por uma tela ou outra, mas quando você precisar chamar o serviço, você espera que ele "retorne das cinzas" (`fenix: true`), criando uma nova instância.
Prosseguindo com as diferenças entre os métodos:
- Get.put e Get.putAsync seguem a mesma ordem de criação, com a diferença que o Async opta por aplicar um método assíncrono: Esses dois métodos criam e já inicializam a instância. Esta é inserida diretamente na memória, através do método interno `insert` com os parâmetros `permanent: false` e `isSingleton: true` (esse parâmetro `isSingleton` serve apenas para dizer se é para utilizar a dependência colocada em `dependency`, ou se é para usar a dependência colocada no `InstanceBuilderCallback`). Depois disso, é chamado o `Get.find` que imediatamente inicializa as instâncias que estão na memória.
- Get.create: Como o nome indica, você vai "criar" a sua dependência! Similar ao `Get.put`, ela também chama o método interno `insert` para instanciamento. Contudo, `permanent` e `isSingleton` passam a ser `true` e `false` (Como estamos "criando" a nossa dependência, não tem como ela ser um Singleton de algo, logo, `false`). E por ser `permanent: true`, temos por padrão o benefício de não se perder entre telas! Além disso, não é chamado o `Get.find`, logo ela fica esperando ser chamada para ser usada. Ele é criado dessa forma para aproveitar o uso do parâmetro `permanent`, já que, vale ressaltar, o Get.create foi criado com o objetivo de criar instâncias não compartilhadas, mas que não se perdem, como por exemplo um botão em um listView, que você quer uma instância única para aquela lista - por conta disso, o Get.create deve ser usado em conjunto com o GetWidget.
- Get.lazyPut: Como o nome dá a entender, é um processo preguiçoso (lazy). A instância é criada, mas ela não é chamada para uso logo em seguida, ela fica aguardando ser chamada. Diferente dos outros métodos, o `insert` não é chamado. Ao invés disso, a instância é inserida em outra parte na memória, uma parte responsável por dizer se a instância pode ser recriada ou não, vamos chamá-la de "fábrica". Se queremos criar algo para ser chamado só depois, não vamos misturá-lo com as coisas que estão sendo usadas agora. E é aqui que entra a mágica do `fenix`. Se você optou por deixar `fenix: false`, e seu `smartManagement` não for `keepFactory`, então ao usar o `Get.find` a instância passa da "fábrica" para a área comum das instância. Em seguinda, por padrão é removida da "fábrica". Agora, se você optou por `fenix: true`, a instância continua a existir nessa parte dedicada, mesmo indo para a área comum, para ser chamada futuramente caso precise.
## Bindings
Um dos grandes diferenciais desse package, talvez, seja a possibilidade de integração total com rotas, gerenciador de estado e gerenciador de dependências.
Quando uma rota é removida da stack, todos os controllers, variáveis e instâncias de objetos relacionados com ela são removidos da memória. Se você está usando streams ou timer, eles serão fechados automaticamente, e você não precisa se preocupar com nada disso.
Na versão 2.10 Get implementou completamente a API Bindings.
Agora você não precisa mais usar o método `init`. Você não precisa nem tipar seus controllers se não quiser. Você pode começar seus controllers e services num lugar apropriado para isso.
A classe Binding é uma classe que vai desacoplar a injeção de dependência, enquanto liga as rotas ao gerenciador de estados e o gerenciador de dependências.
Isso permite Get saber qual tela está sendo mostrada quando um controller particular é usado e saber onde e como descartar o mesmo.
Somando a isso, a classe Binding vai permitir que você tenha um controle de configuração SmartManager. Você pode configurar as dependências que serão organizadas quando for remover a rota da stack, ou quando o widget que usa ele é definido, ou nada disso. Você vai ter gerenciador de dependências inteligente trabalhando para você, e você pode configurá-lo como quiser.
### Classe Bindings
Crie uma classe qualquer que implemente a Bindings.
```dart
class HomeBinding implements Bindings {}
```
Sua IDE vai automaticamente te perguntar para dar override no método `dependencies()`, aí você clica na lâmpada, clica em "override the method", e insira todas as classes que você vai usar nessa rota:
```dart
class HomeBinding implements Bindings{
@override
void dependencies() {
Get.lazyPut<HomeController>(() => HomeController());
Get.lazyPut<Service>(()=> Api());
}
}
class DetalhesBinding implements Bindings {
@override
void dependencies() {
Get.lazyPut<DetalhesController>(() => DetalhesController());
}
}
```
Agora você só precisa informar sua rota que você vai usar esse binding para fazer a conexão entre os gerenciadores de rotas, dependências e estados.
Usando rotas nomeadas
```dart
getPages: [
GetPage(
name: '/',
page: () => HomeView(),
binding: HomeBinding(),
),
GetPage(
name: '/detalhes',
page: () => DetalhesView(),
binding: DetalhesBinding(),
),
];
```
Usando rotas normais:
```dart
Get.to(Home(), binding: HomeBinding());
Get.to(DetalhesView(), binding: DetalhesBinding())
```
Então, você não vai precisar se preocupar com gerenciamento da memória da sua aplicação mais, Get vai fazer para você.
A classe Bindings é chamada quando uma rota é chamada. Você pode criar uma Binding inicial no seu GetMaterialApp para inserir todas as dependências que serão criadas.
```dart
GetMaterialApp(
initialBinding: SampleBind(),
home: Home(),
)
```
### BindingsBuilder
A forma padrão de criar um binding é criando uma classe que implementa o Bindings.
Mas alternativamente, você também pode usar a função `BindingsBuilder` par que você possa simplesmente usar uma função pra criar essas instâncias
Exemplo:
```dart
getPages: [
GetPage(
name: '/',
page: () => HomeView(),
binding: BindingsBuilder(() {
Get.lazyPut<ControllerX>(() => ControllerX());
Get.put<Service>(()=> Api());
}),
),
GetPage(
name: '/detalhes',
page: () => DetalhesView(),
binding: BindingsBuilder(() {
Get.lazyPut<DetalhesController>(() => DetalhesController());
}),
),
];
```
Dessa forma você pode evitar criar uma classe Binding para cada rota, deixando tudo mais simples.
As duas formas funcionam perfeitamente e você é livre para usar o que mais se encaixa no seu estilo de uso
### SmartManagement
GetX por padrão descarta controllers não utilizados da memória, mesmo que uma falha ocorra e um widget que usa ele não for propriamente descartado.
Essa é o chamado modo `full` do gerenciamento de dependências.
Mas se você quiser mudar a forma que o GetX controla o descarte das classes, você tem a sua disposição a classe `SmartManagement` que pode definir diferentes comportamentos.
#### Como alterar
Se você quiser alterar essa configuração (que normalmente você não precisa mudar) essa é a forma:
```dart
void main () {
runApp(
GetMaterialApp(
smartManagement: SmartManagement.onlyBuilders //Aqui
home: Home(),
)
)
}
```
#### SmartManagement.full
É o padrão. Descarta as classes que não estão mais sendo utilizadas e que não foram definidas para serem permanents. Na grande maioria dos casos você não vai querer nem precisar alterar essa configuração. Se você for novo com GetX, não altere isto.
#### SmartManagement.onlyBuilders
Com essa opção, somente controllers iniciados pelo `init:` or iniciados dentro de um Binding com `Get.lazyPut()` serão descartados.
Se você usar `Get.put()` ou `Get.putAsync()` or qualquer outra forma, SmartManagement não vai ter permissão para excluir essa dependência.
Com o comportamento padrão, até widgets instanciados com `Get.put()` serão removidos, ao contrário do `SmartManagement.onlyBuilders`.
#### SmartManagement.keepFactory
Assim como o modo `full`, ele vai descartar as dependências quando não estiverem sendo mais utilizadas. Porém, ele irá manter a "factory" de cada dependência. Isso significa que caso você precise de da dependência novamente, ele vai recriar aquele instância novamente.
#### Como os Bindings funcionam
Bindings cria fábricas transitórias, que são criadas no momento que você clica para ir para outra tela, e será destruído assim que a animação de mudança de tela acontecer.
É tão pouco tempo, tão rápido, que o analyzer sequer conseguirá registrá-lo.
Quando você navegar para essa tela novamente, uma nova fábrica temporária será chamada, então isso é preferível à usar `SmartManagement.keepFactory`, mas se você não quer ter o trabalho de criar Bindings, ou deseja manter todas suas dependências no mesmo Binding, isso certamente irá te ajudar.
Fábricas ocupam pouca memória, elas não guardam instâncias, mas uma função com a "forma" daquela classe que você quer.
Isso é muito pouco, mas como o objetivo dessa lib é obter o máximo de desempenho possível usando o mínimo de recursos, Get remove até as fábricas por padrão. Use o que achar mais conveniente para você.
## Notas
* Nota: NÃO USE SmartManagement.keepfactory se você está usando vários Bindings. Ele foi criado para ser usado sem Bindings, ou com um único Binding ligado ao GetMaterialApp lá no `initialBinding`
* Nota²: Usar Bindings é completamente opcional, você pode usar Get.put() e Get.find() em classes que usam o controller sem problemas. Porém, se você trabalhar com Services ou qualquer outra abstração, eu recomendo usar Bindings. Especialmente em grandes empresas.

View File

@@ -0,0 +1,558 @@
- [Navegação sem rotas nomeadas](#navegação-sem-rotas-nomeadas)
- [SnackBars](#snackbars)
- [Dialogs](#dialogs)
- [BottomSheets](#bottomsheets)
- [Navegar com rotas nomeadas](#navegar-com-rotas-nomeadas)
- [Enviar dados para rotas nomeadas](#enviar-dados-para-rotas-nomeadas)
- [Links de Url dinâmicos](#links-de-url-dinâmicos)
- [Middleware](#middleware)
- [Change Theme](#change-theme)
- [Configurações Globais Opcionais](#configurações-globais-opcionais)
- [Nested Navigators](#nested-navigators)
## Navegação sem rotas nomeadas
Para navegar para uma próxima tela:
```dart
Get.to(ProximaTela());
```
Para fechar snackbars, dialogs, bottomsheets, ou qualquer coisa que você normalmente fecharia com o `Navigator.pop(context)` (como por exemplo fechar a View atual e voltar para a anterior):
```dart
Get.back();
```
Para ir para a próxima tela e NÃO deixar opção para voltar para a tela anterior (bom para SplashScreens, telas de login e etc.):
```dart
Get.off(ProximaTela());
```
Para ir para a próxima tela e cancelar todas as rotas anteriores (útil em telas de carrinho, votações ou testes):
```dart
Get.offAll(ProximaTela());
```
Para navegar para a próxima rota, e receber ou atualizar dados assim que retornar da rota:
```dart
var dados = await Get.to(Pagamento());
```
Na outra tela, envie os dados para a rota anterior:
```dart
Get.back(result: 'sucesso');
```
E use-os:
```dart
if (dados == 'sucesso') fazerQualquerCoisa();
```
Não quer aprender nossa sintaxe?
Apenas mude o `Navigator` (letra maiúscula) para `navigator` (letra minúscula), e você terá todas as funcionalidades de navegação padrão, sem precisar usar `context`
Exemplo:
```dart
// Navigator padrão do Flutter
Navigator.of(context).push(
context,
MaterialPageRoute(
builder: (BuildContext context) {
return HomePage();
},
),
);
// Get usando a sintaxe Flutter sem precisar do context
navigator.push(
MaterialPageRoute(
builder: (_) {
return HomePage();
},
),
);
// Sintaxe do Get (é bem melhor, mas você tem o direito de discordar)
Get.to(HomePage());
```
### SnackBars
Para ter um `SnackBar` simples no Flutter, você precisa do `context` do Scaffold, ou uma `GlobalKey` atrelada ao seu Scaffold.
```dart
final snackBar = SnackBar(
content: Text('Olá!'),
action: SnackBarAction(
label: 'Eu sou uma SnackBar velha e feia :(',
onPressed: (){}
),
);
// Encontra o Scaffold na árvore de Widgets e
// o usa para mostrar o SnackBar
Scaffold.of(context).showSnackBar(snackBar);
```
Com o Get:
```dart
Get.snackbar('Olá', 'eu sou uma SnackBar moderna e linda!');
```
Com Get, tudo que você precisa fazer é chamar `Get.snackbar()` de qualquer lugar no seu código, e/ou customizá-lo da forma que quiser!
```dart
Get.snackbar(
"Ei, eu sou uma SnackBar Get!", // título
"É inacreditável! Eu estou usando uma SnackBar sem context, sem boilerplate, sem Scaffold!", // mensagem
icon: Icon(Icons.alarm),
shouldIconPulse: true,
onTap:(){},
barBlur: 20,
isDismissible: true,
duration: Duration(seconds: 3),
);
////////// TODOS OS RECURSOS //////////
// Color colorText,
// Duration duration,
// SnackPosition snackPosition,
// Widget titleText,
// Widget messageText,
// bool instantInit,
// Widget icon,
// bool shouldIconPulse,
// double maxWidth,
// EdgeInsets margin,
// EdgeInsets padding,
// double borderRadius,
// Color borderColor,
// double borderWidth,
// Color backgroundColor,
// Color leftBarIndicatorColor,
// List<BoxShadow> boxShadows,
// Gradient backgroundGradient,
// TextButton mainButton,
// OnTap onTap,
// bool isDismissible,
// bool showProgressIndicator,
// AnimationController progressIndicatorController,
// Color progressIndicatorBackgroundColor,
// Animation<Color> progressIndicatorValueColor,
// SnackStyle snackStyle,
// Curve forwardAnimationCurve,
// Curve reverseAnimationCurve,
// Duration animationDuration,
// double barBlur,
// double overlayBlur,
// Color overlayColor,
// Form userInputForm
///////////////////////////////////
```
Se você prefere a SnackBar tradicional, ou quer customizar por completo, como por exemplo fazer ele ter uma só linha (`Get.snackbar` tem os parâmetros `title` e `message` obrigatórios), você pode usar `Get.rawSnackbar();` que fornece a API bruta na qual `Get.snackbar` foi contruído.
### Dialogs
Para abrir um dialog:
```dart
Get.dialog(SeuWidgetDialog());
```
Para abrir um dialog padrão:
```dart
Get.defaultDialog(
onConfirm: () => print("Ok"),
middleText: "Dialog made in 3 lines of code",
);
```
Você também pode usar `Get.generalDialog` em vez de `showGeneralDialog`.
Para todos os outros Widgets do tipo dialog do Flutter, incluindo os do Cupertino, você pode usar `Get.overlayContext` em vez do `context`, e abrir em qualquer lugar do seu código.
Para widgets que não usam `overlayContext`, você pode usar `Get.context`. Esses dois contextos vão funcionar em 99% dos casos para substituir o context da sua UI, exceto em casos onde o `inheritedWidget` é usado sem a navigation context.
### BottomSheets
`Get.bottomSheet()` é tipo o `showModalBottomSheet()`, mas não precisa do context.
```dart
Get.bottomSheet(
Container(
child: Wrap(
children: <Widget>[
ListTile(
leading: Icon(Icons.music_note),
title: Text('Música'),
onTap: () {}
),
ListTile(
leading: Icon(Icons.videocam),
title: Text('Vídeo'),
onTap: () {},
),
],
),
),
);
```
## Navegar com rotas nomeadas
- Se você prefere navegar por rotas nomeadas, Get também dá suporte a isso:
Para navegar para uma nova tela
```dart
Get.toNamed("/ProximaTela");
```
Para navegar para uma tela sem a opção de voltar para a rota atual.
```dart
Get.offNamed("/ProximaTela");
```
Para navegar para uma nova tela e remover todas rotas anteriores da stack
```dart
Get.offAllNamed("/ProximaTela");
```
Para definir rotas, use o `GetMaterialApp`:
```dart
void main() {
runApp(
GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => Home()),
GetPage(name: '/login', page: () => Login()),
GetPage(name: '/cadastro', page: () => Cadastro(), transition: Transition.cupertino),
]
)
);
}
```
Para lidar com a navegação para rotas não definidas (erro 404), você pode definir uma página unknownRoute em GetMaterialApp.
```dart
void main() {
runApp(
GetMaterialApp(
unknownRoute: GetPage(name: '/notfound', page: () => UnknownRoutePage()),
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => MyHomePage()),
GetPage(name: '/second', page: () => Second()),
],
)
);
}
```
### Enviar dados para rotas nomeadas
Apenas envie o que você quiser no parâmetro `arguments`. Get aceita qualquer coisa aqui, seja String, Map, List, ou até a instância de uma classe.
```dart
Get.toNamed("/ProximaTela", arguments: 'Get é o melhor');
```
Na sua classe ou controller:
```dart
print(Get.arguments); //valor: Get é o melhor
```
#### Links de Url dinâmicos
Get oferece links de url dinâmicos assim como na Web.
Desenvolvedores Web provavelmente já queriam essa feature no Flutter, e muito provavelmente viram um package que promete essa feature mas entrega uma sintaxe totalmente diferente do que uma url teria na web, mas o Get também resolve isso.
```dart
Get.offAllNamed("/ProximaTela?device=phone&id=354&name=Enzo");
```
na sua classe controller/bloc/stateful/stateless:
```dart
print(Get.parameters['id']); // valor: 354
print(Get.parameters['name']); // valor: Enzo
```
Você também pode receber parâmetros nomeados com o Get facilmente:
```dart
void main() => runApp(
GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => Home()),
/// Importante! ':user' não é uma nova rota, é somente uma
/// especificação do parâmentro. Não use '/segunda/:user/' e '/segunda'
/// se você precisa de uma nova rota para o user, então
/// use '/segunda/user/:user' se '/segunda' for uma rota
GetPage(name: '/segunda/:user', page: () => Segunda()), // recebe a ID
GetPage(name: '/terceira', page: () => Terceira(), transition: Transition.cupertino),
]
),
);
```
Envie dados na rota nomeada
```dart
Get.toNamed("/segunda/34954");
```
Na segunda tela receba os dados usando `Get.parameters[]`
```dart
print(Get.parameters['user']);
// valor: 34954
```
ou envie vários parâmetros como este
```dart
Get.toNamed("/profile/34954?flag=true");
```
Na segunda tela, pegue os dados por parâmetros normalmente
```dart
print(Get.parameters['user']);
print(Get.parameters['flag']);
// valor: 34954 true
```
E agora, tudo que você precisa fazer é usar `Get.toNamed)` para navegar por suas rotas nomeadas, sem nenhum `context` (você pode chamar suas rotas diretamente do seu BLoc ou do Controller), e quando seu aplicativo é compilado para a web, suas rotas vão aparecer na url ❤
#### Middleware
Se você quer escutar eventos do Get para ativar ações, você pode usar `routingCallback` para isso
```dart
GetMaterialApp(
routingCallback: (route){
if(routing.current == '/segunda'){
openAds();
}
}
)
```
Se você não estiver usando o `GetMaterialApp`, você pode usar a API manual para anexar um observer Middleware.
```dart
void main() {
runApp(
MaterialApp(
onGenerateRoute: Router.generateRoute,
initialRoute: "/",
navigatorKey: Get.key,
navigatorObservers: [
GetObserver(MiddleWare.observer), // AQUI !!!
],
)
);
}
```
Criar uma classe MiddleWare
```dart
class MiddleWare {
static observer(Routing routing) {
/// Você pode escutar junto com as rotas, snackbars, dialogs
/// e bottomsheets em cada tela.
/// Se você precisar entrar em algum um desses 3 eventos aqui diretamente,
/// você precisa especificar que o evento é != do que você está tentando fazer
if (routing.current == '/segunda' && !routing.isSnackbar) {
Get.snackbar("Olá", "Você está na segunda rota");
} else if (routing.current =='/terceira'){
print('última rota chamada');
}
}
}
```
Agora, use Get no seu código:
```dart
class Primeira extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.add),
onPressed: () {
Get.snackbar("Oi", "eu sou uma snackbar moderna");
},
),
title: Text('Primeira rota'),
),
body: Center(
child: ElevatedButton(
child: Text('Abrir rota'),
onPressed: () {
Get.toNamed("/segunda");
},
),
),
);
}
}
class Segunda extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.add),
onPressed: () {
Get.snackbar("Oi", "eu sou uma snackbar moderna");
},
),
title: Text('Segunda rota'),
),
body: Center(
child: ElevatedButton(
child: Text('Abrir rota'),
onPressed: () {
Get.toNamed("/terceira");
},
),
),
);
}
}
class Terceira extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Terceira Rota"),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Get.back();
},
child: Text('Voltar!'),
),
),
);
}
}
```
### Change Theme
Por favor não use nenhum widget acima do `GetMaterialApp` para atualizá-lo. Isso pode ativar keys duplicadas. Muitas pessoas estão acostumadas com a forma pré-história de criar um widget `ThemeProvider` só pra mudar o tema do seu app, e isso definitamente NÃO é necessário com o Get.
Você pode criar seu tema customizado e simplesmente adicionar ele dentro de `Get.changeTheme()` sem nenhum boilerplate para isso:
```dart
Get.changeTheme(ThemeData.light());
```
Se você quer criar algo como um botão que muda o tema com um toque, você pode combinar duas APIs do Get para isso, a API que checa se o tema dark está sendo usado, e a API de mudança de tema. E dentro de um `onPressed` você coloca isso:
```dart
Get.changeTheme(Get.isDarkMode? ThemeData.light(): ThemeData.dark());
```
Quando o modo escuro está ativado, ele vai alterar para o modo claro, e vice versa.
Se você quer saber a fundo como mudar o tema, você pode seguir esse tutorial no Medium que até te ensina a persistir o tema usando Get e shared_preferences:
- [Dynamic Themes in 3 lines using Get](https://medium.com/swlh/flutter-dynamic-themes-in-3-lines-c3b375f292e3) - Tutorial by [Rod Brown](https://github.com/RodBr).
### Configurações Globais Opcionais
Você pode mudar configurações globais para o Get. Apenas adicione `Get.config` no seu código antes de ir para qualquer rota ou faça diretamente no seu GetMaterialApp
```dart
// essa forma
GetMaterialApp(
enableLog: true,
defaultTransition: Transition.fade,
opaqueRoute: Get.isOpaqueRouteDefault,
popGesture: Get.isPopGestureEnable,
transitionDuration: Get.defaultDurationTransition,
defaultGlobalState: Get.defaultGlobalState,
);
// ou essa
Get.config(
enableLog = true,
defaultPopGesture = true,
defaultTransition = Transitions.cupertino
)
```
### Nested Navigators
Get fez a navegação aninhada no Flutter mais fácil ainda. Você não precisa do `context`, e você encontrará sua `navigation stack` pela ID.
* Nota: Criar navegação paralela em stacks pode ser perigoso. O idela é não usar `NestedNavigators`, ou usar com moderação. Se o seu projeto requer isso, vá em frente, mas fique ciente que manter múltiplas stacks de navegação na memória pode não ser uma boa ideia no quesito consumo de RAM.
Veja como é simples:
```dart
Navigator(
key: nestedKey(1), // crie uma key com um index
initialRoute: '/',
onGenerateRoute: (settings) {
if (settings.name == '/') {
return GetPageRoute(
page: () => Scaffold(
appBar: AppBar(
title: Text("Principal"),
),
body: Center(
child: TextButton(
color: Colors.blue,
child: Text("Ir para a segunda"),
onPressed: () {
Get.toNamed('/segunda', id:1); // navega pela sua navegação aninhada usando o index
},
)
),
),
);
} else if (settings.name == '/segunda') {
return GetPageRoute(
page: () => Center(
child: Scaffold(
appBar: AppBar(
title: Text("Principal"),
),
body: Center(
child: Text("Segunda")
),
),
),
);
}
}
),
```

View File

@@ -0,0 +1,501 @@
- [Simple State Manager](#simple-state-manager)
- [Uso do gerenciador de estado simples](#uso-do-gerenciador-de-estado-simples)
- [Sem StatefulWidget](#sem-statefulwidget)
- [Formas de uso](#formas-de-uso)
- [Reactive State Manager](#reactive-state-manager)
- [GetX vs GetBuilder vs Obx vs MixinBuilder](#getx-vs-getbuilder-vs-obx-vs-mixinbuilder)
- [Workers](#workers)
## Simple State Manager
Há atualmente vários gerenciadores de estados para o Flutter. Porém, a maioria deles envolve usar `ChangeNotifier` para atualizar os widgets e isso é uma abordagem muito ruim no quesito performance em aplicações de médio ou grande porte. Você pode checar na documentação oficial do Flutter que o [`ChangeNotifier` deveria ser usado com um ou no máximo dois listeners](https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html), fazendo-o praticamente inutilizável em qualquer aplicação média ou grande.
Outros gerenciadores de estado são bons, mas tem suas nuances.
* BLoC é bem seguro e eficiente, mas é muito complexo (especialmente para iniciantes), o que impediu pessoas de desenvolverem com Flutter.
* MobX é mais fácil que o BLoc e é reativo, quase perfeito eu diria, mas você precisa usar um code generator que, para aplicações de grande porte, reduz a produtividade (você terá que beber vários cafés até que seu código esteja pronto denovo depois de um `flutter clean`, o que não é culpa do MobX, na verdade o code generator que é muito lento!).
* Provider usa o `InheritedWidget` para entregar o mesmo listener, como uma forma de solucionar o problema reportado acima com o ChangeNotifier, o que indica que qualquer acesso ao ChangeNotifier dele tem que ser dentro da árvore de widgets por causa do `context` necessário para acessar o Inherited.
Get não é melhor ou pior que nenhum gerenciador de estado, mas você deveria analisar esses pontos tanto quanto os argumentos abaixo para escolher entre usar Get na sua forma pura, ou usando-o em conjunto com outro gerenciador de estado.
Definitivamente, Get não é o inimigo de nenhum gerenciador, porque Get é um microframework, não apenas um gerenciador, e pode ser usado tanto sozinho quanto em conjunto com eles.
Get tem um gerenciador de estado que é extremamente leve e fácil que não usa ChangeNotifier, vai atender a necessidade especialmente daqueles novos no Flutter, e não vai causar problemas em aplicações de grande porte.
**Que melhoras na performance o Get traz?**
1. Atualiza somente o widget necessário.
2. Não usa o `ChangeNotifier`, é o gerenciador de estado que utiliza menos memória (próximo de 0mb até agora).
3. Esqueça StatefulWidget's! Com Get você nunca mais vai precisar deles. Com outros gerenciadores de estado, você provavelmente precisa usar um StatefulWidget para pegar a instância do seu Provider, BLoc, MobX controller, etc. Mas já parou para pensar que seu AppBar, seu Scaffold e a maioria dos widgets que estão na sua classe são stateless? Então porque salvar o estado de uma classe inteira, se você pode salvar somente o estado de um widget stateful? Get resolve isso também. Crie uma classe Stateless, faça tudo stateless. Se você precisar atualizar um único componente, envolva ele com o `GetBuilder`, e seu estado será mantido.
4. Organize seu projeto de verdade! Controllers não devem ficar na sua UI, coloque seus `TextEditController`, ou qualquer controller que você usa dentro da classe Controller.
5. Você precisa acionar um evento para atualizar um widget assim que ele é renderizado? GetBuilder tem a propriedade `initState()` assim como um StatefulWidget, e você pode acionar eventos a partir do seu controller, diretamente de lá. Sem mais de eventos serem colocados no initState.
6. Você precisa acionar uma ação como fechar Streams, timers, etc? GetBuilder também tem a propriedade `dispose()`, onde você pode acionar eventos assim que o widget é destruído.
7. Use `Stream`s somente se necessário. Você pode usar seus StreamControllers dentro do seu controller normalmente, e usar `StreamBuilder` normalmente também, mas lembre-se, um Stream consume uma memória razoável, programação reativa é linda, mas você não abuse. 30 Streams abertos simultaneamente podem ser ainda piores que o `ChangeNotifier` (e olha que o ChangeNotifier é bem ruim)
8. Atualizar widgets sem gastar memória com isso. Get guarda somente a "ID do criador" do GetBuilder, e atualiza esse GetBuilder quando necessário. O consumo de memória do ID do GetBuilder é muito baixo mesmo para milhares de GetBuilders. Quando você cria um novo GetBuilder, na verdade você está compartilhando o estado do GetBuilder quem tem um ID do creator. Um novo estado não é criado para cada GetBuilder, o que reduz MUITO o consumo de memória RAM em aplicações grandes. Basicamente sua aplicação vai ser toda stateless, e os poucos widgets que serão Stateful (dentro do GetBuilder) vão ter um estado único, e assim atualizar um deles vai atualizar todos eles. O estado é um só.
9. Get é onisciente e na maioria dos casos sabe o momento exato de tirar um controller da memória. Você não precisa se preocupar com quando descartar o controller, Get sabe o melhor momento para fazer isso.
Vamos analisar o seguite exemplo:
`Class A => Class B (ControllerX) => Class C (ControllerX)`
* Na classe A o controller não está ainda na memória, porque você ainda não o usou (Get carrega só quando precisa).
* Na classe B você usou o controller, e ele entrou na memória.
* Na classe C você usou o mesmo controller da classe B, então o Get vai compartilhar o estado do controller B com o controller C, e o mesmo controller ainda estará na memória.
* Se você fechar a classe C e classe B, Get vai tirar o controller X da memória automaticamente e liberar recursos, porque a classe A não está usando o controller.
* Se você navegar para a Classe B denovo, o controller X vai entrar na memória denovo.
* Se em vez de ir para a classe C você voltar para a classe A, Get vai tirar o controller da memória do mesmo jeito.
* Se a classe C não usar o controller, e você tirar a classe B da memória, nenhuma classe estaria usando o controller X, e novamente o controller seria descartado.
**Nota**: A única exceção que pode atrapalhar o Get, é se
Você remover classe B da rota de forma inesperada, e tentasse usar o controller na classe C. Nesse caso, o ID do creator do controller que estava em B seria deletado, e o Get foi programado para remover da memória todo controller que não tem nenhum ID de creator. Se é sua intenção fazer isso, adicione a config `autoRemove: false` no GetBuilder da classe B, e use `adoptID = true;` no GetBuilder da classe C.
### Uso do gerenciador de estado simples
```dart
// Crie a classe Controller e entenda ela do GetController
class Controller extends GetController {
int counter = 0;
void increment() {
counter++;
update(); // use update() para atualizar a variável counter na UI quando increment for chamado
}
}
// Na sua classe Stateless/Stateful, use o GetBuilder para atualizar o texto quando a função increment for chamada
GetBuilder<Controller>(
init: Controller(), // INICIE O CONTROLLER SOMENTE NA PRIMEIRA VEZ
builder: (controller) => Text(
'${controller.counter}',
),
),
// Inicialize seu controller somente uma vez. Na segunda vez que você for usar GetBuilder para o mesmo controller, não Inicialize denovo. Seu controller será automaticamente removido da memória. Você não precisa se preocupar com isso, Get vai fazer isso automaticamente, apenas tenha certeza que você não vai inicializar o mesmo controller duas vezes.
```
**Feito!**
Você já aprendeu como gerenciar estados com o Get.
Nota: Você talvez queira uma maior organização, e não querer usar a propriedade `init`. Para isso, você pode criar uma classe e extendê-la da classe `Bindings`, e nela mencionar os controllers que serão criados dentro daquela rota. Controllers não serão criados naquele momento exato, muito pelo contrário, isso é apenas uma declaração, para que na primeira vez que vc use um Controller, Get vai saber onde procurar. Get vai continuar no formato "lazyLoad" (carrega somente quando necessário) e vai continuar descartando os Controllers quando eles não forem mais necessários. Veja pub.dev example para ver como funciona.
Se você navegar por várias rotas e precisa de algum dado que estava em um outro controller previamente utilizado, você só precisa utilizar o GetBuilder novamente (sem o init):
```dart
class OutraClasse extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: GetBuilder<Controller>(
builder: (s) => Text('${s.counter}'),
),
),
);
}
}
```
Se você precisa utilizar seu controller em vários outros lugares, e fora do GetBuilder, apenas crie um getter no seu controller que você consegue ele facilmente (ou use `Get.find<Controller>()` )
```dart
class Controller extends GetController {
/// Você não precisa disso. Eu recomendo usar isso apenas
/// porque a sintaxe é mais fácil.
/// com o método estático: Controller.to.increment();
/// sem o método estático: Get.find<Controller>().increment();
/// Não há diferença em performance, nem efeito colateral por usar esse sintaxe. Só uma não precisa da tipage, e a outra forma a IDE vai autocompletar.
static Controller get to => Get.find(); // adicione esta linha
int counter = 0;
void increment() {
counter++;
update();
}
}
```
E então você pode acessar seu controller diretamente, desse jeito:
```dart
FloatingActionButton(
onPressed:(){
Controller.to.increment(),
} // Isso é incrivelmente simples!
child: Text("${Controller.to.counter}"),
),
```
Quando você pressionar o FloatingActionButton, todos os widgets que estão escutando a variável `counter` serão atualizados automaticamente.
#### Sem StatefulWidget
Usar StatefulWidget's significa guardar o estado de telas inteiras desnecessariamente, mesmo porque se você precisa recarregar minimamente algum widget, você vai incorporá-lo num Consumer/Observer/BlocProvider/GetBuilder/GetX/Obx, que vai ser outro StatefulWidget.
A classe StatefulWidget é maior que a classe StatelessWidget, o que vai alocar mais memória, e isso pode não fazer uma diferença significativa com uma ou duas classes, mas com certeza fará quando você tiver 100 delas!
A não ser que você precise usar um mixin, como o `TickerProviderStateMixin`, será totalmente desnecessário usar um StatefulWidget com o Get.
Você pode chamar todos os métodos de um StatefulWidget diretamente de um GetBuilder.
Se você precisa chamar o método `initState()` ou `dispose()` por exemplo, é possível chamá-los diretamente:
```dart
GetBuilder<Controller>(
initState: (_) => Controller.to.fetchApi(),
dispose: (_) => Controller.to.closeStreams(),
builder: (s) => Text('${s.username}'),
),
```
Uma abordagem muito melhor que isso é usar os métodos `onInit()` e `onClose()` diretamente do seu controller.
```dart
@override
void onInit() {
fetchApi();
super.onInit();
}
```
* Nota: Se você quiser executar um método no momento que o controller é chamado pela primeira vez, você NÃO precisa usar construtores para isso, na verdade, usando um package que é focado em performance como o Get, isso chega no limite de má prática, porque se desvia da lógica de que os controllers são criados ou alocados (Se você criar uma instância desse controller, o construtor vai ser chamado imediatamente, e ele será populado antes de ser usado, ou seja, você está alocando memória e não está utilizando, o que fere os princípios desse package). Os métodos `onInit()` e `onClose()` foram criados para isso, eles serão chamados quando o controller é criado, ou usados pela primeira vez, dependendo de como você está utilizando o Get (lazyPut ou não). Se quiser, por exemplo, fazer uma chamada para sua API para popular dados, você pode esquecer do estilo antigo de usar `initState()/dispose()`, apenas comece sua chamada para a api no `onInit`, e apenas se você precisar executar algum comando como fechar stream, use o `onClose()`.
O propósito desse package é precisamente te dar uma solução completa para navegação de rotas, gerenciamente de dependências e estados, usando o mínimo possível de dependências, com um alto grau de desacoplamento. Get envolve em todas as APIs de baixo e alto nível dentro de si mesmo, para ter certeza que você irá trabalhar com o mínimo possível de acoplamento.
Nós centralizamos tudo em um único package. Dessa forma, você pode colocar somente widgets na sua view, e o controller pode ter só lógica de negócio, sem depender de nenhum elemento da View. Isso fornece um ambiente de trabalho muito mais limpo, para que parte do seu time possa trabalhar apenas com os widgets, sem se preocupar sobre enviar dados para o controller, e outra parte se preocupe apenas com a lógica de negócio, sem depender de nenhum elemento da view.
Então, para simplificar isso:
Você não precisa chamar métodos no `initState()` e enviá-los para seu controller via parâmetros, nem precisa do construtor do controller pra isso, você possui o método `onInit()` que é chamado no momento certo para você inicializar seus services.
Você não precisa chamar o método `dispose()`, você tem o método `onClose()` que vai ser chamado no momento exato quando seu controller não for mais necessário e será removido da memória. Dessa forma, você pode deixar a view somente para os widgets, e o controller só para as regras de negócio.
Não chame o método `dispose()` dentro do GetController, não vai fazer nada. Lembre-se que o controller não é um widget, você não deveria usar o dispose lá, e esse método será automaticamente e inteligentemente removido da memória pelo Get. Se você usou algum stream no controller e quer fechá-lo, apenas insira o método para fechar os stream dentro do método `onClose()`.
Exemplo:
```dart
class Controller extends GetController {
var user = StreamController<User>();
var name = StreamController<String>();
/// fechar stream = método onClose(), não dispose().
@override
void onClose() {
user.close();
name.close();
super.onClose();
}
}
```
Ciclo de vida do controller:
* `onInit()`: Onde ele é criado.
* `onClose()`: Onde ele é fechado para fazer mudanças em preparação para o método delete()
* deleted: Você não tem acesso a essa API porque ela está literalmente removendo o controller da memória. Está literalmente deletado, sem deixar rastros.
##### Formas de uso
Você pode usar uma instância do Controller diretamente no `value` do GetBuilder
```dart
GetBuilder<Controller>(
init: Controller(),
builder: (value) => Text(
'${value.counter}', //aqui
)
),
```
Você talvez também precise de uma instância do seu controller fora do GetBuilder, e você pode usar essas abordagens para conseguir isso:
essa:
```dart
class Controller extends GetController {
static Controller get to => Get.find(); // criando um getter estático
[...]
}
// Na sua view/tela
GetBuilder<Controller>(
init: Controller(), // use somente uma vez por controller, não se esqueça
builder: (_) => Text(
'${Controller.to.counter}', //aqui
)
),
```
ou essa:
```dart
class Controller extends GetController {
// sem nenhum método estático
[...]
}
// Numa classe stateful/stateless
GetBuilder<Controller>(
init: Controller(), // use somente uma vez por controller, não se esqueça
builder: (_) => Text(
'${Get.find<Controller>().counter}', //aqui
)
),
```
* Você pode usar outras abordagens "menos regulares". Se você está utilizando outro gerenciador de dependências, como o get_it, modular, etc., e só quer entregar a instância do controller, pode fazer isso:
```dart
Controller controller = Controller();
[...]
GetBuilder<Controller>(
init: controller, //aqui
builder: (_) => Text(
'${controller.counter}', // aqui
)
),
```
Essa abordagem não é recomendada, uma vez que você vai precisar descartar os controllers manualmente, fechar seus stream manualmente, e literalmente abandonar um dos grandes benefícios desse package, que é controle de memória inteligente. Mas se você confia no seu potencial, vai em frente!
Se você quiser refinar o controle de atualização de widgets do GetBuilder, você pode assinalar a ele IDs únicas
```dart
GetBuilder<Controller>(
id: 'text'
init: Controller(), // use somente uma vez por controller, não se esqueça
builder: (_) => Text(
'${Get.find<Controller>().counter}', //aqui
)
),
```
E atualizá-los dessa forma:
```dart
update(['text']);
```
Você também pode impor condições para o update acontecer:
```dart
update(['text'], counter < 10);
```
GetX faz isso automaticamente e somente reconstrói o widget que usa a exata variável que foi alterada. Se você alterar o valor da variável para o mesmo valor que ela era anteriormente e isso não sugira uma mudança de estado, GetX não vai reconstruir esse widget, economizando memória e ciclos de CPU (Ex: 3 está sendo mostrado na tela, e você muda a variável para ter o valor 3 denovo. Na maioria dos gerenciadores de estado, isso vai causar uma reconstrução do widget, mas com o GetX o widget só vai reconstruir se de fato o estado mudou).
GetBuilder é focado precisamente em múltiplos controles de estados. Imagine que você adicionou 30 produtos ao carrinho, você clica pra deletar um deles, e ao mesmo tempos a lista é atualizada, o preço é atualizado e o pequeno círculo mostrando a quantidade de produtos é atualizado. Esse tipo de abordagem faz o GetBuilder excelente, porque ele agupa estados e muda todos eles de uma vez sem nenhuma "lógica computacional" pra isso. GetBuilder foi criado com esse tipo de situação em mente, já que pra mudanças de estados simples, você pode simplesmente usar o `setState()`, e você não vai precisar de um gerenciador de estado para isso. Porém, há situações onde você quer somente que o widget onde uma certa variável mudou seja reconstruído, e isso é o que o GetX faz com uma maestria nunca vista antes.
Dessa forma, se você quiser controlar individualmente, você pode assinalar ID's para isso, ou usar GetX. Isso é com você, apenas lembre-se que quando mais "widgets individuais" você tiver, mais a performance do GetX vai se sobressair. Mas o GetBuilder vai ser superior quando há multiplas mudanças de estado.
Você pode usar os dois em qualquer situação, mas se quiser refinar a aplicação para a melhor performance possível, eu diria isso: se as suas variáveis são alteradas em momentos diferentes, use GetX, porque não tem competição para isso quando o widget é para reconstruir somente o que é necessário. Se você não precisa de IDs únicas, porque todas as suas variáveis serão alteradas quando você fazer uma ação, use GetBuilder, porque é um atualizador de estado em blocos simples, feito com apenas algumas linhas de código, para fazer justamente o que ele promete fazer: atualizar estado em blocos. Não há forma de comparar RAM, CPU, etc de um gerenciador de estado gigante com um simples StatefulWidget (como GetBuilder) que é atualizado quando você chama `update()`. Foi feito de uma forma simples, para ter o mínimo de lógica computacional, somente para cumprir um único papel e gastar o mínimo de recursos possível.
Se você quer um gerenciador de estados poderoso, você pode ir sem medo para o GetX. Ele não funciona com variáveis, mas sim fluxos. Tudo está em seus streams por baixo dos panos. Você pode usar `rxDart` em conjunto com ele, porque tudo é um stream, você pode ouvir o evento de cada "variável", porque tudo é um stream, é literalmente BLoc, só que mais fácil que MobX e sem code generators ou decorations.
## Reactive State Manager
Se você quer poder, Get té dá o mais avançado gerenciador de estado que você pode ter.
GetX foi construído 100% baseado em Stream, e te dá todo o poder de fogo que o BLoc te dá, com uma sintaxe mais fácil que a do MobX.
Sem decorations, você poder tornar qualquer coisa em um `Observable` com somete um `.obs`
Performance máxima: Somando ao fato de ter um algoritmo inteligente para reconstrução mínima, Get usa comparadores para ter certeza que o estado mudou. Se você encontrar erros na sua aplicação, e enviar uma mudança de estado duplicada, Get vai ter certeza que sua aplicação não entre em colapso.
O estado só muda se o valor mudar. Essa é a principal diferença entre Get, e usar o `Computed` do MobX. Quando juntar dois observables, se um deles é alterado, a escuta daquele observable vai mudar. Com Get, se você juntar duas variáveis (que na verdade é desnecessário), GetX(similar ao Observer) vai somente mudar se implicar numa mudança real de estado. Exemplo:
```dart
final count1 = 0.obs;
final count2 = 0.obs;
int get sum => count1.value + count2.value;
```
```dart
GetX<Controller>(
builder: (_) {
print("count 1 foi reconstruído");
return Text('${_.count1.value}');
},
),
GetX<Controller>(
builder: (_) {
print("count 2 foi reconstruído");
return Text('${_.count2.value}');
},
),
GetX<Controller>(
builder: (_) {
print("sum foi reconstruído");
return Text('${_.sum}');
},
),
```
Se nós incrementarmos o número do `count1`, somente `count1` e `sum` serão reconstruídos, porque `count1` agora tem um valor de 1, e 1 + 0 = 1, alterando o valor do `sum`.
Se nós mudarmos `count2`, somente `count2` e `sum` serão reconstruídos, porque o valor do 2 mudou, e o resultado da variável `sum` é agora 2.
Se definirmos o valor de `count1` para 1, nenhum widget será reconstruído, porque o valor já era 1.
Se definirmos o valor de `count1` para 1 denovo, e definirmos o valor de `count2` para 2, então somente o `count2`e o `sum` vão ser reconstruídos, simplesmente porque o GetX não somente altera o que for necessário, ele também evita eventos duplicados.
Somando a isso, Get provê um controle de estado refinado. Você pode adicionar uma condição a um evento (como adicionar um objeto a uma lista).
```dart
list.addIf(item < limit, item);
```
Sem decorations, sem code generator, sem complicações, GetX vai mudar a forma que você controla seus estados no Flutter, e isso não é uma promessa, isso é uma certeza!
Sabe o app de contador do Flutter? Sua classe Controller pode ficar assim:
```dart
class CountController extends RxController {
final count = 0.obs;
}
```
E com um simples:
```dart
controller.count.value++
```
Você pode atualizar a variável counter na sua UI, independente de onde esteja sendo armazenada.
Você pode transformar qualquer coisa em obs:
```dart
class RxUsuario {
final nome = "Camila".obs;
final idade = 18.obs;
}
class Usuario {
Usuario({String nome, int idade});
final rx = RxUsuario();
String get nome => rx.nome.value;
set nome(String value) => rx.nome.value = value;
int get idade => rx.idade.value;
set idade(int value) => rx.idade.value = value;
}
```
```dart
void main() {
final usuario = Usuario();
print(usuario.nome);
usuario.idade = 23;
usuario.rx.idade.listen((int idade) => print(idade));
usuario.idade = 24;
usuario.idade = 25;
}
___________
saída:
Camila
23
24
25
```
Trabalhar com `Lists` usando Get é algo muito agradável. Elas são completamente observáveis assim como os objetos dentro dela. Dessa forma, se você adicionar uma valor a lista, ela vai automaticamente reconstruir os widgets que a usam.
Você também não precisa usar `.value` com listas, a api dart nos permitiu remover isso. Infelizmente tipos primitivos como String e int não podem ser extendidos, fazend o uso de `.value` obrigatório, mas isso não será um problema se você usar getters e setters para esses.
```dart
final list = List<Usuario>().obs;
ListView.builder (
itemCount: list.length
)
```
Você não precisa trabalhar com sets se não quiser. Você pode usar as APIs `assign` e `assignAll`
A api `assign` vai limpar sua lista e adicionar um único objeto que você quer.
A api `assignAll` vai limpar sua lista e vai adicionar objetos `Iterable` que você precisa.
Nós poderíamos remover a obrigação de usar o value em String e int com uma simples decoration e code generator, mas o propósito desse package é precisamente não precisar de nenhuma dependência externa. É para oferecer um ambiente pronto para programar, envolvendo os essenciais (gerenciamento de rotas, dependências e estados), numa forma simples, leve e performática sem precisar de packages externos. Você pode literalmente adicionar 3 letras e um ':' no seu pubspec.yaml e começar a programar. Todas as soluções incluídas por padrão, miram em facilidade, produtividade e performance.
O peso total desse package é menor que o de um único gerenciador de estado, mesmo sendo uma solução completa, e isso é o que você precisa entender.
Se você está incomodado com o `.value`, e gosta de code generator, MobX é uma excelente alternativa, e você pode usá-lo em conjunto com o Get. Para aqueles que querem adicionar uma única dependência no pubspec.yaml e começar a programar sem se preocupar sobre a versão de um package sendo incompatível com outra, ou se o erro de uma atualização do estado está vindo do gerenciador de estado ou da dependência, ou ainda, não quer se preocupar com a disponibilidade de controllers, prefere literalmente "só programar", Get é perfeito.
Agora se você não tem problemas com o code generator do MobX, ou não vê problema no boilerplate do BLoc, você pode simplesmente usar o Get para rotas, e esquecer que nele existe um gerenciador de estado. Get nasceu da necessidade, minha empresa tinha um projeto com mais de 90 controllers e o code generator simplesmente levava mais de 30 minutos para completar suas tarefas depois de um `flutter clean` numa máquina razoavelmente boa, se o seu projeto tem 5, 10, 15 controllers, qualquer gerenciador de estado vai ter satisfazer.
Se você tem um projeto absurdamente grande, e code generator é um problema para você, então você foi presenteado com essa solução que é o Get.
Obviamente, se alguém quiser contribuir para o projeto e criar um code generator, or algo similar, eu vou linkar no README como uma alternativa, minha necessidade não é a necessidade de todos os devs, mas por agora eu digo: há boas soluções que já fazem isso, como MobX.
Tipagem no Get usando Bindings é desnecessário. Você pode usar o widget `Obx()` em vez do GetX, e ele só recebe a função anônima que cria o widget.
### GetX vs GetBuilder vs Obx vs MixinBuilder
Em uma década de trabalho com programação eu fui capaz de aprender umas lições valiosas.
Meu primeiro contato com programação reactiva foi tão "Nossa, isso é incrível" e de fato é incrível.
Porém, não é ideal para todas as situações. De vez em quando tudo que você precisa é mudar o estado de duas ou três variáveis ao mesmo tempo, ou uma mudança de estado diferente, que nesses casos a programação reativa não é ruim, mas não é apropriado.
Programação reativa tem um consumo de RAM maior que pode ser compensado pelo fluxo individual, que vai se encarregar que apenas o Widget necessário é reconstruído, mas criar uma lista com 80 objetos, cada um com vários streams não é uma boa ideia. Abra o `dart inspect` e cheque quando um StreamBuilder consome, e você vai entender o que eu estou tentando dizer a você.
Com isso em mente, eu criar o gerenciador de estados simples. É simples, e é exatamente o que você deve exigir dele: alterar vários estados em blocos de uma forma simples, e mais econômico possível.
GetBuilder economiza muito quando se trata de RAM, e dificilmente vai existir uma forma mais econômica de lidar com isso do que ele (pelo menos eu não consigo imaginar nenhum. Se existir, me avise).
Entretando, GetBuilder ainda é um gerenciador de estados "mecânico", você precisa chamar o `update()` assim como você faria com o `notifyListeners()` do `Provider`.
Há outras situações onde a programação reativa é muito interessante, e não trabalhar com ela é a mesma coisa que reinventar a roda. Com isso em mente, GetX foi criado para prover tudo que há de mais moder e avançado num gerenciado de estado. Ele atualiza somente o que é necessário quando for necessário. Se por exemplo você tiver um erro que mande 300 alterações de estado simultaneamente, GetX vai filtrar e alterar a tela somente se o estado mudar de verdade.
GetX ainda é mais econômico que qualquer outro gerenciador de estados reativo, mas consome um pouco mais de RAM do que o GetBuilder. Pensando nisso e mirando em minimizar o consumo de recursos que o Obx foi criado.
Ao contrário de GetX e GetBuilder, você não será capaz de inicializar o controller dentro do Obx, é só um Widget com um `StreamSubscription` que recebe eventos de mundança das childrens ou child, só isso. É mais econômico que o GetX, mas perde para o GetBuilder, que é nada mais que o esperado, já que é reativo. GetBuilder tem uma das formas mais simplística que existe, de guardar o hashcode de um Widget e seu StateSetter. Com Obs você não precisa escrever seu tipo de controller, e você pode ouvir mudanças de múltiplos controllers diferentes, mas eles precisam ser inicializados antes, tanto usando o forma demonstrada no exemplo no início desse README, ou usando a classe `Bindings`
Concluindo, uma pessoa abriu um pedido para uma feature nova, como ele queria usar somente um tipo de variável reativa, e precisava inserir um Obx junto do GetBuilder para isso. Pensando nisso, `MixinBuilder` foi criado. Ele permite as duas formas de alterar estados: usando variáveis com `.obs`, ou a forma mecânica via `update()`
Porém, desses 4 widgets, esse é o que consome mais recursos, já que usa os dois gerenciadores de estado combinados.
* Nota: Para usar GetBuilder e MixinBuilder você precisa usar GetController. Para usar GetX ou Obx você precisa usar RxController.
## Workers
Workers vai te dar suporte, ativando callbacks específicos quando um evento ocorrer.
```dart
/// Chamada toda vez que a variável for alterada
ever(count1, (value) => print("novo valor: $value"));
/// Chamada apenas na primeira vez que a variável for alterada
once(count1, (value) => print("novo valor: $value (não vai mudar mais)"));
/// Anti DDos - Chamada toda vez que o usuário parar de digitar por 1 segundo, por exemplo.
debounce(count1, (value) => print("debouce $value"), time: Duration(seconds: 1));
/// Ignore todas as mudanças num período de 1 segundo.
interval(count1, (value) => print("interval $value"), time: Duration(seconds: 1));
```
* **ever**
é chamado toda vez que a variável mudar de valor. É só isso.
* **once**
é chamado somente na primeira vez que a variável mudar de valor.
* **debounce**
É muito útil em funções de pesquisa, onde você somente quer que a API seja chamada depois que o usuário terminar de digitar. Normalmente, se o usuário digitar "Jonny", será feita 5 requests na sua API, pelas letras 'J', 'o', 'n', 'n' e 'y'. Com o `debounce` isso não acontece, porque você tem a sua disposição uma função que só fazer a pesquisa na api quando o usuário parar de digitar. O `debounce` é bom para táticas anti-DDos, para funções de pesquisa em que cada letra digitada ativaria um request na API. Debounce vai esperar o usário parar de digitar o nome, para então fazer o request para API.
* **interval**
Quando se usa `debounce` , se o usuário fizer 1000 mudanças numa variável em 1 segundo, o `debounce` só computa a última mudança feita após a inatividade por um tempo estipulado (o padrão é 800 milisegundos). `interval` por outro lado vai ignorar todas as ações do usuário pelo período estipulado. Se o `interval` for de 1 segundo, então ele só conseguirá enviar 60 eventos por segundo. Se for 3 segundos de prazo, então o `interval` vai enviar 20 eventos por minuto (diferente do `debounce` que só enviaria o evento depois que o prazo estipulado acabar). Isso é recomendado para evitar abuso em funções que o usuário pode clicar rapidamente em algo para ter uma vantagem. Para exemplificar, imagine que o usuário pode ganhar moedas clicando num botão. Se ele clicar 300 vezes no botão em um minuto, ele vai ganhar 300 moedas. Usando o `interval`, você pode definir um prazo de 1 segundo por exemplo, e mesmo se ele clicasse 300 vezes em um minuto, ele ganharia apenas 60 moedas. Se fosse usado o `debounce`, o usuário ganharia apenas 1 moeda, porque só é executado quando o usuário para de clicar por um prazo estabelecido.
- NOTA: Workers devem ser utilizados sempre na inicialização de um Controller ou Classe, dessa forma, ele deve estar sempre no onInit (recomendado), no Construtor de classe, ou no initState de um StatefulWidget (essa prática não é recomendada na maioria dos casos, mas não deve trazer efeitos colaterais).

View File

@@ -0,0 +1,379 @@
# Управление зависимостями
- [Управление зависимостями](#управление-зависимостями)
- [Методы создания экземпляров](#методы-создания-экземпляров)
- [Get.put()](#getput)
- [Get.lazyPut](#getlazyput)
- [Get.putAsync](#getputasync)
- [Get.create](#getcreate)
- [Применение методов/классов создания экземпляров](#применение-методовклассов-создания-экземпляров)
- [Различия между методами](#различия-между-методами)
- [Подвязки](#подвязки)
- [Класс Bindings](#класс-bindings)
- [BindingsBuilder](#bindingsbuilder)
- [SmartManagement](#smartmanagement)
- [Как поменять](#как-поменять)
- [SmartManagement.full](#smartmanagementfull)
- [SmartManagement.onlyBuilders](#smartmanagementonlybuilders)
- [SmartManagement.keepFactory](#smartmanagementkeepfactory)
- [Как подвязки работают под капотом](#как-подвязки-работают-под-капотом)
- [Примечания](#примечания)
Get имеет простой и мощный менеджер зависимостей, позволяющий вам получить тот же класс, что и ваш Bloc или Controller, с помощью 1 строки кода, без Provider и inheritedWidget:
```dart
Controller controller = Get.put(Controller()); // Rather Controller controller = Controller();
```
Вместо того, чтобы создавать экземпляр вашего класса в классе, который вы используете, вы создаёте его в экземпляре Get, что сделает его доступным во всем приложении.
Таким образом Вы можете использовать свой контроллер (или Bloc)
- Примечание: Если вы применяете менеджер состояний Get, обратите внимание на [Bindings](#bindings) api, упрощающего подключение вашего предсталения к контроллеру.
- Примечаие²: Управление зависимостями Get отделено от других частей пакета, поэтому, если, например, ваше приложение уже использует другой менеджер состояний, вам не нужно его менять, вы можете использовать этот менеджер внедрения зависимостей без каких-либо проблем.
## Методы создания экземпляров
Методы и настраиваемые параметры:
### Get.put()
Самый распространенный способ внедрения зависимости. Например, хорош для контроллеров ваших представлений.
```dart
Get.put<SomeClass>(SomeClass());
Get.put<LoginController>(LoginController(), permanent: true);
Get.put<ListItemController>(ListItemController, tag: "some unique string");
```
Это все параметры, которые вы можете установить при использовании put:
```dart
Get.put<S>(
// mandatory: the class that you want to get to save, like a controller or anything
// note: "S" means that it can be a class of any type
S dependency
// optional: this is for when you want multiple classess that are of the same type
// since you normally get a class by using Get.find<Controller>(),
// you need to use tag to tell which instance you need
// must be unique string
String tag,
// optional: by default, get will dispose instances after they are not used anymore (example,
// the controller of a view that is closed), but you might need that the instance
// to be kept there throughout the entire app, like an instance of sharedPreferences or something
// so you use this
// defaults to false
bool permanent = false,
// optional: allows you after using an abstract class in a test, replace it with another one and follow the test.
// defaults to false
bool overrideAbstract = false,
// optional: allows you to create the dependency using function instead of the dependency itself.
// this one is not commonly used
InstanceBuilderCallback<S> builder,
)
```
### Get.lazyPut
Можно отложить загрузку зависимости, чтобы она создавалась только тогда, когда она используется. Очень полезно для вычислительных ресурсоёмких классов или если вы хотите создать экземпляры нескольких классов в одном месте (например, в классе Bindings), и вы знаете, что не собираетесь использовать этот класс в то время.
```dart
/// ApiMock will only be called when someone uses Get.find<ApiMock> for the first time
Get.lazyPut<ApiMock>(() => ApiMock());
Get.lazyPut<FirebaseAuth>(
() {
// ... some logic if needed
return FirebaseAuth();
},
tag: Math.random().toString(),
fenix: true
)
Get.lazyPut<Controller>( () => Controller() )
```
Это все параметры, которые вы можете установить при использовании lazyPut:
```dart
Get.lazyPut<S>(
// mandatory: a method that will be executed when your class is called for the first time
InstanceBuilderCallback builder,
// optional: same as Get.put(), it is used for when you want multiple different instance of a same class
// must be unique
String tag,
// optional: It is similar to "permanent", the difference is that the instance is discarded when
// is not being used, but when it's use is needed again, Get will recreate the instance
// just the same as "SmartManagement.keepFactory" in the bindings api
// defaults to false
bool fenix = false
)
```
### Get.putAsync
Если вы хотите зарегистрировать асинхронный экземпляр, вы можете использовать `Get.putAsync`:
```dart
Get.putAsync<SharedPreferences>(() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setInt('counter', 12345);
return prefs;
});
Get.putAsync<YourAsyncClass>( () async => await YourAsyncClass() )
```
Это все параметры, которые вы можете установить при использовании putAsync:
```dart
Get.putAsync<S>(
// mandatory: an async method that will be executed to instantiate your class
AsyncInstanceBuilderCallback<S> builder,
// optional: same as Get.put(), it is used for when you want multiple different instance of a same class
// must be unique
String tag,
// optional: same as in Get.put(), used when you need to maintain that instance alive in the entire app
// defaults to false
bool permanent = false
)
```
### Get.create
Это хитрый метод. Подробное объяснение того, что это такое, и различий между ними можно найти в разделе [Различия между методами](#различия-между-методами):
```dart
Get.Create<SomeClass>(() => SomeClass());
Get.Create<LoginController>(() => LoginController());
```
Это все параметры, которые вы можете установить при использовании create:
```dart
Get.create<S>(
// required: a function that returns a class that will be "fabricated" every
// time `Get.find()` is called
// Example: Get.create<YourClass>(() => YourClass())
FcBuilderFunc<S> builder,
// optional: just like Get.put(), but it is used when you need multiple instances
// of a of a same class
// Useful in case you have a list that each item need it's own controller
// needs to be a unique string. Just change from tag to name
String name,
// optional: just like int`Get.put()`, it is for when you need to keep the
// instance alive thoughout the entire app. The difference is in Get.create
// permanent is true by default
bool permanent = true
```
## Применение методов/классов создания экземпляров
Представьте, что вы прошли через множество маршрутов и вам нужны данные, которые остались в вашем контроллере. Вам понадобится менеджер состояний в сочетании с Provider или Get_it, верно? Только не с Get. Вам просто нужно попросить Get «найти» ваш контроллер, никаких дополнительных зависимостей вам не потребуется:
```dart
final controller = Get.find<Controller>();
// OR
Controller controller = Get.find();
// Yes, it looks like Magic, Get will find your controller, and will deliver it to you.
// You can have 1 million controllers instantiated, Get will always give you the right controller.
```
И тогда вы сможете восстановить данные вашего контроллера, которые были там получены:
```dart
Text(controller.textFromApi);
```
Поскольку возвращаемое значение является обычным классом, вы можете делать все, что захотите:
```dart
int count = Get.find<SharedPreferences>().getInt('counter');
print(count); // out: 12345
```
Чтобы удалить экземпляр Get:
```dart
Get.delete<Controller>(); //usually you don't need to do this because GetX already delete unused controllers
```
## Различия между методами
Сперва давайте рассмотрим параметр `fenix` метода Get.lazyPut и `permanent` других методов.
Фундаментальное различие между `permanent` и `fenix` заключается в том, как вы хотите хранить свои экземпляры.
Закрепляя это: по умолчанию GetX удаляет экземпляры, когда они не используются.
Это означает, что: если на экране 1 есть контроллер 1, а на экране 2 есть контроллер 2, и вы удаляете первый маршрут из стека (например, если вы используете `Get.off()` или `Get.offNamed()`), контроллер 1 теперь не используется, поэтому он будет стёрт.
Но если вы используете `permanent: true`, тогда контроллер не будет потерян при этом переходе - что очень полезно для служб, которые вы хотите поддерживать на протяжении всего приложения.
`fenix` предназначен для сервисов, о потере которых вы не беспокоитесь между сменами экрана, но когда вам нужен этот сервис, вы ожидаете, что он будет активен. По сути, он избавится от неиспользуемого контроллера/службы/класса, но когда он вам понадобится, он «воссоздает из пепла» новый экземпляр.
Исходя из различий между методами:
- Get.put и Get.putAsync следует одному и тому же порядку создания, с той разницей, что второй использует асинхронный метод: эти два метода создают и инициализируют экземпляр. Они вставляются непосредственно в память с использованием внутреннего метода `insert` с параметрами `permanent: false` и `isSingleton: true` (параметр isSingleton предназначен только для того, чтобы указать, следует ли использовать зависимость от `dependency` или она должна использовать зависимость от `FcBuilderFunc`). После этого вызывается `Get.find()`, который немедленно инициализирует экземпляры, находящиеся в памяти.
- Get.create: Как следует из названия, он «создаст» вашу зависимость! Подобно `Get.put()`, он также вызывает внутренний метод `insert` для создания экземпляра. Но `permanent` становится true и `isSingleton` становится false (поскольку мы «создаем» нашу зависимость, она не может быть синглтоном, поэтому false). И поскольку `permanent: true`, мы по умолчанию не теряем его между экранами! Также, `Get.find()` не вызывается немедленно, он ожидает использования на экране для вызова. Он создан таким образом, чтобы использовать параметр `permanent`, `Get.create()` был создан с целью создания не общих экземпляров, но не удаляемых, как, например, кнопка в listView, где вам нужен уникальный экземпляр для этого списка - по этой причине Get.create необходимо использовать вместе с GetWidget.
- Get.lazyPut: Как следует из названия, это ленивый процесс. Экземпляр создается, но он не вызывается для немедленного использования, он остается в ожидании вызова. В отличие от других методов, `insert` не вызывается здесь. Вместо этого экземпляр вставляется в другую часть памяти, часть, отвечающую за определение возможности воссоздания экземпляра, назовем это «фабрикой». Если мы хотим создать что-то, что будет использоваться позже, это не будет смешиваться с вещами, которые использовались сейчас. И здесь вступает в силу магия `fenix`: если вы решаете оставить `fenix: false`, и ваш `smartManagement` не является `keepFactory`, то, при использовании `Get.find`, экземпляр изменит место в памяти с «фабрики» на область памяти общего экземпляра. Сразу после этого по умолчанию удаляется с «фабрики». Теперь, если вы выберете `fenix: true`, экземпляр продолжит существовать в этой выделенной части, даже перейдя в общую область, для повторного вызова в будущем.
## Подвязки
Возможно, одной из главных особенностей этого пакета является возможность полной интеграции маршрутов, менеджера состояний и менеджера зависимостей.
Когда маршрут удаляется из стека, все контроллеры, переменные и экземпляры связанных с ним объектов удаляются из памяти. Если вы используете потоки или таймеры, они закроются автоматически, и вам не о чем беспокоиться.
В версии 2.10 полностью реализован API привязок.
Теперь вам больше не нужно использовать метод инициализации. Вам даже не нужно вводить контроллеры, если вы этого не хотите. Вы можете запустить свои контроллеры и серисы в соответствующем для этого месте.
Класс Binding - это класс, который будет разделять внедрение зависимостей, при этом «привязывая» маршруты к диспетчеру состояний и диспетчеру зависимостей.
Этот класс позволяет Get узнать, какой экран отображается при использовании конкретного контроллера, а также узнать, где и как его удалить.
Кроме того, класс Binding позволит вам контролировать конфигурацию SmartManager. Вы можете настроить зависимости, которые будут упорядочены при удалении маршрута из стека, или когда виджет, который его использовал, выкладывается, или ни то, ни другое. На вас будет работать интеллектуальное управление зависимостями, но даже в этом случае вы можете настроить его по своему усмотрению.
### Класс Bindings
- Создайте класс и реализуйте Binding
```dart
class HomeBinding implements Bindings {}
```
Ваша IDE автоматически попросит вас переопределить метод «зависимостей», и вам просто нужно последовать этой просьбе, переопределить метод и вставить все классы, которые вы собираетесь использовать на этом маршруте:
```dart
class HomeBinding implements Bindings {
@override
void dependencies() {
Get.lazyPut<HomeController>(() => HomeController());
Get.put<Service>(()=> Api());
}
}
class DetailsBinding implements Bindings {
@override
void dependencies() {
Get.lazyPut<DetailsController>(() => DetailsController());
}
}
```
Теперь вам просто нужно сообщить своему маршруту, что вы будете использовать эту привязку для установления связи между диспетчером маршрутов, зависимостями и состояниями.
- Используя именованные маршруты:
```dart
getPages: [
GetPage(
name: '/',
page: () => HomeView(),
binding: HomeBinding(),
),
GetPage(
name: '/details',
page: () => DetailsView(),
binding: DetailsBinding(),
),
];
```
- Используя обычные маршруты:
```dart
Get.to(Home(), binding: HomeBinding());
Get.to(DetailsView(), binding: DetailsBinding())
```
Вам больше не нужно беспокоиться об управлении памятью вашего приложения, Get сделает это за вас.
Класс Binding вызывается при вызове маршрута, вы можете создать "initialBinding" в GetMaterialApp, чтобы вставить все зависимости, которые будут созданы.
```dart
GetMaterialApp(
initialBinding: SampleBind(),
home: Home(),
);
```
### BindingsBuilder
По умолчанию привязка создается путем создания класса, реализующего привязки.
Но в качестве альтернативы вы можете использовать обратный вызов `BindingsBuilder`, чтобы просто использовать функцию для создания всего, что вы хотите.
Example:
```dart
getPages: [
GetPage(
name: '/',
page: () => HomeView(),
binding: BindingsBuilder(() {
Get.lazyPut<ControllerX>(() => ControllerX());
Get.put<Service>(()=> Api());
}),
),
GetPage(
name: '/details',
page: () => DetailsView(),
binding: BindingsBuilder(() {
Get.lazyPut<DetailsController>(() => DetailsController());
}),
),
];
```
Таким образом, вы можете избежать создания одного класса привязки для каждого маршрута, что сделает это еще проще.
Оба способа работают идеально, и мы хотим, чтобы вы использовали то, что больше всего соответствует вашим вкусам.
### SmartManagement
GetX по умолчанию удаляет неиспользуемые контроллеры из памяти, даже если происходит сбой и виджет, который их использует, не удаляется должным образом.
Это то, что называется `полным` режимом управления зависимостями.
Но если вы хотите поменять способ, которым GetX управляет удалением классов, у вас есть класс `SmartManagement`, в котором вы можете задавать другое поведение.
#### Как поменять
Если вы хотите поменять эту конфигурацию (что обычно не требуется), вот способ:
```dart
void main () {
runApp(
GetMaterialApp(
smartManagement: SmartManagement.onlyBuilders //here
home: Home(),
)
)
}
```
#### SmartManagement.full
Это значение по умолчанию. Удаляет классы, которые не используются и не были постоянными. В большинстве случаев вы захотите оставить эту конфигурацию нетронутой. Если вы новичок в GetX, не меняйте это.
#### SmartManagement.onlyBuilders
С этой опцией будут удалены только контроллеры, запущенные в `init:` или загруженные в Binding с помощью `Get.lazyPut()`.
Если вы используете `Get.put()` или `Get.putAsync()` или любой другой подход, SmartManagement не будет иметь разрешений на исключение этой зависимости.
При поведении по умолчанию даже виджеты, созданные с помощью Get.put, будут удалены, в отличие от SmartManagement.onlyBuilders.
#### SmartManagement.keepFactory
Как и SmartManagement.full, он удаляет зависимости, когда он больше не используется. Однако он сохранит свою фабрику, что означает, что он воссоздает зависимость, если вам снова понадобится этот экземпляр.
### Как подвязки работают под капотом
Привязки создают временные фабрики, которые создаются в тот момент, когда вы кликаете для перехода на другой экран, и будут уничтожены, как только произойдет анимация смены экрана.
Это происходит так быстро, что анализатор даже не сможет это зарегистрировать.
Когда вы снова перейдете на этот экран, будет вызвана новая временная фабрика, поэтому это предпочтительнее, чем использование SmartManagement.keepFactory, но если вы не хотите создавать привязки или хотите сохранить все свои зависимости в одной привязке, это вам поможет.
Фабрики занимают мало памяти, они содержат не экземпляры, а функцию с «формой» того класса, который вам нужен.
Это имеет очень низкую стоимость памяти, но поскольку цель этой библиотеки - получить максимально возможную производительность с использованием минимальных ресурсов, Get по умолчанию удаляет даже фабрики. Используйте то, что вам удобнее.
## Примечания
- НЕ ИСПОЛЬЗУЙТЕ SmartManagement.keepFactory, если вы используете несколько привязок. Он был разработан для использования без привязок или с одной привязкой, связанной в initialBinding GetMaterialApp.
- Использование привязок совершенно необязательно, если вы хотите, вы можете без проблем использовать `Get.put()` и `Get.find()` для классов, которые используют данный контроллер.
Однако, если вы работаете со службами или любой другой абстракцией, я рекомендую использовать привязки для лучшей организации.

View File

@@ -0,0 +1,563 @@
- [Управление маршрутами](#управление-маршрутами)
- [Как использовать](#как-использовать)
- [Навигация без именованных маршрутов](#навигация-без-именованных-маршрутов)
- [Навигация по именованным маршрутам](#навигация-по-именованным-маршрутам)
- [Отправить данные по именованному маршруту](#отправить-данные-по-именованному-маршруту)
- [Динамические url-ссылки](#динамические-url-ссылки)
- [Middleware](#middleware)
- [Навигация без контекста](#навигация-без-контекста)
- [SnackBars](#snackbars)
- [Dialogs](#dialogs)
- [BottomSheets](#bottomsheets)
- [Вложенная навигация](#вложенная-навигация)
# Управление маршрутами
Это полное объяснение всего, что нужно Getx, когда речь идет об управлении маршрутами.
## Как использовать
Добавьте к вашему pubspec.yaml следующее:
```yaml
dependencies:
get:
```
Если вы собираетесь использовать маршруты/snackbars/dialogs/bottomSheets без контекста или использовать высокоуровневые API Get, вам нужно просто добавить «Get» перед вашим MaterialApp, превратив его в GetMaterialApp!
```dart
GetMaterialApp( // Before: MaterialApp(
home: MyHome(),
)
```
## Навигация без именованных маршрутов
Для навигации на новый экран:
```dart
Get.to(NextScreen());
```
Чтобы закрыть snackbars, диалог, bottomsheets, или все, что вы обычно закрывали с помощью Navigator.pop(context);
```dart
Get.back();
```
Для перехода к следующему экрану и отсутствия возможности вернуться к предыдущему экрану (для использования в SplashScreens, экранах входа и т.д.)
```dart
Get.off(NextScreen());
```
Для перехода к следующему экрану и отмены всех предыдущих маршрутов (полезно в корзинах для покупок, опросах и тестах)
```dart
Get.offAll(NextScreen());
```
Чтобы перейти к следующему маршруту и ​​получить или обновить данные, как только вы вернетесь с него:
```dart
var data = await Get.to(Payment());
```
отправить данные на предыдущий экран:
```dart
Get.back(result: 'success');
```
И использовать их:
пример:
```dart
if(data == 'success') madeAnything();
```
Нет желания учить наш синтаксис?
Просто измените Navigator(верхний регистр) на navigator (нижний регистр), и вы получите все функции стандартной навигации без использования контекста.
Пример:
```dart
// Default Flutter navigator
Navigator.of(context).push(
context,
MaterialPageRoute(
builder: (BuildContext context) {
return HomePage();
},
),
);
// Get using Flutter syntax without needing context
navigator.push(
MaterialPageRoute(
builder: (_) {
return HomePage();
},
),
);
// Get syntax (It is much better, but you have the right to disagree)
Get.to(HomePage());
```
## Навигация по именованным маршрутам
- Если вы предпочитаете перемещаться по именованным маршрутам, Get также поддерживает это.
Для навигации на следующий экран
```dart
Get.toNamed("/NextScreen");
```
Для навигации и удаления предыдущего экрана из дерева.
```dart
Get.offNamed("/NextScreen");
```
Для навигации и удаления всех предыдущих экранов из дерева.
```dart
Get.offAllNamed("/NextScreen");
```
Для определения маршрутов используйте GetMaterialApp:
```dart
void main() {
runApp(
GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => MyHomePage()),
GetPage(name: '/second', page: () => Second()),
GetPage(
name: '/third',
page: () => Third(),
transition: Transition.zoom
),
],
)
);
}
```
Для обработки навигации по неопределенным маршрутам (ошибка 404) вы можете определить страницу unknownRoute в GetMaterialApp.
```dart
void main() {
runApp(
GetMaterialApp(
unknownRoute: GetPage(name: '/notfound', page: () => UnknownRoutePage()),
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => MyHomePage()),
GetPage(name: '/second', page: () => Second()),
],
)
);
}
```
### Отправить данные по именованному маршруту
Просто отправьте то, что хотите в качестве аргументов. Get принимает здесь всё, что угодно, будь то String, Map, List или даже экземпляр класса.
```dart
Get.toNamed("/NextScreen", arguments: 'Get is the best');
```
в вашем классе или контроллере:
```dart
print(Get.arguments);
//print out: Get is the best
```
### Динамические url-ссылки
Получите расширенные динамические url-адреса, как в Интернете. Веб-разработчики, вероятно, уже хотели эту функцию на Flutter, и, скорее всего, видели, что пакет обещает эту функцию и предоставляет совершенно другой синтаксис, чем url-адрес в Интернете, но Get также решает эту проблему.
```dart
Get.offAllNamed("/NextScreen?device=phone&id=354&name=Enzo");
```
в вашем controller/bloc/stateful/stateless классе:
```dart
print(Get.parameters['id']);
// out: 354
print(Get.parameters['name']);
// out: Enzo
```
Вы также можете легко получить именованные параметры с помощью Get:
```dart
void main() {
runApp(
GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(
name: '/',
page: () => MyHomePage(),
),
GetPage(
name: '/profile/',
page: () => MyProfile(),
),
//You can define a different page for routes with arguments, and another without arguments, but for that you must use the slash '/' on the route that will not receive arguments as above.
GetPage(
name: '/profile/:user',
page: () => UserProfile(),
),
GetPage(
name: '/third',
page: () => Third(),
transition: Transition.cupertino
),
],
)
);
}
```
Отправьте данные по именованному маршруту
```dart
Get.toNamed("/profile/34954");
```
На втором экране возьмите данные по параметрам
```dart
print(Get.parameters['user']);
// out: 34954
```
или отправьте несколько таких параметров
```dart
Get.toNamed("/profile/34954?flag=true");
```
На втором экране взять данные по параметрам как обычно.
```dart
print(Get.parameters['user']);
print(Get.parameters['flag']);
// out: 34954 true
```
И теперь все, что вам нужно сделать, это использовать Get.toNamed() для навигации по именованным маршрутам без какого-либо контекста (вы можете вызывать свои маршруты непосредственно из класса BLoC или контроллера), а когда ваше приложение будет скомпилировано в Интернете, ваше маршруты появятся в url <3
### Middleware
Если вы хотите прослушивать события Get для запуска действий, вы можете использовать для этого routingCallback
```dart
GetMaterialApp(
routingCallback: (routing) {
if(routing.current == '/second'){
openAds();
}
}
)
```
Если вы не используете GetMaterialApp, вы можете использовать ручной API для подключения наблюдателя.
```dart
void main() {
runApp(
MaterialApp(
onGenerateRoute: Router.generateRoute,
initialRoute: "/",
navigatorKey: Get.key,
navigatorObservers: [
GetObserver(MiddleWare.observer), // HERE !!!
],
),
);
}
```
Создайте класс MiddleWare
```dart
class MiddleWare {
static observer(Routing routing) {
/// You can listen in addition to the routes, the snackbars, dialogs and bottomsheets on each screen.
///If you need to enter any of these 3 events directly here,
///you must specify that the event is != Than you are trying to do.
if (routing.current == '/second' && !routing.isSnackbar) {
Get.snackbar("Hi", "You are on second route");
} else if (routing.current =='/third'){
print('last route called');
}
}
}
```
Теперь используйте Get в своем коде:
```dart
class First extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.add),
onPressed: () {
Get.snackbar("hi", "i am a modern snackbar");
},
),
title: Text('First Route'),
),
body: Center(
child: ElevatedButton(
child: Text('Open route'),
onPressed: () {
Get.toNamed("/second");
},
),
),
);
}
}
class Second extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.add),
onPressed: () {
Get.snackbar("hi", "i am a modern snackbar");
},
),
title: Text('second Route'),
),
body: Center(
child: ElevatedButton(
child: Text('Open route'),
onPressed: () {
Get.toNamed("/third");
},
),
),
);
}
}
class Third extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Third Route"),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Get.back();
},
child: Text('Go back!'),
),
),
);
}
}
```
## Навигация без контекста
### SnackBars
Чтобы получить простой SnackBar с Flutter, вы должны получить контекст Scaffold, или вы должны использовать GlobalKey, прикрепленный к вашему Scaffold
```dart
final snackBar = SnackBar(
content: Text('Hi!'),
action: SnackBarAction(
label: 'I am a old and ugly snackbar :(',
onPressed: (){}
),
);
// Find the Scaffold in the widget tree and use
// it to show a SnackBar.
Scaffold.of(context).showSnackBar(snackBar);
```
Реализация в Get:
```dart
Get.snackbar('Hi', 'i am a modern snackbar');
```
С Get всё, что вам нужно сделать, это вызвать Get.snackbar из любого места кода или настроить его так, как вы хотите!
```dart
Get.snackbar(
"Hey i'm a Get SnackBar!", // title
"It's unbelievable! I'm using SnackBar without context, without boilerplate, without Scaffold, it is something truly amazing!", // message
icon: Icon(Icons.alarm),
shouldIconPulse: true,
onTap:(){},
barBlur: 20,
isDismissible: true,
duration: Duration(seconds: 3),
);
////////// ALL FEATURES //////////
// Color colorText,
// Duration duration,
// SnackPosition snackPosition,
// Widget titleText,
// Widget messageText,
// bool instantInit,
// Widget icon,
// bool shouldIconPulse,
// double maxWidth,
// EdgeInsets margin,
// EdgeInsets padding,
// double borderRadius,
// Color borderColor,
// double borderWidth,
// Color backgroundColor,
// Color leftBarIndicatorColor,
// List<BoxShadow> boxShadows,
// Gradient backgroundGradient,
// TextButton mainButton,
// OnTap onTap,
// bool isDismissible,
// bool showProgressIndicator,
// AnimationController progressIndicatorController,
// Color progressIndicatorBackgroundColor,
// Animation<Color> progressIndicatorValueColor,
// SnackStyle snackStyle,
// Curve forwardAnimationCurve,
// Curve reverseAnimationCurve,
// Duration animationDuration,
// double barBlur,
// double overlayBlur,
// Color overlayColor,
// Form userInputForm
///////////////////////////////////
```
Если вы предпочитаете традиционный snackbar, или хотите настроить его с нуля, вы можете использовать
`Get.rawSnackbar();` который предоставляет RAW API, на котором был построен Get.snackbar.
### Dialogs
Чтобы открыть:
```dart
Get.dialog(YourDialogWidget());
```
Чтобы открыть диалог по умолчанию:
```dart
Get.defaultDialog(
onConfirm: () => print("Ok"),
middleText: "Dialog made in 3 lines of code"
);
```
Вы также можете использовать Get.generalDialog вместо showGeneralDialog.
Для всех других виджетов диалога Flutter, включая cupertino, вы можете использовать Get.overlayContext вместо контекста и открывать его в любом месте вашего кода.
Для виджетов, которые не используют Overlay, вы можете использовать Get.context.
Эти два контекста будут работать в 99% случаев для замены контекста вашего пользовательского интерфейса, за исключением случаев, когда наследуемый виджет используется без контекста навигации.
### BottomSheets
Get.bottomSheet похож на showModalBottomSheet, но не требует контекста.
```dart
Get.bottomSheet(
Container(
child: Wrap(
children: <Widget>[
ListTile(
leading: Icon(Icons.music_note),
title: Text('Music'),
onTap: () {}
),
ListTile(
leading: Icon(Icons.videocam),
title: Text('Video'),
onTap: () {},
),
],
),
)
);
```
## Вложенная навигация
Get сделал вложенную навигацию Flutter еще проще.
Вам не нужен контекст, и вы найдёте свой стек навигации по Id.
- ПРИМЕЧАНИЕ: Создание параллельных стеков навигации может быть опасным. В идеале не используйте NestedNavigators или используйте их редко. Если этого требует ваш проект, продолжайте, но имейте в виду, что хранение нескольких стеков навигации в памяти может быть не лучшим решением для потребления оперативной памяти.
Смотрите как это просто:
```dart
Navigator(
key: Get.nestedKey(1), // create a key by index
initialRoute: '/',
onGenerateRoute: (settings) {
if (settings.name == '/') {
return GetPageRoute(
page: () => Scaffold(
appBar: AppBar(
title: Text("Main"),
),
body: Center(
child: TextButton(
color: Colors.blue,
onPressed: () {
Get.toNamed('/second', id:1); // navigate by your nested route by index
},
child: Text("Go to second"),
),
),
),
);
} else if (settings.name == '/second') {
return GetPageRoute(
page: () => Center(
child: Scaffold(
appBar: AppBar(
title: Text("Main"),
),
body: Center(
child: Text("second")
),
),
),
);
}
}
),
```

View File

@@ -0,0 +1,732 @@
- [Управление состоянием](#управление-состоянием)
- [Реактивное управление состоянием](#реактивное-управление-состоянием)
- [Преимущества](#преимущества)
- [Объявление реактивной переменной](#объявление-реактивной-переменной)
- [Использование значений в представлении](#использование-значений-в-представлении)
- [Условия для перестраивания](#условия-для-перестраивания)
- [Где .obs может быть использован](#где-obs-может-быть-использован)
- [Примечание о списках](#примечание-о-списках)
- [Почему мне нужно использовать .value](#почему-мне-нужно-использовать-value)
- [Obx()](#obx)
- [Workers](#workers)
- [Обычное управление состоянием](#обычное-управление-состоянием)
- [Преимущества](#преимущества-1)
- [Использование](#использование)
- [Как обрабатываются контроллеры](#как-обрабатываются-контроллеры)
- [Вам больше не понадобятся StatefulWidgets](#вам-больше-не-понадобятся-statefulwidgets)
- [Почему это существует](#почему-это-существует)
- [Другие способы использования](#другие-способы-использования)
- [Уникальные идентификаторы](#уникальные-идентификаторы)
- [Смешивание двух менеджеров состояний](#смешивание-двух-менеджеров-состояний)
- [GetBuilder vs GetX vs Obx vs MixinBuilder](#getbuilder-vs-getx-vs-obx-vs-mixinbuilder)
# Управление состоянием
В настоящее время для Flutter есть несколько менеджеров состояний. Однако большинство из них связано с использованием ChangeNotifier для обновления виджетов, и это плохой и очень плохой подход к производительности средних или больших приложений. Вы можете проверить в официальной документации Flutter, что [ChangeNotifier следует использовать с 1 или максимум 2 слушателями](https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html), что делает его практически непригодным для любого приложения среднего или большого размера.
Остальные менеджеры состояний хороши, но есть свои нюансы:
- BLoC безопасен и эффективен, но сложен для новичков, что удерживает людей от разработки с Flutter.
- MobX проще, чем BLoC, реактивен, и я бы сказал, почти идеален, но вам нужно использовать генератор кода, который для больших приложений снижает производительность, таким образом вам нужно будет пить много кофе, прежде чем ваш код снова не будет готов после flutter clean (И это не вина MobX, а вина кодогенерации, которая очень медленная!).
- Provider использует InheritedWidget для доставки слушателя в качестве способа решения проблемы, описанной выше, с помощью ChangeNotifier, что подразумевает, что любой доступ к классу ChangeNotifier должен находиться в дереве виджетов из-за контекста для доступа к Inherited.
Get не лучше и не хуже, чем любой другой менеджер состояний, но вам следует проанализировать эти моменты, а также приведенные ниже пункты, чтобы выбрать между использованием Get в чистом виде (Vanilla) или его вместе с другим менеджером состояний. Определенно, Get - не враг любого другого менеджера состояний, потому что Get - это микрофреймворк, а не просто менеджер состояний, и его можно использовать отдельно или вместе с ними.
## Реактивное управление состоянием
Реактивное программирование может оттолкнуть многих людей, потому что считается сложным. GetX превращает реактивное программирование в нечто довольно простое:
- Вам не нужно создавать StreamControllers.
- Вам не нужно создавать StreamBuilder для каждой переменной.
- Вам не нужно создавать класс для каждого состояния.
- Вам не нужно создавать получение начального значения.
Реактивное программирование с помощью Get так же просто, как использование setState.
Представим, что у вас есть переменная `name` и вы хотите, чтобы каждый раз, когда вы её изменяете, все виджеты, которые её используют, менялись автоматически.
Это ваша переменная:
```dart
var name = 'Jonatas Borges';
```
Чтобы сделать его наблюдаемым, вам просто нужно добавить в конец «.obs»:
```dart
var name = 'Jonatas Borges'.obs;
```
Вот и всё. Это *так* просто.
С этого момента мы могли бы называть эти - ".obs" (ervables) переменные как _Rx_.
Что мы делали под капотом? Мы создали `Stream` из `String`ов, которому было присвоено начальное значение `"Jonatas Borges"`, мы уведомили все виджеты, которые используют `"Jonatas Borges"`, что они теперь «принадлежат» этой переменной, и когда значение _Rx_ изменится, они также должны будут измениться.
Это **волшебство GetX** возможно, благодаря возможностям Dart.
Но, как мы знаем, виджет можно изменить только в том случае, если он находится внутри функции, потому что статические классы не имеют права «автоматически изменяться».
Вам нужно будет создать `StreamBuilder`, подписаться на эту переменную, чтобы отслеживать изменения, и создать «каскад» вложенных `StreamBuilder`, если вы хотите изменить несколько переменных в одной области, верно?
Нет, вам не нужен `StreamBuilder`, но насчёт статических классов вы правы.
Что ж, в представлении во Flutter, когда мы хотим изменить конкретный виджет, приходится писать много шаблоного кода.
C **GetX** вы можете забыть о шаблонном коде.
`StreamBuilder( … )`? `initialValue: …`? `builder: …`? Нет, вам просто нужно поместить эту переменную в виджет `Obx()`.
```dart
Obx (() => Text (controller.name));
```
_Что нужно запомнить?_ Только `Obx(() =>`.
Вы просто передаёте этот виджет через стрелочную функцию в `Obx()` ("Observer" в _Rx_).
`Obx` довольно умён и изменится только при изменении значения `controller.name`.
Если `name` == `"John"`, и вы измените его на `"John"` (`name.value = "John"`), на экране ничего не изменится, так как это то же значение, что и раньше. `Obx` для экономии ресурсов просто проигнорирует новое значение, а не будет перестраивать виджет. **Разве это не потрясающе?**
> Итак, что, если у меня есть 5 переменных _Rx_ (observable) в `Obx`?
Он просто обновится, когда **любой** из них изменится.
> И если у меня есть 30 переменных в классе, когда я обновлю одну, обновятся ли **все** переменные этого класса?
Нет, только **конкретный виджет**, который использует эту переменную _Rx_.
Итак, **GetX** обновляет экран только тогда, когда переменная _Rx_ меняет свое значение.
```
final isOpen = false.obs;
// NOTHING will happen... same value.
void onButtonTap() => isOpen.value=false;
```
### Преимущества
**GetX()** поможет вам, когда вам нужен **детальный** контроль над тем, что обновляется.
Если вам не нужны уникальные идентификаторы, из-за того что все ваши переменные будут изменены при выполнении, используйте GetBuilder, потому что это простой модуль обновления состояния (как `setState()`), написанный всего в несколько строк кода.
Он был сделан простым, чтобы иметь наименьшее влияние на CPU и просто выполнять единственную цель (восстановление состояния), тратя минимально возможные ресурсы.
Если вам нужен **мощный** менеджер состояний, то вашим выбором будет **GetX**.
Он не работает с переменными, а работает с потоками, все в нем - это `Streams` под капотом.
Вы можете использовать _rxDart_ вместе с ним, потому что все это `Streams`,
вы можете прослушивать событие каждой «переменной _Rx_», потому что всё в нём - это `Streams`.
Это буквально подход _BLoC_, который проще, чем _MobX_, и без генераторов кода и тд.
Вы можете превратить что угодно в _"Observable"_ с помощью `.obs`.
### Максимальная производительность:
В дополнение к интеллектуальному алгоритму минимальных перестроек, **GetX** использует компараторы, чтобы убедиться, что состояние изменилось.
Если вы столкнетесь с ошибками в своем приложении и отправите дублирующее изменение состояния,
**GetX** гарантирует, что оно не выйдет из строя.
С **GetX** состояние изменяется только при изменении значения.
В этом основное отличие между **GetX** и применением _`computed` из MobX_.
При объединении двух __observables__, когда один из них изменяется; слушатель этого _observable_ также изменится.
В **GetX**, если вы объедините две переменные, `GetX()` (аналогично `Observer()`) будет перестраиваться только в том случае, если это подразумевает реальное изменение состояния.
### Объявление реактивной переменной
У вас есть 3 способа превратить переменную в "observable".
1 - Первый использует **`Rx{Type}`**.
```dart
// initial value is recommended, but not mandatory
final name = RxString('');
final isLogged = RxBool(false);
final count = RxInt(0);
final balance = RxDouble(0.0);
final items = RxList<String>([]);
final myMap = RxMap<String, int>({});
```
2 - Второй - использовать **`Rx`** и дженерики `Rx<Type>`
```dart
final name = Rx<String>('');
final isLogged = Rx<Bool>(false);
final count = Rx<Int>(0);
final balance = Rx<Double>(0.0);
final number = Rx<Num>(0)
final items = Rx<List<String>>([]);
final myMap = Rx<Map<String, int>>({});
// Custom classes - it can be any class, literally
final user = Rx<User>();
```
3 - Третий, более практичный, простой и предпочтительный подход, просто добавьте **`.obs`** в качестве свойства вашего значения:
```dart
final name = ''.obs;
final isLogged = false.obs;
final count = 0.obs;
final balance = 0.0.obs;
final number = 0.obs;
final items = <String>[].obs;
final myMap = <String, int>{}.obs;
// Custom classes - it can be any class, literally
final user = User().obs;
```
##### Реактивные состояния - это просто.
Как мы знаем, _Dart_ сейчас движется в сторону _null safety_.
Чтобы быть готовым, с этого момента вы всегда должны начинать свои переменные _Rx_ с **начальным значением**.
> Преобразование переменной в _observable_ + _начальное значение_ c **GetX** - самый простой и практичный подход.
Вы буквально добавите "`.obs`" в конец своей переменной и **всё**, вы сделали её observable,
и её `.value`, будет начальным значением.
### Использование значений в представлении
```dart
// controller file
final count1 = 0.obs;
final count2 = 0.obs;
int get sum => count1.value + count2.value;
```
```dart
// view file
GetX<Controller>(
builder: (controller) {
print("count 1 rebuild");
return Text('${controller.count1.value}');
},
),
GetX<Controller>(
builder: (controller) {
print("count 2 rebuild");
return Text('${controller.count2.value}');
},
),
GetX<Controller>(
builder: (controller) {
print("count 3 rebuild");
return Text('${controller.sum}');
},
),
```
Если мы увеличим `count1.value++`, он выведет:
- `count 1 rebuild`
- `count 3 rebuild`
поскольку `count1` имеет значение `1`, а `1 + 0 = 1`, изменяет геттер `sum`.
Если мы изменим `count2.value++`, он выведет:
- `count 2 rebuild`
- `count 3 rebuild`
так как `count2.value` изменился, и теперь `sum` равен `2`.
- Примечание: По умолчанию самое первое событие перестраивает виджет, даже если это то же значение.
Такое поведение существует из-за Boolean переменных.
Представьте, что вы сделали это:
```dart
var isLogged = false.obs;
```
А затем вы проверили, вошел ли пользователь в систему, чтобы вызвать событие в `ever`.
```dart
@override
onInit(){
ever(isLogged, fireRoute);
isLogged.value = await Preferences.hasToken();
}
fireRoute(logged) {
if (logged) {
Get.off(Home());
} else {
Get.off(Login());
}
}
```
Если `hasToken` был `false`, `isLogged` не изменится, поэтому `ever()` никогде не будет вызван.
Чтобы избежать такого поведения, первое изменение _observable_ всегда будет запускать событие, даже если оно содержит то же самое `.value`.
Вы можете убран данное поведение, если хотите, используя:
`isLogged.firstRebuild = false;`
### Условия для перестраивания
Кроме того, Get обеспечивает усовершенствованный контроль состояния. Вы можете обусловить событие (например, добавление объекта в список) определенным условием.
```dart
// First parameter: condition, must return true of false
// Second parameter: the new value to aplly if the condition is true
list.addIf(item < limit, item);
```
Без украшений, без генератора кода, без сложностей :smile:
Вы ведь знаете счётчик Flutter? Ваш класс контроллера может выглядеть так:
```dart
class CountController extends GetxController {
final count = 0.obs;
}
```
С простым:
```dart
controller.count.value++
```
Вы можете обновить переменную счетчика в своем пользовательском интерфейсе, независимо от того, где она хранится.
### Где .obs может быть использован
Вы можете преобразовать что угодно в obs. Вот два способа сделать это:
* Вы можете преобразовать значения вашего класса в obs
```dart
class RxUser {
final name = "Camila".obs;
final age = 18.obs;
}
```
* или вы можете преобразовать весь класс в observable
```dart
class User {
User({String name, int age});
var name;
var age;
}
// when instantianting:
final user = User(name: "Camila", age: 18).obs;
```
### Примечание о списках
Списки полностью наблюдаемы, как и объекты внутри них. Таким образом, если вы добавите значение в список, он автоматически перестроит виджеты, которые его используют.
Вам также не нужно использовать ".value" со списками, замечательный API-интерфейс Dart позволяет нам избежать этого.
К сожалению, примитивные типы, такие как String и int, не могут быть расширены, что делает использование .value обязательным, но это не будет проблемой, если вы работаете с геттерами и сеттерами для них.
```dart
// On the controller
final String title = 'User Info:'.obs
final list = List<User>().obs;
// on the view
Text(controller.title.value), // String need to have .value in front of it
ListView.builder (
itemCount: controller.list.length // lists don't need it
)
```
Когда вы делаете свои собственные классы наблюдаемыми, есть другой способ их обновить:
```dart
// on the model file
// we are going to make the entire class observable instead of each attribute
class User() {
User({this.name = '', this.age = 0});
String name;
int age;
}
// on the controller file
final user = User().obs;
// when you need to update the user variable:
user.update( (user) { // this parameter is the class itself that you want to update
user.name = 'Jonny';
user.age = 18;
});
// an alternative way of update the user variable:
user(User(name: 'João', age: 35));
// on view:
Obx(()=> Text("Name ${user.value.name}: Age: ${user.value.age}"))
// you can also access the model values without the .value:
user().name; // notice that is the user variable, not the class (variable has lowercase u)
```
Вам не нужно работать с наборами, если вы этого не хотите, вы можете использовать API "assign" и "assignAll".
API "assign" очистит ваш список и добавит один объект, который вы хотите начать там.
API "assignAll" очистит существующий список и добавит любые повторяемые объекты, которые вы в него вставляете.
### Почему мне нужно использовать .value
Мы могли бы убрать обязательство использовать 'value' для `String` и `int` с помощью простого оформления и генератора кода, но цель этой библиотеки как раз и состоит в том, чтобы избежать внешних зависимостей. Мы хотим предложить среду, готовую для программирования, включающую в себя самое необходимое (управление маршрутами, зависимостями и состояниями) простым, легким и производительным способом без необходимости во внешнем пакете.
Вы можете буквально добавить 3 буквы в свой pubspec (get), двоеточие и начать программировать. Все решения, включенные по умолчанию, от управления маршрутами до управления состоянием, нацелены на простоту, продуктивность и производительность.
Общий вес этой библиотеки меньше, чем у одного менеджера состояний, хотя это полное решение, и это то, что вы должны понимать.
Если вас беспокоит `.value`, и вам нравится генератор кода, MobX - отличная альтернатива, и вы можете использовать его вместе с Get. Для тех, кто хочет добавить одну зависимость в pubspec и начать программировать, не беспокоясь ни о совместимости версий пакетов, ни об ошибках обновления состояния исходящих от менеджеров состояний или зависимостей и не хочет беспокоиться о доступности контроллеров, а «просто программирование», Get идеален.
Если у вас нет проблем с MobX или BLoC, вы можете просто использовать Get для маршрутов и забыть о том, что у него есть менеджер состояний. Простой и реактивный менеджеры состояний Get появились из-за то, что в моей компании был проект с более чем 90 контроллерами, а генератору кода после flutter clean требовалось более 30 минут для выполнения своих задач на достаточно хорошей машине. Если у вас 5, 10, 15 контроллеров, то вам подойдёт любой менеджер состояний. Если у вас абсурдно большой проект и генератор кода является для вас проблемой, то решение прямо перед вами.
Очевидно, что если кто-то хочет внести свой вклад в проект и создать генератор кода или что-то подобное, я укажу об этом в readme в качестве альтернативы. Моя потребность не в востребованности для всех разработчиков, я лишь говорю, что есть хорошие решения, которые уже делают это, например, MobX.
### Obx()
Bindings в Get необязательны. Вы можете использовать виджет Obx вместо GetX, который получает только анонимную функцию, создающую виджет.
Очевидно, что если вы не используете тип, вам потребуется экземпляр вашего контроллера для использования переменных или использовать `Get.find<Controller>()`.value или Controller.to.value для получения значения.
### Workers
Workers помогут вам, инициируя определенные обратные вызовы при возникновении события.
```dart
/// Called every time `count1` changes.
ever(count1, (_) => print("$_ has been changed"));
/// Called only first time the variable $_ is changed
once(count1, (_) => print("$_ was changed once"));
/// Anti DDos - Called every time the user stops typing for 1 second, for example.
debounce(count1, (_) => print("debouce$_"), time: Duration(seconds: 1));
/// Ignore all changes within 1 second.
interval(count1, (_) => print("interval $_"), time: Duration(seconds: 1));
```
У всех workers (кроме `debounce`) есть именованный параметр `condition`, которое может быть `bool` или обратным вызовом возвращающим `bool`.
Этот `condition` определяет, когда выполняется функция обратного вызова.
Все workers возвращают экземпляр `Worker`, который можно использовать для отмены (с помощью метода `dispose()`) worker.
- **`ever`**
вызывается каждый раз, когда переменная _Rx_ выдает новое значение.
- **`everAll`**
как `ever`, но он принимает `List` значений _Rx_ вызываемый каждый раз, когда его переменная изменяется. Вот и всё.
- **`once`**
'once' вызывается только при первом изменении переменной.
- **`debounce`**
'debounce' очень полезен в функциях поиска, где вы хотите, чтобы API вызывался только тогда, когда пользователь заканчивает ввод. Если пользователь вводит "Jonny", у вас будет 5 поисковых запросов в API по буквам J, o, n, n и y. С Get этого не происходит, потому что у вас будет "debounce" Worker, который будет запускаться только в конце набора.
- **`interval`**
'interval' отличается от `debouce`. В `debouce`, если пользователь внесёт 1000 изменений в переменную в течение 1 секунды, он отправит только последнее после установленного таймера (по умолчанию 800 миллисекунд). Вместо этого Interval будет игнорировать все действия пользователя в течение указанного периода. Если вы отправляете события в течение 1 минуты, 1000 в секунду, debounce отправит вам только последнее, когда пользователь прекратит стрелять событиями. Interval будет доставлять события каждую секунду, а если установлен на 3 секунды, то он будет доставлять 20 событий в эту минуту. Это рекомендуется во избежание злоупотреблений в функциях, где пользователь может быстро щелкнуть что-либо и получить некоторое преимущество (представьте, что пользователь может зарабатывать монеты, щелкая что-либо, если он щелкнет 300 раз за ту же минуту, у него будет 300 монет, используя интервал, вы можете установить временной интервал на 3 секунды, и даже если щелкнуть 300 или тысячу раз, максимум, который он получит за 1 минуту, составит 20 монет, щелкнув 300 или 1 миллион раз). Debounce подходит для защиты от DDos, для таких функций, как поиск, где каждое изменение onChange будет вызывать запрос к вашему API. Debounce будет ждать, пока пользователь перестанет вводить имя, чтобы сделать запрос. Если бы он использовался в вышеупомянутом сценарии с монетами, пользователь накликал бы только 1 монету, потому что он выполняется только тогда, когда пользователь "делает паузу" на установленное время.
- ПРИМЕЧАНИЕ: должны всегда использоваться при запуске контроллера или класса, поэтому он всегда должен быть в onInit (рекомендуется), в конструкторе класса или в initState StatefulWidget (в большинстве случаев эта практика не рекомендуется, но не должна иметь побочных эффектов).
## Обычное управление состоянием
Get имеет чрезвычайно легкий и простой менеджер состояний, который не использует ChangeNotifier, удовлетворит потребности, особенно для тех, кто плохо знаком с Flutter, и не вызовет проблем для больших приложений.
GetBuilder нацелен именно на контроль нескольких состояний. Представьте, что вы добавили 30 продуктов в корзину, вы нажимаете удалить один, одновременно с этим обновляется список, обновляется цена, а значок в корзине покупок обновляется до меньшего числа. Такой подход делает GetBuilder убийственным, потому что он группирует состояния и изменяет их все сразу без какой-либо "вычислительной логики" для этого. GetBuilder был создан с учётом такого рода ситуаций, поскольку для временного изменения состояния вы можете использовать setState, и для этого вам не понадобится менеджер состояний.
Таким образом, если вам нужен отдельный контроллер, вы можете назначить для него идентификаторы или использовать GetX. Это зависит от вас, помните, что чем больше у вас «индивидуальных» виджетов, тем больше ресурсов будет забирать GetX, в то время как производительность GetBuilder должна быть выше при многократном изменении состояния.
### Преимущества
1. Обновляйте только необходимые виджеты.
2. Не используйте changeNotifier, это менеджер состояний, который использует меньше памяти (около 0 МБ).
3. Забудьте о StatefulWidget! С Get он больше не понадобится. С другими менеджерами состояний вам, вероятно, придется использовать StatefulWidget, чтобы получить экземпляр вашего Provider, BLoC, MobX Controller и т.д. Но задумывались ли вы когда-нибудь о том, что ваш AppBar, Scaffold и большинство виджетов в вашем классе не имеют состояния и по сути являются Stateless? Так зачем хранить состояние всего класса, если можно хранить только состояние виджета, которые истинно Stateful? Get решает и эту проблему. Создавайте классы Stateless, всё делайте stateless. Если вам нужно обновить один компонент, просто оберните его GetBuilder.
4. Организуйте свой проект по-настоящему! Контроллеры не должны быть в вашем пользовательском интерфейсе, поместите ваш TextEditController или любой контроллер, который вы используете, в свой класс Controller.
5. Вам нужно инициировать событие для обновления виджета, как только он будет отрисован? GetBuilder имеет свойство initState, как и StatefulWidget, и вы можете вызывать события вашего контроллера прямо из него.
6. Вам необходимо инициировать такие действия как закрытия потоков, таймеров и т.д.? GetBuilder также имеет свойство dispose, с помощью которого вы можете вызывать события, как только этот виджет будет уничтожен.
7. Используйте потоки только при необходимости. Вы можете использовать свои StreamControllers внутри своего контроллера в обычном режиме, а также использовать StreamBuilder как обычно, но помните, что поток разумно потребляет память и реактивное программирование - это прекрасно, но вы не должны злоупотреблять этим. 30 потоков, открытых одновременно, может быть хуже, чем changeNotifier (а changeNotifier - очень плохо).
8. Обновляйте виджеты, не тратя на это оперативную память. Get сохраняет только идентификатор создателя GetBuilder и обновляет этот GetBuilder при необходимости. Потребление памяти для хранения идентификатора get в памяти очень низкое даже для тысяч GetBuilders. Когда вы создаете новый GetBuilder, вы фактически передаёте состояние GetBuilder, у которого есть идентификатор создателя. Новое состояние не создается для каждого GetBuilder, что экономит МНОГО ОЗУ для больших приложений. В основном ваше приложение будет полностью Stateless, и несколько виджетов, которые Stateful (при помощи GetBuilder), будут иметь общее состояние, и поэтому обновление обного обновит их всех. Состояние всего одно.
9. Get - всеведущий и в большинстве случаев точно знает, в какое время нужно извлечь контроллер из памяти. Вам не следует беспокоиться о том, когда утилизировать контроллер, Get знает, когда это сделать.
### Использование
```dart
// Create controller class and extends GetxController
class Controller extends GetxController {
int counter = 0;
void increment() {
counter++;
update(); // use update() to update counter variable on UI when increment be called
}
}
// On your Stateless/Stateful class, use GetBuilder to update Text when increment be called
GetBuilder<Controller>(
init: Controller(), // INIT IT ONLY THE FIRST TIME
builder: (_) => Text(
'${_.counter}',
),
)
//Initialize your controller only the first time. The second time you are using ReBuilder for the same controller, do not use it again. Your controller will be automatically removed from memory as soon as the widget that marked it as 'init' is deployed. You don't have to worry about that, Get will do it automatically, just make sure you don't start the same controller twice.
```
**Готово!**
- Вы уже узнали, как управлять состояниями с помощью Get.
- Примечание: Возможно, вам нужна более крупная организация и не использовать свойство init. Для этого вы можете создать класс и расширить класс Bindings, указав в нем контроллеры, которые будут созданы в рамках этого маршрута. Контроллеры не будут создаваться в это время, наоборот, это просто инструкция, так что при первом использовании контроллера Get будет знать, где его искать. Get останется lazyLoad и продолжит удалять контроллеры, когда они больше не нужны. Смотрите пример в pub.dev, чтобы увидеть, как это работает.
Если вы перемещаетесь по многим маршрутам и вам нужны данные, которые были в вашем ранее используемом контроллере, вам просто нужно использовать снова GetBuilder (без инициализации):
```dart
class OtherClass extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: GetBuilder<Controller>(
builder: (s) => Text('${s.counter}'),
),
),
);
}
```
Если вам нужно использовать свой контроллер во многих других местах и ​​за пределами GetBuilder, просто создайте get в своем контроллере и легко его получите. (или используйте `Get.find<Controller>()`)
```dart
class Controller extends GetxController {
/// You do not need that. I recommend using it just for ease of syntax.
/// with static method: Controller.to.increment();
/// with no static method: Get.find<Controller>().increment();
/// There is no difference in performance, nor any side effect of using either syntax. Only one does not need the type, and the other the IDE will autocomplete it.
static Controller get to => Get.find(); // add this line
int counter = 0;
void increment() {
counter++;
update();
}
}
```
И тогда вы можете напрямую получить доступ к своему контроллеру:
```dart
FloatingActionButton(
onPressed: () {
Controller.to.increment(),
} // This is incredibly simple!
child: Text("${Controller.to.counter}"),
),
```
Когда вы нажимаете FloatingActionButton, все виджеты, которые прослушивают переменную 'counter', будут обновлены автоматически.
### Как обрабатываются контроллеры
Допустим, у нас есть это:
`Class a => Class B (has controller X) => Class C (has controller X)`
В классе A контроллер ещё не находится в памяти, потому что вы его ещё не использовали (Get is lazyLoad). В классе B вы использовали контроллер, и он вошёл в память. В классе C вы использовали тот же контроллер, что и в классе B, Get будет разделять состояние контроллера B с контроллером C, и тот же контроллер всё ещё находится в памяти. Если вы закроете экран C и экран B, Get автоматически извлечёт контроллер X из памяти и освободит ресурсы, поскольку класс A не использует контроллер. Если вы снова перейдете к B, контроллер X снова войдет в память, если вместо перехода к классу C вы снова вернётесь к классу A, Get таким же образом выведет контроллер из памяти. Если класс C не использовал контроллер, а вы вынули класс B из памяти, ни один класс не будет использовать контроллер X, и, соответственно, он будет удален. Единственное исключение, которое может случиться с Get, - это если вы неожиданно удалите B из маршрута и попытаетесь использовать контроллер в C. В этом случае идентификатор создателя контроллера, который был в B, был удален, и Get был запрограммирован на удаление его из памяти каждого контроллера, у которого нет идентификатора создателя. Если вы намереваетесь сделать это, добавьте флаг "autoRemove: false" в GetBuilder класса B GetBuilder и используйте adoptID = true в GetBuilder класса C.
### Вам больше не понадобятся StatefulWidgets
Использование StatefulWidgets означает ненужное сохранение состояния всех экранов, даже если вам нужно минимально перестроить виджет, вы встроите его в Consumer/Observer/BlocProvider/GetBuilder/GetX/Obx, которые будет ещё одним StatefulWidget.
Класс StatefulWidget - это класс большего размера, чем StatelessWidget, который будет выделять больше оперативной памяти, и это может не иметь существенного значения между одним или двумя классами, но, безусловно, будет иметь место, когда у вас их 100!
Если вам не нужно использовать миксин, например TickerProviderStateMixin, использовать StatefulWidget с Get совершенно не нужно.
Вы можете вызывать все методы StatefulWidget прямо из GetBuilder.
Например, если вам нужно вызвать метод initState() или dispose(), вы можете вызвать их напрямую;
```dart
GetBuilder<Controller>(
initState: (_) => Controller.to.fetchApi(),
dispose: (_) => Controller.to.closeStreams(),
builder: (s) => Text('${s.username}'),
),
```
Гораздо лучший подход, чем этот, - использовать методы onInit() и onClose() непосредственно из вашего контроллера.
```dart
@override
void onInit() {
fetchApi();
super.onInit();
}
```
- ПРИМЕЧАНИЕ: Если вы хотите запустить метод в момент первого вызова контроллера, вам НЕ НУЖНО использовать для этого конструкторы, на самом деле, используя ориентированный на производительность пакет, такой как Get, это граничит с плохой практикой, потому что отклоняется от логики, в которой контроллеры создаются или выделяются (если вы создаете экземпляр этого контроллера, конструктор будет вызываться немедленно, вы будете заполнять контроллер ещё до того, как он будет использован, вы выделяете память, не используя её , это определенно вредит принципам этой библиотеки).
Методы onInit(); и onClose(); были созданы для этого, они будут вызываться при создании Контроллера или использоваться в первый раз, в зависимости от того, используете вы Get.lazyPut или нет.
Если вы хотите, например, вызвать ваш API для заполнения данных, вы можете забыть о старомодном методе initState/dispose, просто начните свой вызов api в onInit, и если вам нужно выполнить любую команду как закрытие потоков, используйте для этого onClose().
### Почему это существует
Цель этого пакета - предоставить вам законченное решение для навигации по маршрутам, управления зависимостями и состояниями с использованием минимально возможных зависимостей с высокой степенью разделения.
Get включает в себя все высокоуровневые и низкоуровневые API-интерфейсы Flutter, чтобы гарантировать, что вы работаете с наименьшими взаимозависимостями.
Мы централизуем всё в одном пакете, чтобы гарантировать, что у вас нет никакой взаимозависимости в вашем проекте.
Таким образом, вы можете поместить в свое представление только виджеты и оставить часть своей команды, которая работает с бизнес-логикой, свободной, чтобы работать с бизнес-логикой независимо от какого-либо элемента представления.
Это обеспечивает гораздо более чистую рабочую среду, так что часть вашей команды работает только с виджетами, не беспокоясь об отправке данных на ваш контроллер, а часть вашей команды работает только с бизнес-логикой, независимо от какого-либо элемента представления.
Итак, чтобы упростить это: вам не нужно вызывать методы в initState и отправлять их по параметрам на ваш контроллер или использовать для этого конструктор вашего контроллера, у вас есть метод onInit (), который вызывается в нужное время, чтобы вы могли начать ваши сервисы.
Вам не нужно вызывать устройство, у вас есть метод onClose(), который будет вызываться именно в тот момент, когда ваш контроллер больше не нужен и будет удален из памяти. Таким образом, оставьте представления только для виджетов, воздерживаясь от какой-либо бизнес-логики.
Не вызывайте метод удаления внутри GetxController, он ничего не сделает, помните, что контроллер не является виджетом, его не следует "удалять", и он будет автоматически и разумно удалён из памяти с помощью Get.
Если вы использовали какой-либо поток и хотите закрыть его, просто вставьте его в метод close. Пример:
```dart
class Controller extends GetxController {
StreamController<User> user = StreamController<User>();
StreamController<String> name = StreamController<String>();
/// close stream = onClose method, not dispose.
@override
void onClose() {
user.close();
name.close();
super.onClose();
}
}
```
Жизненный цикл контроллера:
- onInit() когда он создается.
- onClose() когда он закрывается для внесения каких-либо изменений при подготовке к удалению.
- удалено: у вас нет доступа к этому API, потому что он буквально удаляет контроллер из памяти. Он буквально удаляется, не оставляя следов.
### Другие способы использования
Вы можете использовать экземпляр контроллера непосредственно со значением GetBuilder:
```dart
GetBuilder<Controller>(
init: Controller(),
builder: (value) => Text(
'${value.counter}', //here
),
),
```
Вам также может понадобиться экземпляр вашего контроллера вне GetBuilder, и вы можете использовать эти подходы для достижения этой цели:
```dart
class Controller extends GetxController {
static Controller get to => Get.find();
[...]
}
// on you view:
GetBuilder<Controller>(
init: Controller(), // use it only first time on each controller
builder: (_) => Text(
'${Controller.to.counter}', //here
)
),
```
или
```dart
class Controller extends GetxController {
// static Controller get to => Get.find(); // with no static get
[...]
}
// on stateful/stateless class
GetBuilder<Controller>(
init: Controller(), // use it only first time on each controller
builder: (_) => Text(
'${Get.find<Controller>().counter}', //here
),
),
```
- Для этого можно использовать "неканоничные" подходы. Если вы используете какой-либо другой менеджер зависимостей, например get_it, modular и т.д., и просто хотите доставить экземпляр контроллера, вы можете сделать это:
```dart
Controller controller = Controller();
[...]
GetBuilder<Controller>(
init: controller, //here
builder: (_) => Text(
'${controller.counter}', // here
),
),
```
### Уникальные идентификаторы
Если вы хотите уточнить элемент управления обновлением виджета с помощью GetBuilder, вы можете назначить им уникальные идентификаторы:
```dart
GetBuilder<Controller>(
id: 'text'
init: Controller(), // use it only first time on each controller
builder: (_) => Text(
'${Get.find<Controller>().counter}', //here
),
),
```
И обновите это следующим образом:
```dart
update(['text']);
```
Также можно наложить условия на обновление:
```dart
update(['text'], counter < 10);
```
GetX делает это автоматически и восстанавливает только виджет, который использует точную переменную, которая была изменена, если вы измените переменную на ту же самую, что и предыдущая, и это не означает изменения состояния, GetX не будет перестраивать виджет для экономии памяти и CPU (На экране отображается 3, и вы снова меняете переменную на 3. В большинстве менеджеров состояний это вызовет новую перестройку, но с GetX виджет будет перестраиваться снова только в том случае, если на самом деле его состояние изменилось).
## Смешивание двух менеджеров состояний
Некоторые открыли запрос, так как они хотели использовать только один тип реактивной переменной и другой механизм, и для этого нужно было вставить Obx в GetBuilder.
Подумав об этом, был создан MixinBuilder.
Он позволяет как реактивные изменения путем изменения переменных ".obs", так и механические обновления через update().
Однако из 4 виджетов он - тот, который потребляет больше всего ресурсов, поскольку помимо подписки на получение событий изменений от своих дочерних элементов, он подписывается на метод обновления своего контроллера.
Расширение GetxController важно, поскольку у них есть жизненные циклы, и они могут "запускать" и "завершать" события в своих методах onInit() и onClose().
Вы можете использовать для этого любой класс, но я настоятельно рекомендую вам использовать класс GetxController для размещения ваших переменных, независимо от того, наблюдаемы они или нет.
## GetBuilder vs GetX vs Obx vs MixinBuilder
За десять лет работы с программированием я смог извлечь несколько ценных уроков.
Мой первый контакт с реактивным программированием был таким: "Вау, это невероятно", и на самом деле реактивное программирование невероятно.
Однако он подходит не для всех ситуаций. Часто всё, что вам нужно, - это изменить состояние 2 или 3 виджетов одновременно или кратковременное изменение состояния, и в этом случае реактивный подход неплох, но не подходит.
Реактивное программирование требует более высокого потребления оперативной памяти, что может быть компенсировано отдельным рабочим процессом, который гарантирует, что только один виджет будет перестроен и при необходимости, но создание списка из 80 объектов, каждый с несколькими потоками, не является хорошей идеей.
Откройте dart inspect и проверьте, сколько потребляет StreamBuilder, и вы поймёте, что я пытаюсь вам сказать.
Имея это в виду, я создал простой менеджер состояний. Это просто, и это именно то, что вы должны от него требовать: обновление состояния в блоках простым и наиболее экономичным способом.
GetBuilder очень экономичен в оперативной памяти, и вряд ли существует более экономичный подход, чем он (по крайней мере, я не могу представить его, если он существует, сообщите нам).
Однако GetBuilder по-прежнему является механическим менеджером состояний, вам нужно вызвать update() так же, как вам нужно было бы вызвать notifyListeners() провайдера.
Бывают и другие ситуации, когда реактивное программирование действительно интересно, и не работать с ним - все равно, что изобретать колесо.
Имея это в виду, GetX был создан, чтобы предоставить всё самое современное и продвинутое в менеджере состояний.
Он обновляет только то, что необходимо, и при необходимости, если у вас есть ошибка и вы отправляете 300 изменений состояния одновременно, GetX будет фильтровать и обновлять экран только в том случае, если состояние действительно изменяется.
GetX по-прежнему более экономичен, чем любой другой менеджер реактивного состояния, но он потребляет немного больше оперативной памяти, чем GetBuilder.
Думая об этом и стремясь максимизировать потребление ресурсов, Obx был создан.
В отличие от GetX и GetBuilder, вы не сможете инициализировать контроллер внутри Obx, это просто виджет с StreamSubscription, который получает события изменения от ваших детей, вот и всё.
Он более экономичен, чем GetX, но проигрывает GetBuilder, что и следовало ожидать, поскольку он является реактивным, а GetBuilder имеет самый упрощенный подход к хранению хэш-кода виджета и его StateSetter.
С Obx вам не нужно писать свой тип контроллера, и вы можете услышать изменение от нескольких разных контроллеров, но его необходимо инициализировать перед этим, используя примерный подход в начале этого файла readme или используя класс Bindings.

View File

@@ -0,0 +1,380 @@
# Quản lý dependency
- [Quản lý dependency](#dependency-management)
- [Instancing methods](#instancing-methods)
- [Get.put()](#getput)
- [Get.lazyPut](#getlazyput)
- [Get.putAsync](#getputasync)
- [Get.create](#getcreate)
- [Sử dụng các phương thức / class](#using-instantiated-methodsclasses)
- [Khác nhau giữa phương thức (methods)](#differences-between-methods)
- [Bindings](#bindings)
- [Cách sử dụng](#how-to-use)
- [BindingsBuilder](#bindingsbuilder)
- [SmartManagement](#smartmanagement)
- [Cách thay đổi](#How-to-change)
- [SmartManagement.full](#smartmanagementfull)
- [SmartManagement.onlyBuilders](#smartmanagementonlybuilders)
- [SmartManagement.keepFactory](#smartmanagementkeepfactory)
- [Cách bindings làm việc ngầm](#how-bindings-work-under-the-hood)
- [Chí ú](#notes)
Get có một trình quản lý dependency đơn giản và mạnh mẽ cho phép bạn truy xuất cùng một class với Blocs hoặc Controller của bạn chỉ với 1 dòng mã, không có "context", không có InheritedWidget
```dart
Controller controller = Get.put(Controller()); // Rather Controller controller = Controller();
```
Thay vì khởi tạo class của bạn trong class bạn đang sử dụng, bạn đang khởi tạo nó trong phiên bản Get, điều này sẽ làm cho nó có sẵn trên toàn bộ Ứng dụng của bạn.
Vì vậy, bạn có thể sử dụng controller (hoặc class Blocs) của mình một cách bình thường
- Note: Nếu bạn đang sử dụng Get's State Manager, hãy chú ý hơn đến [Bindings](#bindings) api, điều này sẽ giúp kết nối chế độ xem với controller của bạn dễ dàng hơn.
- Note²: Quản lý state của Get được tách biệt khỏi các phần khác của gói, vì vậy, nếu ví dụ: nếu ứng dụng của bạn đã sử dụng trình quản lý state (bất kỳ cái nào, không quan trọng), bạn không cần phải thay đổi điều đó, bạn có thể sử dụng phần dependency này người quản lý không có vấn đề gì cả
## Instancing methods
Các phương thức và các tham số có thể định cấu hình của nó là:
### Get.put()
Cách phổ biến nhất để chèn một dependency, là một điều tốt cho controller của View của bạn.
```dart
Get.put<SomeClass>(SomeClass());
Get.put<LoginController>(LoginController(), permanent: true);
Get.put<ListItemController>(ListItemController, tag: "some unique string");
```
Đây là tùy chọn mà bạn có thể đặt lệnh:
```dart
Get.put<S>(
// mandatory: the class that you want to get to save, like a controller or anything
// note: "S" means that it can be a class of any type
S dependency
// optional: this is for when you want multiple classess that are of the same type
// since you normally get a class by using Get.find<Controller>(),
// you need to use tag to tell which instance you need
// must be unique string
String tag,
// optional: by default, get will dispose instances after they are not used anymore (example,
// the controller of a view that is closed), but you might need that the instance
// to be kept there throughout the entire app, like an instance of sharedPreferences or something
// so you use this
// defaults to false
bool permanent = false,
// optional: allows you after using an abstract class in a test, replace it with another one and follow the test.
// defaults to false
bool overrideAbstract = false,
// optional: allows you to create the dependency using function instead of the dependency itself.
// this one is not commonly used
InstanceBuilderCallback<S> builder,
)
```
### Get.lazyPut
Có thể lazyLoad một dependecy để nó chỉ được khởi tạo khi được sử dụng. Rất hữu ích cho các class ngốn nhiều tài nguyên hoặc nếu bạn muốn khởi tạo một số class chỉ ở một nơi (như trong class Bindings) và bạn biết rằng mình sẽ không sử dụng class đó tại thời điểm nhất định.
```dart
/// ApiMock will only be called when someone uses Get.find<ApiMock> for the first time
Get.lazyPut<ApiMock>(() => ApiMock());
Get.lazyPut<FirebaseAuth>(
() {
// ... some logic if needed
return FirebaseAuth();
},
tag: Math.random().toString(),
fenix: true
)
Get.lazyPut<Controller>( () => Controller() )
```
Đây là các tùy chọn bạn có thể đặt lệnh:
```dart
Get.lazyPut<S>(
// mandatory: a method that will be executed when your class is called for the first time
InstanceBuilderCallback builder,
// optional: same as Get.put(), it is used for when you want multiple different instance of a same class
// must be unique
String tag,
// optional: It is similar to "permanent", the difference is that the instance is discarded when
// is not being used, but when it's use is needed again, Get will recreate the instance
// just the same as "SmartManagement.keepFactory" in the bindings api
// defaults to false
bool fenix = false
)
```
### Get.putAsync
Đây là khi bạn muốn xài asynchronize code `Get.putAsync`:
```dart
Get.putAsync<SharedPreferences>(() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setInt('counter', 12345);
return prefs;
});
Get.putAsync<YourAsyncClass>( () async => await YourAsyncClass() )
```
Đây là tùy chọn bạn có thể đặt lệnh với putAsync:
```dart
Get.putAsync<S>(
// mandatory: an async method that will be executed to instantiate your class
AsyncInstanceBuilderCallback<S> builder,
// optional: same as Get.put(), it is used for when you want multiple different instance of a same class
// must be unique
String tag,
// optional: same as in Get.put(), used when you need to maintain that instance alive in the entire app
// defaults to false
bool permanent = false
)
```
### Get.create
Cái này hơi khó giải thích, nhưng sự khác nhau giữa chúng có thể được tìm thấy trên mục [Differences between methods:](#differences-between-methods)
```dart
Get.Create<SomeClass>(() => SomeClass());
Get.Create<LoginController>(() => LoginController());
```
Tùy chọn có thể sử dụng:
```dart
Get.create<S>(
// required: a function that returns a class that will be "fabricated" every
// time `Get.find()` is called
// Example: Get.create<YourClass>(() => YourClass())
FcBuilderFunc<S> builder,
// optional: just like Get.put(), but it is used when you need multiple instances
// of a of a same class
// Useful in case you have a list that each item need it's own controller
// needs to be a unique string. Just change from tag to name
String name,
// optional: just like int`Get.put()`, it is for when you need to keep the
// instance alive thoughout the entire app. The difference is in Get.create
// permanent is true by default
bool permanent = true
```
## Sử dụng các phương thức / class
Hãy tưởng tượng rằng bạn đã điều hướng qua nhiều route và bạn cần một dữ liệu còn sót trong controller của mình, bạn sẽ cần một trình quản lý state kết hợp với Provider hoặc Get_it, phải hem? Với Get, bạn chỉ cần yêu cầu Get to "find" cho controller của mình và thế là xong:
```dart
final controller = Get.find<Controller>();
// OR
Controller controller = Get.find();
// Yes, it looks like Magic, Get will find your controller, and will deliver it to you.
// You can have 1 million controllers instantiated, Get will always give you the right controller.
```
Và sau đó, bạn sẽ có thể khôi phục dữ liệu controller của mình đã lấy được ở đó:
```dart
Text(controller.textFromApi);
```
Vì giá trị trả về là một class bình thường, bạn có thể làm bất cứ điều gì bạn muốn:
```dart
int count = Get.find<SharedPreferences>().getInt('counter');
print(count); // out: 12345
```
Để xóa một controller đang chạy ngầm của Get:
```dart
Get.delete<Controller>(); //thường thì Get tự xóa, bạn không cần phải đặt lệnh này.
```
## Khác nhau giữa phương thức (methods)
Đầu tiên, hãy nói về `fenix` của Get.lazyPut và `permanent`của các phương thức khác.
Sự khác biệt cơ bản giữa `permanent``fenix` là cách bạn muốn lưu trữ các cá thể của mình.
Củng cố: theo mặc định, GetX xóa các trường hợp khi chúng không được sử dụng.
Có nghĩa là: Nếu màn hình 1 có controller 1 và màn hình 2 có controller 2 và bạn xóa route đầu tiên khỏi stack, (chẳng hạn như nếu bạn sử dụng `` Get.off () 'hoặc' `Get.offNamed()``) thì controller 1 bị mất việc sử dụng nó vì vậy nó sẽ bị xóa.
Nhưng nếu bạn muốn chọn sử dụng `permanent: true`, thì controller sẽ không bị mất trong quá trình chuyển đổi này - điều này rất hữu ích cho các dịch vụ mà bạn muốn duy trì hoạt động trong toàn bộ ứng dụng.
Mặt khác, `fenix` dành cho các dịch vụ mà bạn không lo bị mất giữa các lần thay đổi màn hình, nhưng khi bạn cần dịch vụ đó, bạn hy vọng rằng nó vẫn tồn tại. Vì vậy, về cơ bản, nó sẽ loại bỏ controller / service / class không sử dụng, nhưng khi bạn cần, nó sẽ "tạo lại từ đống tro tàn" ở một trường hợp (instance) mới.
Tiếp tục với sự khác biệt giữa các phương pháp:
- Get.put và Get.putAsync tuân theo cùng một thứ tự tạo, với sự khác biệt là một cái sử dụng phương thức không đồng bộ: hai phương thức đó đều tạo và khởi tạo các trường hợp. Cái sử dụng không đồng bộ được chèn trực tiếp vào bộ nhớ, bằng cách sử dụng phương thức nội bộ `insert` với các tham số `permanent: false` và` isSingleton: true` (tham số isSingleton này chỉ nhằm mục đích cho biết liệu nó có sử dụng dependency vào `dependency` hay không hoặc nếu nó được sử dụng dependency vào `FcBuilderFunc`). Sau đó, `Get.find ()` được gọi để khởi tạo ngay lập tức các các trường hợp trên bộ nhớ.
- Get.create: Như tên của nó, nó sẽ "tạo ra" sự dependency cho bạn! Tương tự như `Get.put ()`, nó cũng gọi phương thức nội bộ là `insert` để các trường hợp. Nhưng `permanent` trở thành true và` isSingleton` trở thành false (vì chúng ta đang "tạo" dependency của mình, không có cách nào để nó là một instace singleton, đó là lý do tại sao lại là false). Và bởi vì nó có `permanent: true`, chúng tôi mặc định có lợi ích là không bị mất nó giữa các màn hình! Ngoài ra, `` Get.find () 'không được gọi ngay lập tức, nó phải chờ được sử dụng trong màn hình để được gọi. Nó được tạo ra theo cách này để sử dụng tham số `permanent ', vì vậy, đáng chú ý là` Get.create () `được tạo ra với mục tiêu tạo ra các phiên bản không được chia sẻ, nhưng không bị loại bỏ, như ví dụ: trong listView, mà bạn muốn có một phiên bản duy nhất cho danh sách đó - do đó, Get.create phải được sử dụng cùng với GetWidget.
- Get.lazyPut: Như tên của nó, nó là một quy trình lười biếng. Cá thể được tạo, nhưng nó không được gọi để sử dụng ngay lập tức, nó vẫn đang chờ được gọi. Trái ngược với các phương thức khác, `insert` không được gọi ở đây. Thay vào đó, cá thể được chèn vào một phần khác của bộ nhớ, một phần chịu trách nhiệm cho biết liệu cá thể đó có thể được tạo lại hay không, chúng ta hãy gọi nó là "nhà máy". Nếu chúng ta muốn tạo ra thứ gì đó để sử dụng sau này, nó sẽ không bị trộn lẫn với những thứ đã được sử dụng ngay bây giờ. Và đây là nơi phép thuật của `fenix` đi vào: nếu bạn chọn bỏ` fenix: false`, và `smartManagement` của bạn không phải là` keepFactory`, thì khi sử dụng `Get.find`, instance sẽ thay đổi vị trí trong bộ nhớ từ "nhà máy" đến vùng bộ nhớ cá thể chung. Ngay sau đó, theo mặc định, nó được xóa khỏi "nhà máy". Bây giờ, nếu bạn chọn `fenix: true`, cá thể vẫn tiếp tục tồn tại trong phần dành riêng này, thậm chí sẽ chuyển sang vùng chung, sẽ được gọi lại trong tương lai.
## Bindings
Có lẽ, một trong những điểm khác biệt lớn của gói này là khả năng tích hợp đầy đủ các route, trình quản lý state và trình quản lý dependency.
Khi một route bị xóa khỏi stack, tất cả các controller, biến và phiên bản của các đối tượng liên quan đến nó sẽ bị xóa khỏi bộ nhớ. Nếu bạn đang sử dụng luồng hoặc bộ hẹn giờ, chúng sẽ tự động bị đóng và bạn không phải lo lắng về bất kỳ điều gì trong số đó.
Trong phiên bản 2.10 Được triển khai hoàn toàn API bindings.
Bây giờ bạn không cần sử dụng phương thức init nữa. Bạn thậm chí không cần phải nhập controller của mình nếu bạn không muốn. Bạn có thể khởi động controller và dịch vụ của mình ở nơi thích hợp cho việc đó.
Lớp Binding là một class sẽ tách riêng việc tiêm dependency, trong khi "bindings" các route đường tới trình quản lý state và trình quản lý dependency.
Điều này cho phép Nhận biết màn hình nào đang được hiển thị khi một controller cụ thể được sử dụng và biết vị trí và cách vứt bỏ nó.
Ngoài ra, class Binding sẽ cho phép bạn kiểm soát cấu hình SmartManager. Bạn có thể định cấu hình các phần dependency được sắp xếp khi xóa một route khỏi ngăn xếp hoặc khi widget con đã sử dụng nó được bố trí hoặc không. Bạn sẽ có quản lý dependency thông minh làm việc cho bạn, nhưng ngay cả như vậy, bạn có thể định cấu hình nó theo ý muốn.
### Bindings class
- Tạo một class và implements Binding
```dart
class HomeBinding implements Bindings {}
```
IDE của bạn sẽ tự động yêu cầu bạn ghi đè phương thức "dependency" và bạn chỉ cần nhấp vào đèn, ghi đè phương thức và chèn tất cả các class bạn sẽ sử dụng trên route đó:
```dart
class HomeBinding implements Bindings {
@override
void dependencies() {
Get.lazyPut<HomeController>(() => HomeController());
Get.put<Service>(()=> Api());
}
}
class DetailsBinding implements Bindings {
@override
void dependencies() {
Get.lazyPut<DetailsController>(() => DetailsController());
}
}
```
Bây giờ bạn chỉ cần thông báo route của mình, rằng bạn sẽ sử dụng bindings đó để tạo kết nối giữa trình quản lý route, các dependency và state.
- Sử dụng routes có tên:
```dart
getPages: [
GetPage(
name: '/',
page: () => HomeView(),
binding: HomeBinding(),
),
GetPage(
name: '/details',
page: () => DetailsView(),
binding: DetailsBinding(),
),
];
```
- Sử dụng routes thường:
```dart
Get.to(Home(), binding: HomeBinding());
Get.to(DetailsView(), binding: DetailsBinding())
```
Ở đó, bạn không phải lo lắng về việc quản lý bộ nhớ của ứng dụng của mình nữa, Get sẽ thay bạn làm điều đó.
Lớp Binding được gọi khi một route được gọi, bạn có thể tạo một "InitialBinding trong GetMaterialApp của mình để chèn tất cả các dependency sẽ được tạo.
```dart
GetMaterialApp(
initialBinding: SampleBind(),
home: Home(),
);
```
### BindingsBuilder
Cách mặc định để tạo bindings là tạo một class thực hiện các bindings.
Nhưng cách khác, bạn có thể sử dụng lệnh gọi lại `BindingsBuilder` để bạn có thể chỉ cần sử dụng một hàm để khởi tạo bất cứ thứ gì bạn muốn.
Example:
```dart
getPages: [
GetPage(
name: '/',
page: () => HomeView(),
binding: BindingsBuilder(() {
Get.lazyPut<ControllerX>(() => ControllerX());
Get.put<Service>(()=> Api());
}),
),
GetPage(
name: '/details',
page: () => DetailsView(),
binding: BindingsBuilder(() {
Get.lazyPut<DetailsController>(() => DetailsController());
}),
),
];
```
Bằng cách đó, bạn có thể tránh tạo một class Binding cho mỗi routes, làm cho việc này trở nên đơn giản hơn.
Cả hai cách làm việc đều hoàn toàn tốt và chúng tôi muốn bạn sử dụng những gì phù hợp với sở thích của bạn nhất.
### SmartManagement
GetX theo mặc định loại bỏ controller không sử dụng khỏi bộ nhớ, ngay cả khi xảy ra lỗi và widget con sử dụng nó không được xử lý đúng cách.
Đây được gọi là chế độ quản lý dependency `` đầy đủ`.
Nhưng nếu bạn muốn thay đổi cách GetX kiểm soát việc xử lý các class, bạn có class `SmartManagement` để bạn có thể thiết lập các hành vi khác nhau.
#### Cách thay đổi
Nếu bạn muốn thay đổi cấu hình này (mà bạn thường không cần) thì đây là cách:
```dart
void main () {
runApp(
GetMaterialApp(
smartManagement: SmartManagement.onlyBuilders //here
home: Home(),
)
)
}
```
#### SmartManagement.full
Nó là một trong những mặc định. Loại bỏ các class không được sử dụng và không được đặt thành vĩnh viễn. Trong phần lớn các trường hợp, bạn sẽ muốn giữ nguyên cấu hình này. Nếu bạn mới sử dụng GetX thì đừng thay đổi điều này.
#### SmartManagement.onlyBuilders
Với tùy chọn này, chỉ những controller bắt đầu trong `init: 'hoặc được tải vào Binding với` `Get.lazyPut ()` mới được xử lý.
Nếu bạn sử dụng `Get.put () 'hoặc' Get.putAsync ()` hoặc bất kỳ cách tiếp cận nào khác, SmartManagement sẽ không có quyền loại trừ sự dependency này.
Với hành vi mặc định, ngay cả các widget con được khởi tạo bằng "Get.put" sẽ bị xóa, không giống như SmartManagement.onlyBuilders.
#### SmartManagement.keepFactory
Cũng giống như SmartManagement.full, nó sẽ loại bỏ các phần dependency của nó khi nó không được sử dụng nữa. Tuy nhiên, nó sẽ giữ nguyên chế độ factory của họ, có nghĩa là nó sẽ tạo lại phần dependency nếu bạn cần lại phiên bản đó.
### Cách bindings làm việc ngầm
Các liên kết tạo ra các factory tạm thời, được tạo ra ngay khi bạn nhấp để chuyển sang màn hình khác và sẽ bị phá hủy ngay sau khi hoạt ảnh thay đổi màn hình xảy ra.
Điều này xảy ra quá nhanh đến nỗi máy phân tích thậm chí sẽ không thể đăng ký nó.
Khi bạn điều hướng đến màn hình này một lần nữa, một factory tạm thời mới sẽ được gọi, vì vậy điều này thích hợp hơn khi sử dụng SmartManagement.keepFactory, nhưng nếu bạn không muốn tạo Bindings hoặc muốn giữ tất cả các dependency của mình trên cùng một Binding, thì chắc chắn sẽ giúp ích cho bạn.
Các factory chiếm ít bộ nhớ, chúng không chứa các cá thể mà là một chức năng có "hình dạng" của class đó mà bạn muốn.
Điều này có chi phí bộ nhớ rất thấp, nhưng vì mục đích của lib này là để đạt được hiệu suất tối đa có thể bằng cách sử dụng tài nguyên tối thiểu, Get xóa ngay cả các factory theo mặc định.
Sử dụng cái nào thuận tiện nhất cho bạn.
## Chí ú
- KHÔNG SỬ DỤNG SmartManagement.keepFactory nếu bạn đang sử dụng nhiều Binding. Nó được thiết kế để sử dụng mà không có Bindings, hoặc với một Bindings duy nhất được liên kết trong `initialBinding` của GetMaterialApp.
- Việc sử dụng Bindings là hoàn toàn tùy chọn, nếu muốn, bạn có thể sử dụng `Get.put () 'và' Get.find()` trên các class sử dụng controller nhất định mà không gặp bất kỳ vấn đề gì.
Tuy nhiên, nếu bạn làm việc với Service hoặc bất kỳ abstract nào khác, tôi khuyên bạn nên sử dụng Bindings để tổ chức tốt hơn.

View File

@@ -0,0 +1,569 @@
- [Quản lý route](#route-management)
- [Hướng dẫn sử dụng trước khi dùng](#how-to-use)
- [Điều hướng không cần tên](#navigation-without-named-routes)
- [Điều hướng cần tên](#navigation-with-named-routes)
- [Gửi data cho route có tên](#send-data-to-named-routes)
- [Dynamic urls links](#dynamic-urls-links)
- [Middleware](#middleware)
- [Điều hướng không cần context](#navigation-without-context)
- [SnackBars](#snackbars)
- [Dialogs](#dialogs)
- [BottomSheets](#bottomsheets)
- [Điều hướng lồng (Nested Navigation)](#nested-navigation)
# Quản lý route
Đây là lời giải thích đầy đủ về tất cả những gì có cho Getx khi vấn đề là quản lý routes.
## Hướng dẫn sử dụng trước khi dùng
Thêm cái này vào file pubspec.yaml của bạn:
```yaml
dependencies:
get:
```
Nếu bạn định sử dụng các routes / snackbars / dialogs / bottomsheets mà không có "context" hoặc sử dụng các API cấp cao, bạn chỉ cần thêm Get trước MaterialApp của mình, biến nó thành GetMaterialApp và tung hành!
```dart
GetMaterialApp( // Before: MaterialApp(
home: MyHome(),
)
```
## Điều hướng không cần tên
Để điều hướng đến một màn hình mới:
```dart
Get.to(NextScreen());
```
Để đóng snackbars, dialog, bottomsheets hoặc bất cứ thứ gì bạn thường đóng bằng Navigator.pop (context);
```dart
Get.back();
```
Để chuyển đến màn hình tiếp theo và không có tùy chọn nào để quay lại màn hình trước đó (để sử dụng trong SplashScreens, màn hình đăng nhập, v.v.)
```dart
Get.off(NextScreen());
```
Để chuyển đến màn hình tiếp theo và hủy tất cả các lộ trình trước đó (hữu ích trong giỏ hàng, polls và test)
```dart
Get.offAll(NextScreen());
```
Để điều hướng đến routes tiếp theo và nhận hoặc cập nhật dữ liệu ngay sau khi bạn trở về từ routes đó:
```dart
var data = await Get.to(Payment());
```
trên màn hình khác, gửi dữ liệu cho routes trước đó:
```dart
Get.back(result: 'success');
```
And use it:
ex:
```dart
if(data == 'success') madeAnything();
```
Bạn không muốn học cú pháp của chúng tôi?
Chỉ cần thay đổi Navigator (chữ in hoa) thành navigator (chữ thường) và bạn sẽ có tất cả các chức năng của điều hướng tiêu chuẩn mà không cần phải sử dụng "context"
Thí dụ:
```dart
// Default Flutter navigator
Navigator.of(context).push(
context,
MaterialPageRoute(
builder: (BuildContext context) {
return HomePage();
},
),
);
// Get using Flutter syntax without needing context
navigator.push(
MaterialPageRoute(
builder: (_) {
return HomePage();
},
),
);
// Get syntax (It is much better, but you have the right to disagree)
Get.to(HomePage());
```
## Điều hướng cần tên
- Nếu bạn thích điều hướng bằng tên, Get cũng hỗ trợ điều này.
To navigate to nextScreen
```dart
Get.toNamed("/NextScreen");
```
Để điều hướng và xóa màn hình trước đó khỏi cây widget.
```dart
Get.offNamed("/NextScreen");
```
Để điều hướng và xóa tất cả các màn hình trước đó khỏi cây widget.
```dart
Get.offAllNamed("/NextScreen");
```
Để định dạng routes, sử dụng GetMaterialApp:
```dart
void main() {
runApp(
GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => MyHomePage()),
GetPage(name: '/second', page: () => Second()),
GetPage(
name: '/third',
page: () => Third(),
transition: Transition.zoom
),
],
)
);
}
```
Để xử lý điều hướng đến các routes không được xác định (lỗi 404), bạn có thể xác định trang 'không xác định' trong GetMaterialApp.
```dart
void main() {
runApp(
GetMaterialApp(
unknownRoute: GetPage(name: '/notfound', page: () => UnknownRoutePage()),
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => MyHomePage()),
GetPage(name: '/second', page: () => Second()),
],
)
);
}
```
### Gửi data cho route có tên
Chỉ cần gửi những gì bạn muốn cho các đối số (arguments). Get chấp nhận bất kỳ thứ gì ở đây, cho dù đó là String, Map, List hay thậm chí là một class trường hợp.
```dart
Get.toNamed("/NextScreen", arguments: 'Get is the best');
```
Trong class controller của bạn:
```dart
print(Get.arguments);
//print out: Get is the best
```
### Dynamic urls links
Get hỗ trợ các url động nâng cao giống như trên Web. Các nhà phát triển web có lẽ đã muốn tính năng này trên Flutter và rất có thể đã thấy một gói hứa hẹn tính năng này và cung cấp một cú pháp hoàn toàn khác so với một URL sẽ có trên web, và Get cũng giải quyết được điều này.
```dart
Get.offAllNamed("/NextScreen?device=phone&id=354&name=Enzo");
```
trong controller/bloc/stateful/stateless của class:
```dart
print(Get.parameters['id']);
// out: 354
print(Get.parameters['name']);
// out: Enzo
```
Bạn có thể đặt NamedParameters với Get dễ dàng:
```dart
void main() {
runApp(
GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(
name: '/',
page: () => MyHomePage(),
),
GetPage(
name: '/profile/',
page: () => MyProfile(),
),
//You can define a different page for routes with arguments, and another without arguments, but for that you must use the slash '/' on the route that will not receive arguments as above.
GetPage(
name: '/profile/:user',
page: () => UserProfile(),
),
GetPage(
name: '/third',
page: () => Third(),
transition: Transition.cupertino
),
],
)
);
}
```
Gửi data bằng tên
```dart
Get.toNamed("/profile/34954");
```
Trên màn hình thứ hai, lấy dữ liệu theo tham số (parameters)
```dart
print(Get.parameters['user']);
// out: 34954
```
hoặc gửi nhiều tham số như thế này
```dart
Get.toNamed("/profile/34954?flag=true&country=italy");
```
or
```dart
var parameters = <String, String>{"flag": "true","country": "italy",};
Get.toNamed("/profile/34954", parameters: parameters);
```
Trên màn hình thứ hai, lấy dữ liệu theo các tham số như thường lệ
```dart
print(Get.parameters['user']);
print(Get.parameters['flag']);
print(Get.parameters['country']);
// out: 34954 true italy
```
Và bây giờ, tất cả những gì bạn cần làm là sử dụng Get.toNamed () để điều hướng các routes đã đặt tên của bạn mà không cần bất kỳ "context" nào (bạn có thể gọi các routes của mình trực tiếp từ BLoC hoặc lớp Bộ điều khiển) và khi ứng dụng của bạn được biên dịch lên web, các routes sẽ xuất hiện trong url <3
### Middleware
Nếu bạn muốn nghe Get events để kích hoạt các hành động, bạn có thể sử dụng routingCallback cho nó
```dart
GetMaterialApp(
routingCallback: (routing) {
if(routing.current == '/second'){
openAds();
}
}
)
```
Nếu bạn không sử dụng GetMaterialApp, bạn có thể sử dụng API thủ công để đính kèm trình quan sát Middleware.
```dart
void main() {
runApp(
MaterialApp(
onGenerateRoute: Router.generateRoute,
initialRoute: "/",
navigatorKey: Get.key,
navigatorObservers: [
GetObserver(MiddleWare.observer), // HERE !!!
],
),
);
}
```
Tạo một MiddleWare class
```dart
class MiddleWare {
static observer(Routing routing) {
/// You can listen in addition to the routes, the snackbars, dialogs and bottomsheets on each screen.
///If you need to enter any of these 3 events directly here,
///you must specify that the event is != Than you are trying to do.
if (routing.current == '/second' && !routing.isSnackbar) {
Get.snackbar("Hi", "You are on second route");
} else if (routing.current =='/third'){
print('last route called');
}
}
}
```
Bây giờ, hãy sử dụng Get trên code của bạn:
```dart
class First extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.add),
onPressed: () {
Get.snackbar("hi", "i am a modern snackbar");
},
),
title: Text('First Route'),
),
body: Center(
child: ElevatedButton(
child: Text('Open route'),
onPressed: () {
Get.toNamed("/second");
},
),
),
);
}
}
class Second extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.add),
onPressed: () {
Get.snackbar("hi", "i am a modern snackbar");
},
),
title: Text('second Route'),
),
body: Center(
child: ElevatedButton(
child: Text('Open route'),
onPressed: () {
Get.toNamed("/third");
},
),
),
);
}
}
class Third extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Third Route"),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Get.back();
},
child: Text('Go back!'),
),
),
);
}
}
```
## Điều hướng không cần context
### SnackBars
Để có một SnackBar đơn giản với Flutter, bạn phải lấy context của Scaffold, hoặc bạn phải sử dụng GlobalKey được gắn vào Scaffold của bạn
```dart
final snackBar = SnackBar(
content: Text('Hi!'),
action: SnackBarAction(
label: 'I am a old and ugly snackbar :(',
onPressed: (){}
),
);
// Find the Scaffold in the widget tree and use
// it to show a SnackBar.
Scaffold.of(context).showSnackBar(snackBar);
```
With Get:
```dart
Get.snackbar('Hi', 'i am a modern snackbar');
```
Với Get, tất cả những gì bạn phải làm là gọi thanh Get.snackbar từ bất kỳ đâu trong code của bạn hoặc tùy chỉnh nó theo cách bạn muốn!
```dart
Get.snackbar(
"Hey i'm a Get SnackBar!", // title
"It's unbelievable! I'm using SnackBar without context, without boilerplate, without Scaffold, it is something truly amazing!", // message
icon: Icon(Icons.alarm),
shouldIconPulse: true,
onTap:(){},
barBlur: 20,
isDismissible: true,
duration: Duration(seconds: 3),
);
////////// ALL FEATURES //////////
// Color colorText,
// Duration duration,
// SnackPosition snackPosition,
// Widget titleText,
// Widget messageText,
// bool instantInit,
// Widget icon,
// bool shouldIconPulse,
// double maxWidth,
// EdgeInsets margin,
// EdgeInsets padding,
// double borderRadius,
// Color borderColor,
// double borderWidth,
// Color backgroundColor,
// Color leftBarIndicatorColor,
// List<BoxShadow> boxShadows,
// Gradient backgroundGradient,
// TextButton mainButton,
// OnTap onTap,
// bool isDismissible,
// bool showProgressIndicator,
// AnimationController progressIndicatorController,
// Color progressIndicatorBackgroundColor,
// Animation<Color> progressIndicatorValueColor,
// SnackStyle snackStyle,
// Curve forwardAnimationCurve,
// Curve reverseAnimationCurve,
// Duration animationDuration,
// double barBlur,
// double overlayBlur,
// Color overlayColor,
// Form userInputForm
///////////////////////////////////
```
Nếu bạn thích snackbar truyền thống hoặc muốn tùy chỉnh nó từ đầu, bao gồm chỉ thêm một dòng (Get.snackbar sử dụng tiêu đề và thông báo bắt buộc), bạn có thể sử dụng
`Get.rawSnackbar ()`; 'cung cấp API RAW trên đó Get.
### Dialogs
To open dialog:
```dart
Get.dialog(YourDialogWidget());
```
To open default dialog:
```dart
Get.defaultDialog(
onConfirm: () => print("Ok"),
middleText: "Dialog made in 3 lines of code"
);
```
Bạn cũng có thể sử dụng Get.generalDialog thay vì showGeneralDialog.
Đối với tất cả các tiện ích hộp thoại Flutter khác, bao gồm cả cupertinos, bạn có thể sử dụng Get.overlayContext thay vì context và mở nó ở bất kỳ đâu trong mã của bạn.
Đối với các widget không sử dụng Overlay, bạn có thể sử dụng Get.context.
Hai context này sẽ hoạt động trong 99% trường hợp để thay thế context của UI của bạn, ngoại trừ các trường hợp trong đó inheritWidget được sử dụng mà không có context điều hướng.
### BottomSheets
Get.bottomSheet giống như showModalBottomSheet, nhưng không cần context.
```dart
Get.bottomSheet(
Container(
child: Wrap(
children: <Widget>[
ListTile(
leading: Icon(Icons.music_note),
title: Text('Music'),
onTap: () {}
),
ListTile(
leading: Icon(Icons.videocam),
title: Text('Video'),
onTap: () {},
),
],
),
)
);
```
## Điều hướng lồng (Nested Navigation)
Làm cho điều hướng lồng (nested navigation) của Flutter thậm chí còn dễ dàng hơn.
Bạn không cần context và bạn sẽ tìm thấy stack điều hướng của mình theo Id.
- CHÍ Ú: Việc tạo các stack điều hướng song song có thể gây nguy hiểm. Lý tưởng nhất là không sử dụng NestedNavigators, hoặc sử dụng một cách tối thiểu. Nếu dự án của bạn yêu cầu, hãy tiếp tục, nhưng hãy nhớ rằng việc giữ nhiều stack điều hướng trong bộ nhớ có thể không phải là một ý tưởng hay cho việc tiêu thụ RAM.
Xem nó code đơn giản nè:
```dart
Navigator(
key: Get.nestedKey(1), // create a key by index
initialRoute: '/',
onGenerateRoute: (settings) {
if (settings.name == '/') {
return GetPageRoute(
page: () => Scaffold(
appBar: AppBar(
title: Text("Main"),
),
body: Center(
child: TextButton(
color: Colors.blue,
onPressed: () {
Get.toNamed('/second', id:1); // navigate by your nested route by index
},
child: Text("Go to second"),
),
),
),
);
} else if (settings.name == '/second') {
return GetPageRoute(
page: () => Center(
child: Scaffold(
appBar: AppBar(
title: Text("Main"),
),
body: Center(
child: Text("second")
),
),
),
);
}
}
),
```

View File

@@ -0,0 +1,784 @@
* [Quản lý State](#state-management)
+ [Quản lý Reactive State](#reactive-state-manager)
- [Lợi thế](#advantages)
- [Hiệu suất tối đa:](#maximum-performance)
- [Khai báo một biến phản ứng (reactive variable)](#declaring-a-reactive-variable)
- [Thât dễ khi có reactive state.](#having-a-reactive-state-is-easy)
- [Sử dụng values trong View](#using-the-values-in-the-view)
- [Điều kiện để tái tạo lại](#conditions-to-rebuild)
- [Nơi .obs có thể dùng](#where-obs-can-be-used)
- [Chí ú về Lists](#note-about-lists)
- [Tại sao tôi phải dùng .value](#why-i-have-to-use-value)
- [Obx()](#obx)
- [Workers](#workers)
+ [Quản lý State đơn giản](#simple-state-manager)
- [Lợi thế](#advantages-1)
- [Sử dụng](#usage)
- [Cách GetX sử dụng controllers](#how-it-handles-controllers)
- [Không cần StatefulWidget nữa!](#you-wont-need-statefulwidgets-anymore)
- [Tại sao GetX tồn tại?](#why-it-exists)
- [Cách sử dụng khác](#other-ways-of-using-it)
- [IDs độc nhất](#unique-ids)
+ [Trộn hai trình quản lý state](#mixing-the-two-state-managers)
+ [GetBuilder vs GetX vs Obx vs MixinBuilder](#getbuilder-vs-getx-vs-obx-vs-mixinbuilder)
# Quản lý State
GetX không sử dụng Streams hoặc ChangeNotifier như các quản lý state khác. Tại sao? Ngoài việc xây dựng các ứng dụng cho android, iOS, web, linux, macos và linux, với GetX bạn có thể xây dựng các ứng dụng máy chủ với cú pháp tương tự như Flutter / GetX. Để cải thiện thời gian phản hồi và giảm mức tiêu thụ RAM, chúng tôi đã tạo GetValue và GetStream, là các giải pháp có độ trễ thấp mang lại nhiều hiệu suất với chi phí vận hành thấp. Chúng tôi sử dụng cơ sở này để xây dựng tất cả các nguồn lực của mình, bao gồm cả quản lý state.
* _Phức hợp_: Một số quản lý state rất phức tạp và có rất nhiều cơ sở hạ tầng. Với GetX, bạn không phải xác định một class cho mỗi event, code rất rõ ràng và rõ ràng, và bạn làm được nhiều việc hơn bằng cách viết ít hơn. Nhiều người đã từ bỏ Flutter vì chủ đề này, và cuối cùng họ đã có một giải pháp đơn giản đến mức đần độn để quản lý các state.
* _Không trình tạo mã_: Bạn dành một nửa thời gian phát triển để viết logic ứng dụng của mình. Một số quản lý state dựa vào trình tạo mã để có mã có thể đọc được ở mức tối thiểu. Việc thay đổi một biến và phải chạy build_runner có thể gây mất hiệu quả, chuyện này rất ngốn thời gian chờ đợi sau khi quét sạch sẽ rất lâu và bạn phải uống rất nhiều cà phê.
Với GetX, mọi thứ đều hoạt động và độc lập với trình tạo mã, giúp tăng năng suất của bạn trong mọi khía cạnh phát triển của bạn.
* _Không phụ thuộc vào context_: Có thể bạn đã cần gửi context của chế độ xem của mình tới controller, làm cho khả năng kết hợp của View với business logic của bạn cao hơn. Bạn có thể phải sử dụng một dependency cho một nơi không có context và phải chuyển context qua các class và hàm khác nhau. Điều này không tồn tại với GetX. Bạn có quyền truy cập vào controller của mình từ bên trong controller mà không cần bất kỳ context nào. Bạn không cần phải gửi context theo tham số vì không có gì theo nghĩa đen.
* _Kiểm soát hạt_: Hầu hết các quản lý state đều dựa trên ChangeNotifier. ChangeNotifier sẽ thông báo cho tất cả các widget phụ thuộc vào nó khi thông báo cho các widget được gọi. Nếu bạn có 40 widget con trên một màn hình, trong đó có một biến thuộc class ChangeNotifier của bạn, khi bạn cập nhật một widget con, tất cả chúng sẽ được xây dựng lại.
Với GetX, ngay cả các widget lồng nhau cũng được tôn trọng. Nếu bạn có Obx đang xem ListView của bạn và người khác đang xem hộp kiểm bên trong ListView, thì khi thay đổi giá trị CheckBox, chỉ nó mới được cập nhật, khi thay đổi giá trị List, chỉ ListView sẽ được cập nhật.
* _Chỉ tái tạo lại nếu biến CẦN thay đổi_: GetX có tính năng kiểm soát streams, điều đó có nghĩa là nếu bạn hiển thị Text là 'Kaiser', nếu bạn thay đổi lại biến có thể quan sát thành 'Kaiser', widget sẽ không được tạo lại. Đó là bởi vì GetX biết rằng 'Kaiser' đã được hiển thị trong Văn bản và sẽ không thực hiện các thao tác tái tạo không cần thiết.
Hầu hết (nếu không phải tất cả) các trình quản lý state hiện tại sẽ xây dựng lại trên màn hình.
## Quản lý Reactive State
Lập trình phản ứng (Reactive programming) có thể khiến nhiều người xa lánh vì nó được cho là phức tạp. GetX biến lập trình phản ứng thành một thứ khá đơn giản:
* Bạn sẽ không cần tạo StreamControllers.
* Bạn sẽ không cần tạo StreamBuilder cho mỗi biến
* Bạn sẽ không cần phải tạo một class cho mỗi state.
* Bạn sẽ không cần tạo get cho một giá trị ban đầu.
Lập trình phản ứng với Get dễ dàng như sử dụng setState.
Hãy tưởng tượng rằng bạn có một biến tên và muốn rằng mỗi khi bạn thay đổi nó, tất cả các widget sử dụng nó sẽ được tự động thay đổi.
Đây là count variable của bạn:
``` dart
var name = 'Khang Huỳnh';
```
Để làm cho nó có thể quan sát được, bạn chỉ cần thêm ".obs" vào cuối nó:
``` dart
var name = 'Khang Huỳnh'.obs;
```
Chỉ vậy thôi, chỉ *vậy thôi* người ơi~
Từ bây giờ, chúng ta có thể tham chiếu đến các biến reactive - ". Obs" (có thể thay thế) này là _Rx_.
Chúng tôi đã làm gì phía dưới class code? Chúng tôi đã tạo một `Stream` của `String`, được gán giá trị ban đầu `"Khang Huỳnh"`, chúng tôi đã thông báo cho tất cả các widget con sử dụng `"Khang Huỳnh"` rằng chúng hiện "thuộc về" biến này và khi giá trị _Rx_ thay đổi, chúng phải thay đổi theo.
Đây là **phép màu của GetX**, nhờ vào khả năng của Dart.
Tuy nhiên, như chúng ta đã biết, một `Widget` chỉ có thể được thay đổi nếu nó nằm bên trong một hàm, bởi vì các class tĩnh không có quyền" tự động thay đổi ".
Bạn sẽ cần tạo một `StreamBuilder`, đăng ký biến này để lắng nghe các thay đổi và tạo một "stream" các` StreamBuilder` lồng nhau nếu bạn muốn thay đổi một số biến trong cùng một phạm vi, phải không?
Không, bạn không cần `StreamBuilder`, nhưng bạn đã đúng về các class tĩnh.
Theo quan điểm, chúng ta thường có rất nhiều bảng soạn sẵn khi chúng ta muốn thay đổi một Widget cụ thể, đó là cách Flutter.
Với ** GetX **, bạn cũng có thể quên mã soạn sẵn này.
`StreamBuilder (…)`? `initialValue:…`? `builder:…`? Không, bạn chỉ cần đặt biến này bên trong Widget `Obx ()`.
``` dart
Obx (() => Text (controller.name));
```
_Bạn cần nhớ gì?_ Chỉ `Obx(() =>` .
Bạn chỉ đang chuyển Widget đó thông qua một hàm mũi tên vào một `Obx ()` ("Observer" của _Rx_).
`Obx` khá thông minh và sẽ chỉ thay đổi nếu giá trị của `controller.name` thay đổi.
Nếu `name` là` "Kaiser" `và bạn thay đổi nó thành` "Kaiser" `(` name.value = "Kaiser" `), vì nó giống như` giá trị` như trước, sẽ không có gì thay đổi trên màn hình, và `Obx`, để tiết kiệm tài nguyên, sẽ đơn giản bỏ qua giá trị mới và không xây dựng lại Widget. **Tuyệt vời ông mặt trời chứ?**
> So, what if I have 5 _Rx_ (observable) variables within an `Obx` ?
Nó sẽ chỉ cập nhật khi ** bất kỳ ** nào trong số chúng thay đổi.
> And if I have 30 variables in a class, when I update one, will it update **all** the variables that are in that class?
Không, chỉ **Widget cụ thể** sử dụng biến _Rx_ đó.
Vì vậy, **GetX** chỉ cập nhật màn hình, khi biến _Rx_ thay đổi giá trị của nó.
```
final isOpen = false.obs;
// NOTHING will happen... same value.
void onButtonTap() => isOpen.value=false;
```
### Lợi thế
**GetX()** giúp bạn khi bạn cần kiểm soát **chi tiết** đối với những gì đang được cập nhật.
Nếu bạn không cần `ID duy nhất`, vì tất cả các biến của bạn sẽ được sửa đổi khi bạn thực hiện một hành động, thì hãy sử dụng` GetBuilder`,
bởi vì nó là một Trình cập nhật state đơn giản (trong các khối, như `setState ()` '), được tạo chỉ trong một vài dòng mã.
Nó được làm đơn giản, ít ảnh hưởng đến CPU nhất và chỉ để thực hiện một mục đích duy nhất (xây dựng lại _State_) và sử dụng tài nguyên tối thiểu có thể.
Nếu bạn cần một Trình quản lý state **mạnh mẽ**, bạn không thể làm sai với **GetX**.
Nó không hoạt động với các biến, nhưng __flows__, mọi thứ trong đó đều là `Streams`.
Bạn có thể sử dụng _rxDart_ kết hợp với nó, vì mọi thứ đều là `Luồng`,
bạn có thể nghe `event` của từng" biến _Rx_ ",
bởi vì mọi thứ trong đó đều là `Streams`.
Nó thực sự là một cách tiếp cận _BLoC_, dễ dàng hơn _MobX_ và không có trình tạo code hoặc decorations.
Bạn có thể biến **mọi thứ** thành một _"Observable" _ chỉ với một `.obs`.
### Hiệu suất tối đa:
Ngoài việc có một thuật toán thông minh để xây dựng lại tối thiểu, **GetX** sử dụng trình so sánh để đảm bảo rằng Bang đã thay đổi.
Nếu bạn gặp bất kỳ lỗi nào trong ứng dụng của mình và gửi một bản thay đổi state, **GetX** sẽ đảm bảo rằng nó sẽ không gặp sự cố.
Với **GetX** State chỉ thay đổi nếu `giá trị` thay đổi.
Đó là sự khác biệt chính giữa **GetX** và việc sử dụng _ `computed` từ MobX_.
Khi kết hợp hai __observables__, và một thay đổi; trình nghe của _observable_ đó cũng sẽ thay đổi.
Với **GetX**, nếu bạn nối hai biến, `GetX ()` (tương tự như `Observer ()`) sẽ chỉ xây dựng lại nếu nó ngụ ý thay đổi state thực sự.
### Khai báo một biến phản ứng (reactive variable)
Bạn có 3 cách để thay đổi variable thành "observable".
1 - Sử dụng **`Rx{Type}`**.
``` dart
// initial value is recommended, but not mandatory
final name = RxString('');
final isLogged = RxBool(false);
final count = RxInt(0);
final balance = RxDouble(0.0);
final items = RxList<String>([]);
final myMap = RxMap<String, int>({});
```
2 - Sử dụng **`Rx`** và dùng Darts Generics, `Rx<Type>`
``` dart
final name = Rx<String>('');
final isLogged = Rx<Bool>(false);
final count = Rx<Int>(0);
final balance = Rx<Double>(0.0);
final number = Rx<Num>(0);
final items = Rx<List<String>>([]);
final myMap = Rx<Map<String, int>>({});
// Custom classes - it can be any class, literally
final user = Rx<User>();
```
3 - Cách tối ưu nhất, thêm **`.obs`** ở `value` :
``` dart
final name = ''.obs;
final isLogged = false.obs;
final count = 0.obs;
final balance = 0.0.obs;
final number = 0.obs;
final items = <String>[].obs;
final myMap = <String, int>{}.obs;
// Custom classes - it can be any class, literally
final user = User().obs;
```
##### Thât dễ khi có reactive state.
Như chúng ta biết, _Dart_ đang hướng tới _null safety_.
Để chuẩn bị, từ bây giờ, bạn phải luôn bắt đầu các biến _Rx_ của mình bằng một **initial value**.
> Transforming a variable into an _observable_ + _initial value_ with **GetX** is the simplest, and most practical approach.
Theo đúng nghĩa đen, bạn sẽ thêm một "` .obs` "vào cuối biến của mình và **vậy thôi người ơi~**, bạn đã làm cho nó có thể quan sát được, và `.value` của nó sẽ là _initial value_).
### Sử dụng values trong View
``` dart
// controller file
final count1 = 0.obs;
final count2 = 0.obs;
int get sum => count1.value + count2.value;
```
``` dart
// view file
GetX<Controller>(
builder: (controller) {
print("count 1 rebuild");
return Text('${controller.count1.value}');
},
),
GetX<Controller>(
builder: (controller) {
print("count 2 rebuild");
return Text('${controller.count2.value}');
},
),
GetX<Controller>(
builder: (controller) {
print("count 3 rebuild");
return Text('${controller.sum}');
},
),
```
Nếu chúng ta cộng `count1.value++` , nó sẽ in:
* `count 1 rebuild`
* `count 3 rebuild`
bởi vì `count1` có giá trị là` 1` và `1 + 0 = 1`, thay đổi giá trị getter` sum`.
Nếu ta thay đổi `count2.value++` , nó sẽ in:
* `count 2 rebuild`
* `count 3 rebuild`
bởi vì `count2.value` đã thay đổi, và kết quả của` sum` bây giờ là `2`.
* LƯU Ý: Theo mặc định, event đầu tiên sẽ xây dựng lại widget con, ngay cả khi nó là cùng một `giá trị`.
Hành vi này tồn tại do các biến Boolean.
Ví dụ, bạn code thế này:
``` dart
var isLogged = false.obs;
```
Và sau đó, bạn đã kiểm tra xem người dùng có "đăng nhập" để kích hoạt event trong `ever` không.
``` dart
@override
onInit(){
ever(isLogged, fireRoute);
isLogged.value = await Preferences.hasToken();
}
fireRoute(logged) {
if (logged) {
Get.off(Home());
} else {
Get.off(Login());
}
}
```
nếu `hasToken` là` false`, sẽ không có thay đổi thành `isLogged`, vì vậy `ever ()` sẽ không bao giờ được gọi.
Để tránh loại hành vi này, thay đổi đầu tiên đối với _observable_ sẽ luôn kích hoạt một event,
ngay cả khi nó chứa cùng một `.value`.
Bạn có thể xóa hành vi này nếu muốn, bằng cách sử dụng:
`isLogged.firstRebuild = false;`
### Điều kiện để tái tạo lại
Ngoài ra, Get cung cấp khả năng kiểm soát state đã được tinh chỉnh. Bạn có thể điều kiện một event (chẳng hạn như thêm một đối tượng vào danh sách), với một điều kiện nhất định.
``` dart
// First parameter: condition, must return true or false.
// Second parameter: the new value to apply if the condition is true.
list.addIf(item < limit, item);
```
Không có decoration, không có trình tạo mã, không có phức tạp hóa vấn đề: smile:
Bạn có biết ứng dụng counter của Flutter không? Class controller của bạn có thể trông giống như sau:
``` dart
class CountController extends GetxController {
final count = 0.obs;
}
```
Đơn giản hơn:
``` dart
controller.count.value++
```
Bạn có thể cập nhật counter trong UI của mình, bất kể nó được lưu trữ ở đâu.
### Nơi .obs có thể dùng
Bạn có thể biến đổi bất cứ thứ gì trên obs. Đây là hai cách để làm điều đó:
* Bạn có thể chuyển đổi các giá trị class của mình thành obs
``` dart
class RxUser {
final name = "Camila".obs;
final age = 18.obs;
}
```
* hoặc bạn có thể biến cả 1 class thành observable
``` dart
class User {
User({String name, int age});
var name;
var age;
}
// when instantianting:
final user = User(name: "Camila", age: 18).obs;
```
### Chí ú về Lists
List hoàn toàn có thể quan sát được cũng như các đối tượng bên trong nó. Bằng cách đó, nếu bạn thêm một giá trị vào danh sách, nó sẽ tự động xây dựng lại các widget con sử dụng nó.
Bạn cũng không cần phải sử dụng ".value" với các danh sách, api phi tiêu tuyệt vời đã cho phép chúng tôi loại bỏ điều đó.
Tiếc thay, các kiểu nguyên thủy như String và int không thể được mở rộng, khiến việc sử dụng .value là bắt buộc, nhưng điều đó sẽ không thành vấn đề nếu bạn làm việc với getters và setters cho những thứ này.
``` dart
// On the controller
final String title = 'User Info:'.obs
final list = List<User>().obs;
// on the view
Text(controller.title.value), // String need to have .value in front of it
ListView.builder (
itemCount: controller.list.length // lists don't need it
)
```
Khi bạn đang làm cho các class của riêng mình có thể quan sát được, có một cách khác để cập nhật chúng:
``` dart
// on the model file
// we are going to make the entire class observable instead of each attribute
class User() {
User({this.name = '', this.age = 0});
String name;
int age;
}
// on the controller file
final user = User().obs;
// when you need to update the user variable:
user.update( (user) { // this parameter is the class itself that you want to update
user.name = 'Jonny';
user.age = 18;
});
// an alternative way of update the user variable:
user(User(name: 'João', age: 35));
// on view:
Obx(()=> Text("Name ${user.value.name}: Age: ${user.value.age}"))
// you can also access the model values without the .value:
user().name; // notice that is the user variable, not the class (variable has lowercase u)
```
Bạn không cần phải làm việc với các bộ nếu bạn không muốn. bạn có thể sử dụng api "assign" và "assignAll".
Api "assign" sẽ xóa danh sách của bạn và thêm một đối tượng duy nhất mà bạn muốn bắt đầu ở đó.
Api "allowAll" sẽ xóa danh sách hiện có và thêm bất kỳ đối tượng có thể lặp lại nào mà bạn đưa vào đó.
### Tại sao tôi phải dùng .value
Chúng ta có thể loại bỏ việc sử dụng 'value' đối với `String` và` int` bằng một trình tạo mã và decoration đơn giản, nhưng mục đích của thư viện này chính là tránh các dependency bên ngoài. Chúng tôi muốn cung cấp một môi trường sẵn sàng cho việc lập trình, liên quan đến các yếu tố cần thiết (quản lý các route, dependency và state), theo cách đơn giản, nhẹ và hiệu quả mà không cần gói bên ngoài.
Theo nghĩa đen, bạn có thể thêm 3 chữ cái vào pubspec (get) của mình và dấu hai chấm và bắt đầu lập trình. Tất cả các giải pháp được bao gồm theo mặc định, từ quản lý route đến quản lý state, nhằm mục đích dễ dàng, năng suất và hiệu suất.
Tổng trọng lượng của thư viện này ít hơn của một trình quản lý state duy nhất, mặc dù nó là một giải pháp hoàn chỉnh và đó là những gì bạn phải hiểu.
Nếu bạn bị làm phiền bởi `.value`, và giống như một trình tạo mã, MobX là một giải pháp thay thế tuyệt vời và bạn có thể sử dụng nó cùng với Get. Đối với những người muốn thêm một gói dependency duy nhất vào pubspec và bắt đầu lập trình mà không cần lo lắng về phiên bản của gói không tương thích với gói khác hoặc nếu lỗi cập nhật state đến từ trình quản lý state hoặc dependency, hoặc vẫn không muốn lo lắng về sự sẵn có của controller, cho dù theo nghĩa đen là "chỉ là lập trình", GetX là lựa chọn hoàn hảo.
Nếu bạn không gặp vấn đề gì với trình tạo mã MobX hoặc không gặp vấn đề gì với bảng soạn sẵn BLoC, bạn có thể chỉ cần sử dụng Get cho các route và quên rằng nó có trình quản lý state. Get SEM và RSM ra đời không cần thiết, công ty của tôi có một dự án với hơn 90 controller và trình tạo mã chỉ mất hơn 30 phút để hoàn thành nhiệm vụ của nó sau khi Flutter Clean trên một máy khá tốt, nếu dự án của bạn có 5, 10, 15 controller, bất kỳ nhà quản lý state sẽ cung cấp cho bạn tốt. Nếu bạn có một dự án lớn đến mức ngớ ngẩn và trình tạo mã là một vấn đề đối với bạn, thì bạn đã được trao giải pháp này.
Rõ ràng, nếu ai đó muốn đóng góp vào dự án và tạo trình tạo mã, hoặc thứ gì đó tương tự, tôi sẽ liên kết trong readme này như một giải pháp thay thế, nhu cầu của tôi không phải là nhu cầu của tất cả các nhà phát triển, nhưng ý tôi là thế, đã có những giải pháp tốt đã làm được điều đó, như MobX.
### Obx()
Nhập vào Get bằng cách sử dụng Bindings là không cần thiết. bạn có thể sử dụng widget Obx thay vì GetX, widget chỉ nhận được chức năng ẩn danh tạo widget.
Rõ ràng, nếu bạn không sử dụng một kiểu, bạn sẽ cần phải có một phiên bản của controller để sử dụng các biến hoặc sử dụng `Get.find <Controller> ()` .value hoặc Controller.to.value để truy xuất giá trị .
### Workers
Workers sẽ hỗ trợ bạn, kích hoạt các lệnh gọi lại cụ thể khi một event xảy ra.
``` dart
/// Called every time `count1` changes.
ever(count1, (_) => print("$_ has been changed"));
/// Called only first time the variable $_ is changed
once(count1, (_) => print("$_ was changed once"));
/// Anti DDos - Called every time the user stops typing for 1 second, for example.
debounce(count1, (_) => print("debouce$_"), time: Duration(seconds: 1));
/// Ignore all changes within 1 second.
interval(count1, (_) => print("interval $_"), time: Duration(seconds: 1));
```
Tất cả các workers (except `debounce` ) có `condition` tham số được đặt tên, mà có thể là loại `bool` hoặc lệnh gọi lại trả về một `bool` .
`condition` này mô tả khi `callback` kích hoạt.
Tất cả các workers đều trả về trường hợp `Worker`, mà bạn có thể đóng ( thông qua `dispose()` ) của worker.
* **`ever`**
được gọi mỗi khi biến _Rx_ tạo ra một giá trị mới.
* **`everAll`**
Giống như `ever`, nhưng nó có một` List` gồm các giá trị _Rx_ Được gọi mỗi khi biến của nó bị thay đổi. Chỉ vậy thôi người ơi~ 😊
* **`once`**
'once' chỉ được gọi lần đầu tiên biến được thay đổi.
* **`debounce`**
'debounce' rất hữu ích trong các hàm tìm kiếm, nơi bạn chỉ muốn API được gọi khi người dùng nhập xong. Nếu người dùng nhập "Kaiser", bạn sẽ có 6 tìm kiếm trong các API, theo ký tự K, a, i, s, e và r. Với Get, điều này không xảy ra, bởi vì bạn sẽ có một Worker "debounce" sẽ chỉ được kích hoạt khi kết thúc nhập.
* **`interval`**
'interval' khác với debounce. Debounce xảy ra nếu người dùng thực hiện 1000 thay đổi đối với một biến trong vòng 1 giây, y sẽ chỉ gửi biến cuối cùng sau bộ hẹn giờ quy định (mặc định là 800 mili giây). Thay vào đó, interval sẽ bỏ qua tất cả các hành động của người dùng trong interval quy định. Nếu bạn gửi event trong 1 phút, 1000 mỗi giây, tính năng gỡ lỗi sẽ chỉ gửi cho bạn event cuối cùng, khi người dùng ngừng phân chia event. interval sẽ phân phối các event mỗi giây và nếu được đặt thành 3 giây, nó sẽ phân phối 20 event trong phút đó. Điều này được khuyến nghị để tránh lạm dụng, trong các chức năng mà người dùng có thể nhanh chóng nhấp vào một thứ gì đó và có được một số lợi thế (hãy tưởng tượng rằng người dùng có thể kiếm được xu bằng cách nhấp vào thứ gì đó, nếu y nhấp 300 lần trong cùng một phút, y sẽ có 300 xu, bằng cách sử dụng interval, bạn có thể đặt khung thời gian trong 3 giây, và thậm chí sau đó nhấp vào 300 hoặc một nghìn lần, tối đa y sẽ nhận được trong 1 phút sẽ là 20 xu, nhấp 300 hoặc 1 triệu lần). Việc gỡ lỗi này phù hợp cho việc chống DDos, cho các chức năng như tìm kiếm trong đó mỗi thay đổi đối với onChange sẽ gây ra một truy vấn tới api của bạn. Debounce sẽ đợi người dùng ngừng nhập tên để thực hiện yêu cầu. Nếu nó được sử dụng trong kịch bản đồng xu được đề cập ở trên, người dùng sẽ chỉ giành được 1 đồng xu, bởi vì nó chỉ được thực thi, khi người dùng "tạm dừng" trong thời gian đã thiết lập.
* CHÍ Ú: Workers phải luôn được sử dụng khi khởi động Controller hoặc Class, vì vậy nó phải luôn ở trên onInit (được khuyến nghị), phương thức khởi tạo Class hoặc initState của StatefulWidget (phương pháp này không được khuyến khích trong hầu hết các trường hợp, nhưng nó không nên có hiệu ứng phụ nào khác).
## Quản lý State đơn giản
Get có một trình quản lý state cực kỳ nhẹ và dễ dàng, không sử dụng ChangeNotifier, sẽ đáp ứng nhu cầu đặc biệt cho những người mới sử dụng Flutter và sẽ không gây ra sự cố cho các ứng dụng lớn.
GetBuilder nhắm chính xác vào việc kiểm soát nhiều state. Hãy tưởng tượng rằng bạn đã thêm 30 sản phẩm vào giỏ hàng, bạn nhấp vào xóa một sản phẩm, đồng thời danh sách được cập nhật, giá được cập nhật và huy hiệu trong giỏ hàng được cập nhật thành số lượng nhỏ hơn. Kiểu tiếp cận này khiến GetBuilder trở thành kẻ giết người, bởi vì nó nhóm các state và thay đổi tất cả chúng cùng một lúc mà không có bất kỳ "logic tính toán" nào cho điều đó. GetBuilder được tạo ra với loại tình huống này, vì để thay đổi state tạm thời, bạn có thể sử dụng setState và bạn sẽ không cần trình quản lý state cho việc này.
Bằng cách đó, nếu bạn muốn một controller riêng lẻ, bạn có thể gán ID cho controller đó hoặc sử dụng GetX. Điều này là tùy thuộc vào bạn, hãy nhớ rằng bạn càng có nhiều widget "riêng lẻ" thì hiệu suất của GetX càng nổi bật, trong khi hiệu suất của GetBuilder phải vượt trội hơn khi có nhiều thay đổi state.
### Lợi thế
1. Chỉ cập nhật các widget được yêu cầu.
2. Không sử dụng changeNotifier, đó là trình quản lý state sử dụng ít bộ nhớ hơn (gần như bằng 0mb).
3. Quên StatefulWidget! Với Get, bạn sẽ không bao giờ cần đến nó. Với các trình quản lý state khác, bạn có thể sẽ phải sử dụng StatefulWidget để lấy phiên bản của Provider, BLoC, MobX, v.v. Nhưng bạn đã bao giờ nghĩ rằng AppBar, Scaffole và hầu hết các widget trong class của bạn là stateless? Vậy tại sao phải lưu state của toàn bộ class, nếu bạn chỉ có thể lưu state của Widget là stateful? Get giải quyết được điều đó bằng cách tạo một class Stateless, làm cho mọi thứ trở nên vô trạng. Nếu bạn cần cập nhật một thành phần riêng lẻ, hãy bọc nó bằng GetBuilder và state của nó sẽ được duy trì.
4. Tái cơ cấu cho dự án của bạn xanh, sạch và đẹp! Controller không được nằm trong UI của bạn, hãy đặt TextEditController của bạn hoặc bất kỳ controller nào bạn sử dụng trong class Controller của mình.
5. Bạn có cần kích hoạt event để cập nhật widget con ngay khi nó được hiển thị không? GetBuilder có thuộc tính "initState", giống như StatefulWidget và bạn có thể gọi các event từ controller của mình, trực tiếp từ nó, không có thêm event nào được đặt trong initState của bạn.
6. Bạn có cần phải kích hoạt một hành động như đóng streams, hẹn giờ, v.v. không? GetBuilder cũng có dispose property, nơi bạn có thể gọi các event ngay khi widget đó bị phá hủy.
7. Chỉ sử dụng các streams nếu cần thiết. Bạn có thể sử dụng StreamControllers bên trong controller của mình một cách bình thường và sử dụng StreamBuilder cũng bình thường, nhưng hãy nhớ rằng, một streams tiêu thụ bộ nhớ một kha khá, lập trình phản ứng rất đẹp, nhưng bạn không nên lạm dụng nó. 30 streams mở cùng lúc có thể tệ hơn changeNotifier (và changeNotifier đã rất là tệ).
8. Cập nhật các widgets mà không tốn ram. Chỉ lưu trữ ID người tạo GetBuilder và cập nhật GetBuilder đó khi cần thiết. Mức tiêu thụ bộ nhớ của get ID trong bộ nhớ là rất thấp ngay cả đối với hàng nghìn GetBuilders. Khi bạn tạo GetBuilder mới, bạn thực sự đang chia sẻ state GetBuilder có ID người tạo. Một state mới không được tạo cho mỗi GetBuilder, giúp tiết kiệm RẤT NHIỀU ram cho các ứng dụng lớn. Về cơ bản, ứng dụng của bạn sẽ hoàn toàn là Không state và một số ít Tiện ích sẽ có state (trong GetBuilder) sẽ có một state duy nhất, và do đó cập nhật một sẽ cập nhật tất cả. Nhà nước chỉ là một.
9. Get là toàn trí và trong hầu hết các trường hợp, nó biết chính xác thời gian để lấy controller ra khỏi bộ nhớ. Bạn không nên lo lắng về việc khi nào nên vứt bỏ controller, Get biết thời điểm tốt nhất để thực hiện việc này.
### Sử dụng
``` dart
// Create controller class and extends GetxController
class Controller extends GetxController {
int counter = 0;
void increment() {
counter++;
update(); // use update() to update counter variable on UI when increment be called
}
}
// On your Stateless/Stateful class, use GetBuilder to update Text when increment be called
GetBuilder<Controller>(
init: Controller(), // INIT IT ONLY THE FIRST TIME
builder: (_) => Text(
'${_.counter}',
),
)
//Initialize your controller only the first time. The second time you are using ReBuilder for the same controller, do not use it again. Your controller will be automatically removed from memory as soon as the widget that marked it as 'init' is deployed. You don't have to worry about that, Get will do it automatically, just make sure you don't start the same controller twice.
```
**OK, giải thích xong rồi!**
* Bạn đã học cách quản lý state với Get.
* Lưu ý: Bạn có thể muốn một tổ chức lớn hơn và không sử dụng thuộc tính init. Vì vậy, bạn có thể tạo một class và mở rộng class Bindings và trong đó đề cập đến các controller sẽ được tạo trong route đó. Khi đó các Controllers sẽ không được tạo, ngược lại, đây chỉ là một câu lệnh, để lần đầu sử dụng Controller, Get sẽ biết cần tìm ở đâu. Get sẽ vẫn là lazyLoad và sẽ tiếp tục loại bỏ Controller khi chúng không còn cần thiết nữa. Hãy xem ví dụ pub.dev để xem nó hoạt động như thế nào.
Nếu bạn điều hướng nhiều route và cần dữ liệu trong controller đã sử dụng trước đó, bạn chỉ cần sử dụng GetBuilder Again (không có init):
``` dart
class OtherClass extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: GetBuilder<Controller>(
builder: (s) => Text('${s.counter}'),
),
),
);
}
```
Nếu bạn cần sử dụng controller của mình ở nhiều nơi khác và bên ngoài GetBuilder, chỉ cần tạo quyền truy cập vào controller của bạn và có nó một cách dễ dàng. (hoặc sử dụng `Get.find <Controller> ()`)
``` dart
class Controller extends GetxController {
/// You do not need that. I recommend using it just for ease of syntax.
/// with static method: Controller.to.increment();
/// with no static method: Get.find<Controller>().increment();
/// There is no difference in performance, nor any side effect of using either syntax. Only one does not need the type, and the other the IDE will autocomplete it.
static Controller get to => Get.find(); // add this line
int counter = 0;
void increment() {
counter++;
update();
}
}
```
Sau đó, truy cập thẳng vào controller của bạn:
``` dart
FloatingActionButton(
onPressed: () {
Controller.to.increment(),
} // This is incredibly simple!
child: Text("${Controller.to.counter}"),
),
```
Khi bạn nhấn FloatingActionButton, tất cả các widget đang lắng nghe biến 'counter' sẽ được cập nhật tự động.
### Cách GetX sử dụng controllers
Ví dụ:
`Class a => Class B (has controller X) => Class C (has controller X)`
Trong class A, controller chưa có trong bộ nhớ, vì bạn chưa sử dụng nó (Get là lazyLoad). Trong class B, bạn đã sử dụng controller và nó đã vào bộ nhớ. Trong class C, bạn đã sử dụng cùng một controller như trong class B, Get sẽ chia sẻ state của controller B với controller C, và controller tương tự vẫn còn trong bộ nhớ. Nếu bạn đóng màn hình C và màn hình B, Get sẽ tự động lấy controller X ra khỏi bộ nhớ và giải phóng tài nguyên, vì Class A không sử dụng controller. Nếu bạn điều hướng đến B một lần nữa, controller X sẽ nhập lại bộ nhớ, nếu thay vì đi đến class C, bạn quay lại class A một lần nữa, Get sẽ đưa controller ra khỏi bộ nhớ theo cách tương tự. Nếu class C không sử dụng controller và bạn đã lấy class B ra khỏi bộ nhớ, thì sẽ không có class nào sử dụng controller X và tương tự như vậy, nó sẽ bị loại bỏ. Ngoại lệ duy nhất có thể gây rắc rối với Get là nếu bạn xóa B khỏi route một cách bất ngờ và cố gắng sử dụng controller trong C. Trong trường hợp này, ID người tạo của controller ở B đã bị xóa và Get được lập trình để xóa nó khỏi bộ nhớ mọi controller không có ID người tạo. Nếu bạn dự định làm điều này, hãy thêm flag "autoRemove: false" vào GetBuilder của class B và sử dụng adoptID = true trong GetBuilder của class C.
### Không cần StatefulWidget nữa!
Sử dụng StatefulWidgets có nghĩa là lưu trữ state của toàn bộ màn hình một cách không cần thiết, ngay cả khi bạn cần xây dựng lại một cách tối thiểu widget, bạn sẽ nhúng nó vào Consumer / Observer / BlocProvider / GetBuilder / GetX / Obx, đây sẽ là một StatefulWidget khác.
Class StatefulWidget là một class lớn hơn StatelessWidget, class này sẽ phân bổ nhiều RAM hơn và điều này có thể không tạo ra sự khác biệt đáng kể giữa một hoặc hai class, nhưng chắc chắn nó sẽ làm được khi bạn có 100 class trong số chúng!
Trừ khi bạn cần sử dụng một mixin, như TickerProviderStateMixin, thì việc sử dụng StatefulWidget với Get là hoàn toàn không cần thiết.
Bạn có thể gọi trực tiếp tất cả các phương thức của StatefulWidget từ GetBuilder.
Nếu bạn cần gọi phương thức initState () hoặc dispose () chẳng hạn, bạn có thể gọi chúng trực tiếp;
``` dart
GetBuilder<Controller>(
initState: (_) => Controller.to.fetchApi(),
dispose: (_) => Controller.to.closeStreams(),
builder: (s) => Text('${s.username}'),
),
```
Một cách tiếp cận tốt hơn nhiều so với cách này là sử dụng phương thức onInit () và onClose () trực tiếp từ controller của bạn.
``` dart
@override
void onInit() {
fetchApi();
super.onInit();
}
```
* CHÍ Ú: Nếu bạn muốn bắt đầu một phương thức tại thời điểm controller được gọi lần đầu tiên, bạn KHÔNG CẦN sử dụng các hàm tạo cho việc này, trên thực tế, bằng cách sử dụng gói hướng hiệu suất như Get, điều này không phù hợp với thực tiễn xấu, bởi vì nó lệch khỏi logic trong đó controller được tạo hoặc chỉ định (nếu bạn tạo một phiên bản của controller này, hàm tạo sẽ được gọi ngay lập tức, bạn sẽ điền controller trước khi nó được sử dụng, bạn đang cấp phát bộ nhớ mà không sử dụng nó , điều này chắc chắn làm hỏng các nguyên tắc của thư viện này). Các phương thức onInit(); và onClose(); được tạo ra cho mục đích này, chúng sẽ được gọi khi Controller được tạo hoặc được sử dụng lần đầu tiên, tùy thuộc vào việc bạn có đang sử dụng Get.lazyPut hay không. Ví dụ: nếu bạn muốn thực hiện lệnh gọi tới API của mình để điền dữ liệu, bạn có thể quên phương thức cũ của initState / dispose, chỉ cần bắt đầu lệnh gọi tới api trong onInit. Nếu bạn cần thực thi bất kỳ lệnh nào, như đóng streams, hãy sử dụng onClose() cho việc đó.
### Tại sao GetX tồn tại?
Mục đích của gói này chính xác là cung cấp cho bạn một giải pháp hoàn chỉnh để điều hướng các route, quản lý các dependency và state, sử dụng các dependency ít nhất có thể, với mức độ tách biệt cao. Nhận tất cả các API Flutter cấp cao và cấp thấp trong chính nó, để đảm bảo rằng bạn làm việc với ít khớp nối nhất có thể. Chúng tôi tập trung mọi thứ trong một gói duy nhất, để đảm bảo rằng bạn không có bất kỳ loại khớp nối nào trong dự án của mình. Bằng cách đó, bạn có thể chỉ đặt các widget trong chế độ xem của mình và để phần của nhóm làm việc với logic nghiệp vụ tự do làm việc với logic nghiệp vụ độc lập với View. Điều này cung cấp một môi trường làm việc sạch hơn nhiều, để một phần nhóm của bạn chỉ hoạt động với các widget mà không phải lo lắng về việc gửi dữ liệu đến controller của bạn và một phần nhóm của bạn chỉ làm việc với logic nghiệp vụ trong phạm vi bề rộng của nó mà không dependency vào bất kỳ yếu tố View.
Vì vậy, để đơn giản hóa điều này:
Bạn không cần gọi các phương thức trong initState và gửi chúng theo tham số đến controller của mình, cũng như không sử dụng phương thức khởi tạo controller cho việc đó, bạn có phương thức onInit() được gọi vào đúng thời điểm để bạn khởi động các dịch vụ của mình.
Bạn không cần phải gọi thiết bị, bạn có phương thức onClose() sẽ được gọi vào thời điểm chính xác khi controller của bạn không còn cần thiết nữa và sẽ bị xóa khỏi bộ nhớ. Bằng cách đó, chỉ để lại chế độ xem cho các widget, tránh bất kỳ loại logic nghiệp vụ nào từ nó.
Đừng gọi một phương thức vứt bỏ bên trong GetxController, nó sẽ không làm được gì cả, hãy nhớ rằng controller không phải là một Widget, bạn không nên "vứt bỏ" nó, và nó sẽ được Get tự động và thông minh xóa khỏi bộ nhớ. Nếu bạn đã sử dụng bất kỳ streams nào trên đó và muốn đóng streams đó, chỉ cần chèn streams đó vào phương thức đóng. Thí dụ:
``` dart
class Controller extends GetxController {
StreamController<User> user = StreamController<User>();
StreamController<String> name = StreamController<String>();
/// close stream = onClose method, not dispose.
@override
void onClose() {
user.close();
name.close();
super.onClose();
}
}
```
Vòng đời của controller:
* onInit() nơi nó được tạo.
* onClose() nơi nó được đóng để thực hiện bất kỳ thay đổi nào nhằm chuẩn bị cho phương thức xóa
* deleted: bạn không có quyền truy cập vào API này vì nó sẽ xóa controller khỏi bộ nhớ theo đúng nghĩa đen. Nó được xóa theo đúng nghĩa đen, mà không để lại bất kỳ dấu vết nào.
### Cách sử dụng khác
Bạn có thể sử dụng phiên bản Controller trực tiếp trên giá trị GetBuilder:
``` dart
GetBuilder<Controller>(
init: Controller(),
builder: (value) => Text(
'${value.counter}', //here
),
),
```
Bạn cũng có thể cần một phiên bản của controller bên ngoài GetBuilder và bạn có thể sử dụng các phương pháp này để đạt được điều này:
``` dart
class Controller extends GetxController {
static Controller get to => Get.find();
[...]
}
// on you view:
GetBuilder<Controller>(
init: Controller(), // use it only first time on each controller
builder: (_) => Text(
'${Controller.to.counter}', //here
)
),
```
or
``` dart
class Controller extends GetxController {
// static Controller get to => Get.find(); // with no static get
[...]
}
// on stateful/stateless class
GetBuilder<Controller>(
init: Controller(), // use it only first time on each controller
builder: (_) => Text(
'${Get.find<Controller>().counter}', //here
),
),
```
* Bạn có thể sử dụng các phương pháp tiếp cận "không chuẩn" để thực hiện việc này. Nếu bạn đang sử dụng một số trình quản lý dependency khác, như get_it, modular, v.v. và chỉ muốn cung cấp phiên bản controller, bạn có thể thực hiện điều này:
``` dart
Controller controller = Controller();
[...]
GetBuilder<Controller>(
init: controller, //here
builder: (_) => Text(
'${controller.counter}', // here
),
),
```
### IDs độc nhất
Nếu bạn muốn tinh chỉnh kiểm soát cập nhật của widget con với GetBuilder, bạn có thể gán cho chúng các ID độc:
``` dart
GetBuilder<Controller>(
id: 'text'
init: Controller(), // use it only first time on each controller
builder: (_) => Text(
'${Get.find<Controller>().counter}', //here
),
),
```
Và cập nhật nó vào biểu mẫu này:
``` dart
update(['text']);
```
Bạn cũng có thể áp đặt các điều kiện cho bản cập nhật:
``` dart
update(['text'], counter < 10);
```
GetX thực hiện điều này tự động và chỉ cấu trúc lại widget con sử dụng biến chính xác đã được thay đổi, nếu bạn thay đổi một biến thành giống với biến trước đó và điều đó không ngụ ý thay đổi state, GetX sẽ không xây dựng lại widget con để tiết kiệm bộ nhớ và Chu kỳ CPU ( 3 đang được hiển thị trên màn hình và bạn lại thay đổi biến thành 3. Trong hầu hết các trình quản lý state, điều này sẽ gây ra việc xây dựng lại mới, nhưng với GetX, widget sẽ chỉ được xây dựng lại, nếu trên thực tế state của nó đã thay đổi ).
## Trộn hai trình quản lý state
Một số người đã mở một yêu cầu tính năng, vì họ chỉ muốn sử dụng một loại biến phản ứng và cơ chế khác và cần chèn Obx vào GetBuilder cho việc này. Suy nghĩ về nó MixinBuilder đã được tạo ra. Nó cho phép cả những thay đổi phản ứng bằng cách thay đổi các biến ".obs" và cập nhật thủ công thông qua update(). Tuy nhiên, trong số 4 widget, nó là widget tiêu tốn nhiều tài nguyên nhất, vì ngoài việc có Subscription để nhận các event thay đổi từ con mình, nó còn đăng ký phương thức cập nhật của controller của mình.
Việc mở rộng GetxController rất quan trọng, vì chúng có vòng đời và có thể "bắt đầu" và "kết thúc" các event trong các phương thức onInit() và onClose() của chúng. Bạn có thể sử dụng bất kỳ lớp nào cho việc này, nhưng tôi thực sự khuyên bạn nên sử dụng lớp GetxController để đặt các biến của bạn, cho dù chúng có thể quan sát được hay không.
## StateMixin
Một cách khác để xử lý state `UI` của bạn là sử dụng` StateMixin <T> `.
Để triển khai nó, hãy sử dụng dấu `với` để thêm` StateMixin <T> ` bộ điều khiển của bạn cho phép một mô hình T.
``` dart
class Controller extends GetController with StateMixin<User>{}
```
Phương thức `change()` thay đổi state bất cứ khi nào chúng ta muốn.
Chỉ cần truyền dữ liệu và state theo cách này:
```dart
change(data, status: RxStatus.success());
```
RxStatus cho phép các state này:
``` dart
RxStatus.loading();
RxStatus.success();
RxStatus.empty();
RxStatus.error('message');
```
Để diễn tả nó trên UI, sử dụng:
```dart
class OtherClass extends GetView<Controller> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: controller.obx(
(state)=>Text(state.name),
// here you can put your custom loading indicator, but
// by default would be Center(child:CircularProgressIndicator())
onLoading: CustomLoadingIndicator(),
onEmpty: Text('No data found'),
// here also you can set your own error widget, but by
// default will be an Center(child:Text(error))
onError: (error)=>Text(error),
),
);
}
```
## GetBuilder vs GetX vs Obx vs MixinBuilder
Trong một thập kỷ làm việc với lập trình, tôi đã có thể học được một số bài học quý giá.
Lần đầu tiên tôi tiếp xúc với lập trình phản ứng là rất "Trời thần ơi, tuyệt vời ông mặt trời!" và trên thực tế, lập trình phản ứng là không thể tin được.
Tuy nhiên, nó không phải là thích hợp cho tất cả các trường hợp. Thông thường, tất cả những gì bạn cần là thay đổi state của 2 hoặc 3 widget cùng lúc, hoặc thay đổi state trong thời gian ngắn, trong trường hợp này, lập trình phản ứng không phải là xấu, nhưng nó không phù hợp.
Lập trình phản ứng có mức tiêu thụ RAM cao hơn có thể được bù đắp bởi quy trình làm việc riêng lẻ, điều này sẽ đảm bảo rằng chỉ một widget con được xây dựng lại và khi cần thiết, nhưng tạo danh sách với 80 đối tượng, mỗi đối tượng có nhiều streams không phải là một ý kiến hay . Mở thanh kiểm tra phi tiêu và kiểm tra xem StreamBuilder tiêu thụ bao nhiêu và bạn sẽ hiểu những gì tôi đang cố gắng nói với bạn.
Với ý nghĩ đó, tôi đã tạo trình quản lý state đơn giản. Nó đơn giản, và đó chính xác là những gì bạn cần ở nó: cập nhật state trong các khối theo cách đơn giản và tiết kiệm nhất.
GetBuilder rất tiết kiệm RAM và khó có cách tiếp cận nào tiết kiệm hơn nó (ít nhất là tôi không thể tưởng tượng được, nếu đã tồn tại cách khác, vui lòng cho chúng tôi biết).
Tuy nhiên, GetBuilder vẫn là một trình quản lý state cơ học, bạn cần phải gọi update () giống như bạn sẽ cần gọi tới Provider's InformListaries ().
Có những tình huống khác mà lập trình phản ứng thực sự thú vị và nếu không dùng nó đồng nghĩa như đang phát minh lại cái bánh xe. Với suy nghĩ đó, GetX được tạo ra để cung cấp mọi thứ hiện đại và tiên tiến nhất trong một trình quản lý state. Nó chỉ cập nhật những gì cần thiết và khi cần thiết, nếu bạn gặp lỗi và gửi 300 state thay đổi đồng thời, GetX sẽ lọc và cập nhật màn hình chỉ khi state thực sự thay đổi.
GetX vẫn tiết kiệm hơn bất kỳ trình quản lý state phản ứng nào khác, nhưng nó tiêu tốn nhiều RAM hơn GetBuilder một chút. Suy nghĩ về điều đó và hướng tới việc tiêu thụ tối đa tài nguyên mà Obx đã tạo ra. Không giống như GetX và GetBuilder, bạn sẽ không thể khởi tạo controller bên trong Obx, nó chỉ là một Widget với StreamSubscription nhận các event thay đổi từ con bạn, vậy thôi. Nó tiết kiệm hơn GetX, nhưng thua GetBuilder, điều được mong đợi, vì nó có tính phản ứng và GetBuilder có cách tiếp cận đơn giản nhất tồn tại, đó là lưu trữ hashCode của widget con và StateSetter của nó. Với Obx, bạn không cần phải viết loại controller của mình và bạn có thể nghe thấy sự thay đổi từ nhiều controller khác nhau, nhưng nó cần được khởi tạo trước đó, sử dụng phương pháp ví dụ ở đầu readme này hoặc sử dụng class Bindings.

View File

@@ -0,0 +1,375 @@
# #依赖管理
- [依赖管理](#依赖管理)
- [实例方法](#实例方法)
- [Get.put()](#Get.put())
- [Get.lazyPut](#Get.lazyPut)
- [Get.putAsync](#Get.putAsync)
- [Get.create](#Get.create)
- [使用实例化方法/类](#使用实例化方法/类)
- [方法之间的差异](#方法之间的差异)
- [Bindings](#Bindings)
- [Bindings类](#Bindings类)
- [BindingsBuilder](#BindingsBuilder)
- [智能管理](#智能管理)
- [如何改变](#如何改变)
- [SmartManagement.full](#smartmanagementfull)
- [SmartManagement.onlyBuilders](#SmartManagement.onlyBuilders)
- [SmartManagement.keepFactory](#smartmanagementkeepFactory)
- [Bindings的工作原理](#Bindings的工作原理)
- [注释](#注释)
Get有一个简单而强大的依赖管理器它允许你只用1行代码就能检索到与你的Bloc或Controller相同的类无需Provider上下文无需 inheritedWidget。
```dart
Controller controller = Get.put(Controller()); // 而不是 Controller controller = Controller();
```
你是在Get实例中实例化它而不是在你正在使用的类中实例化你的类这将使它在整个App中可用。
所以你可以正常使用你的控制器或Bloc类
- 注意如果你使用的是Get的状态管理器请多关注[Bindings](#Bindings)api这将使你的视图更容易连接到你的控制器。
- 注意事项²。Get的依赖管理与包中的其他部分是分离的所以如果你的应用已经使用了一个状态管理器任何一个都没关系你不需要修改也可以同时使用这个依赖注入管理器完全没有问题。
## 实例方法
这些方法和它的可配置参数是:
### Get.put()
最常见的插入依赖关系的方式。例如,对于你的视图的控制器来说:
```dart
Get.put<SomeClass>(SomeClass());
Get.put<LoginController>(LoginController(), permanent: true);
Get.put<ListItemController>(ListItemController, tag: "some unique string");
```
这是你使用put时可以设置的所有选项。
```dart
Get.put<S>(
// 必备:你想得到保存的类,比如控制器或其他东西。
// 注:"S "意味着它可以是任何类型的类。
S dependency
// 可选:当你想要多个相同类型的类时,可以用这个方法。
// 因为你通常使用Get.find<Controller>()来获取一个类。
// 你需要使用标签来告诉你需要哪个实例。
// 必须是唯一的字符串
String tag,
// 可选默认情况下get会在实例不再使用后进行销毁
// 例如一个已经销毁的视图的Controller)
// 但你可能需要这个实例在整个应用生命周期中保留在那里就像一个sharedPreferences的实例或其他东西。
//所以你设置这个选项
// 默认值为false
bool permanent = false,
// 可选:允许你在测试中使用一个抽象类后,用另一个抽象类代替它,然后再进行测试。
// 默认为false
bool overrideAbstract = false,
// 可选允许你使用函数而不是依赖dependency本身来创建依赖。
// 这个不常用
InstanceBuilderCallback<S> builder,
)
```
### Get.lazyPut
可以懒加载一个依赖这样它只有在使用时才会被实例化。这对于计算代价高的类来说非常有用或者如果你想在一个地方实例化几个类比如在Bindings类中而且你知道你不会在那个时候使用这个类。
```dart
///只有当第一次使用Get.find<ApiMock>时ApiMock才会被调用。
Get.lazyPut<ApiMock>(() => ApiMock());
Get.lazyPut<FirebaseAuth>(
() {
// ... some logic if needed
return FirebaseAuth();
},
tag: Math.random().toString(),
fenix: true
)
Get.lazyPut<Controller>( () => Controller() )
```
这是你在使用lazyPut时可以设置的所有选项。
```dart
Get.lazyPut<S>(
// 强制性:当你的类第一次被调用时,将被执行的方法。
InstanceBuilderCallback builder,
// 可选和Get.put()一样,当你想让同一个类有多个不同的实例时,就会用到它。
// 必须是唯一的
String tag,
// 可选:类似于 "永久"
// 不同的是当不使用时实例会被丢弃但当再次需要使用时Get会重新创建实例
// 就像 bindings api 中的 "SmartManagement.keepFactory "一样。
// 默认值为false
bool fenix = false
)
```
### Get.putAsync
如果你想注册一个异步实例,你可以使用`Get.putAsync`
```dart
Get.putAsync<SharedPreferences>(() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setInt('counter', 12345);
return prefs;
});
Get.putAsync<YourAsyncClass>( () async => await YourAsyncClass() )
```
这都是你在使用putAsync时可以设置的选项。
```dart
Get.putAsync<S>(
// 必备:一个将被执行的异步方法,用于实例化你的类。
AsyncInstanceBuilderCallback<S> builder,
// 可选和Get.put()一样,当你想让同一个类有多个不同的实例时,就会用到它。
// 必须是唯一的
String tag,
// 可选与Get.put()相同,当你需要在整个应用程序中保持该实例的生命时使用。
// 默认值为false
bool permanent = false
)
```
### Get.create
这个就比较棘手了。关于这个是什么和其他的区别,可以在[方法之间的差异](#方法之间的差异)部分找到详细的解释。
```dart
Get.Create<SomeClass>(() => SomeClass());
Get.Create<LoginController>(() => LoginController());
```
这是你在使用create时可以设置的所有选项。
```dart
Get.create<S>(
// 需要:一个返回每次调用"Get.find() "都会被新建的类的函数。
// 示例: Get.create<YourClass>(()=>YourClass())
FcBuilderFunc<S> builder,
// 可选就像Get.put()一样,但当你需要多个同类的实例时,会用到它。
// 当你有一个列表,每个项目都需要自己的控制器时,这很有用。
// 需要是一个唯一的字符串。只要把标签改成名字
String name,
// 可选:就像 Get.put() 一样,
// 它是为了当你需要在整个应用中保活实例的时候
// 区别在于 Get.create 的 permanent默认为true
bool permanent = true
```
## 使用实例化方法/类
想象一下你已经浏览了无数条路由现在你需要拿到一个被遗留在控制器中的数据那么你会需要一个状态管理器与Provider或Get_it相结合对吗用Get则不然你只需要让Get为你的控制器自动 "寻找",你不需要任何额外的依赖关系。
```dart
final controller = Get.find<Controller>();
// 或者
Controller controller = Get.find();
// 是的它看起来像魔术Get会找到你的控制器并将其提供给你。
// 你可以实例化100万个控制器Get总会给你正确的控制器。
```
然后你就可以恢复你在后面获得的控制器数据。
```dart
Text(controller.textFromApi);
```
由于返回的值是一个正常的类,你可以做任何你想做的事情。
```dart
int count = Get.find<SharedPreferences>().getInt('counter');
print(count); // out: 12345
```
移除一个Get实例:
```dart
Get.delete<Controller>(); //通常你不需要这样做因为GetX已经删除了未使用的控制器。
```
## 方法之间的差异
首先让我们来看看Get.lazyPut的 "fenix "和其他方法的 "permanent"。
`permanent``fenix`的根本区别在于你想如何存储实例。
强化默认情况下GetX会在不使用实例时删除它们。
这意味着 如果页面1有控制器1页面2有控制器2而你从堆栈中删除了第一个路由比如你使用`Get.off()``Get.offNamed()`控制器1失去了它的使用所以它将被删除。
但是如果你想选择使用`permanent:true`,那么控制器就不会在这个过渡中丢失--这对于你想在整个应用程序中保持生命的服务来说非常有用。
`fenix`则是针对那些你不担心在页面变化之间丢失的服务,但当你需要该服务时,你希望它还活着。所以基本上,它会处理未使用的控制器/服务/类,但当你需要它时,它会 "从灰烬中重新创建 "一个新的实例。
继续说说方法之间的区别:
- Get.put和Get.putAsync的创建顺序是一样的不同的是第二个方法使用的是异步方法创建和初始化实例。put是直接插入内存使用内部方法`insert`,参数`permanent: false``isSingleton: true`这个isSingleton参数只是告诉它是使用`dependency`上的依赖,还是使用`FcBuilderFunc`上的依赖),之后,调用`Get.find()`,立即初始化内存中的实例。
- Get.create。顾名思义它将 "创建 "你的依赖关系!类似于`Get.put()`。与`Get.put()`类似,它也会调用内部方法`insert`来实例化。但是`permanent`变成了true`isSingleton`变成了false因为我们是在 "创建 "我们的依赖关系所以它没有办法成为一个单例这就是为什么是false。因为它有`permanent: true`,所以我们默认的好处是不会在页面跳转之间销毁它。另外,`Get.find()`并不是立即被调用,而是等待在页面中被调用,这样创建是为了利用 "permanent "这个参数,值得注意的是,`Get.create()`的目标是创建不共享的实例但不会被销毁比如listView中的一个按钮你想为该列表创建一个唯一的实例--正因为如此Get.create必须和GetWidget一起使用。
- Get.lazyPut。顾名思义这是一个懒加载的过程。实例被创建了但它并没有被调用来立即使用而是一直等待被调用。与其他方法相反这里没有调用 "insert"。取而代之的是,实例被插入到内存的另一个部分,这个部分负责判断实例是否可以被重新创建,我们称之为 "工厂"。如果我们想创建一些以后使用的东西,它不会和现在使用的东西混在一起。这就是 "fenix "的魔力所在:如果你选择留下 "fenix: false",并且你的 "smartManagement "不是 "keepFactory",那么当使用 "Get.find "时,实例将把内存中的位置从 "工厂 "改为普通实例内存区域。紧接着,默认情况下,它将从 "工厂 "中移除。现在,如果你选择 "fenix: true",实例将继续存在这个专用的部分,甚至进入公共区域,以便将来再次被调用。
## Bindings
这个包最大的区别之一,也许就是可以将路由、状态管理器和依赖管理器完全集成。
当一个路由从Stack中移除时所有与它相关的控制器、变量和对象的实例都会从内存中移除。如果你使用的是流或定时器它们会自动关闭你不必担心这些。
在2.10版本中Get完全实现了Bindings API。
现在你不再需要使用init方法了。如果你不想的话你甚至不需要键入你的控制器。你可以在适当的地方启动你的控制器和服务来实现。
Binding类是一个将解耦依赖注入的类同时 "Bindings "路由到状态管理器和依赖管理器。
这使得Get可以知道当使用某个控制器时哪个页面正在显示并知道在哪里以及如何销毁它。
此外Binding类将允许你拥有SmartManager配置控制。你可以配置依赖关系当从堆栈中删除一个路由时或者当使用它的widget被布置时或者两者都不布置。你将有智能依赖管理为你工作但即使如此你也可以按照你的意愿进行配置。
### Bindings类
- 创建一个类并实现Binding
```dart
class HomeBinding implements Bindings {}
```
你的IDE会自动要求你重写 "dependencies"方法,然后插入你要在该路由上使用的所有类。
```dart
class HomeBinding implements Bindings {
@override
void dependencies() {
Get.lazyPut<HomeController>(() => HomeController());
Get.put<Service>(()=> Api());
}
}
class DetailsBinding implements Bindings {
@override
void dependencies() {
Get.lazyPut<DetailsController>(() => DetailsController());
}
}
```
现在你只需要通知你的路由,你将使用该 Binding 来建立路由管理器、依赖关系和状态之间的连接。
- 使用别名路由:
```dart
getPages: [
GetPage(
name: '/',
page: () => HomeView(),
binding: HomeBinding(),
),
GetPage(
name: '/details',
page: () => DetailsView(),
binding: DetailsBinding(),
),
];
```
- 使用正常路由。
```dart
Get.to(Home(), binding: HomeBinding());
Get.to(DetailsView(), binding: DetailsBinding())
```
至此你不必再担心你的应用程序的内存管理Get将为你做这件事。
Binding类在调用路由时被调用你可以在你的GetMaterialApp中创建一个 "initialBinding "来插入所有将要创建的依赖关系。
```dart
GetMaterialApp(
initialBinding: SampleBind(),
home: Home(),
);
```
### BindingsBuilder
创建Bindings的默认方式是创建一个实现Bindings的类但是你也可以使用`BindingsBuilder`回调,这样你就可以简单地使用一个函数来实例化任何你想要的东西。
例子:
```dart
getPages: [
GetPage(
name: '/',
page: () => HomeView(),
binding: BindingsBuilder(() {
Get.lazyPut<ControllerX>(() => ControllerX());
Get.put<Service>(()=> Api());
}),
),
GetPage(
name: '/details',
page: () => DetailsView(),
binding: BindingsBuilder(() {
Get.lazyPut<DetailsController>(() => DetailsController());
}),
),
];
```
这样一来,你就可以避免为每条路径创建一个 Binding 类,使之更加简单。
两种方式都可以完美地工作,我们希望您使用最适合您的风格。
### 智能管理
GetX 默认情况下会将未使用的控制器从内存中移除。
但是如果你想改变GetX控制类的销毁方式你可以用`SmartManagement`类设置不同的行为。
#### 如何改变
如果你想改变这个配置(你通常不需要),就用这个方法。
```dart
void main () {
runApp(
GetMaterialApp(
smartManagement: SmartManagement.onlyBuilders //这里
home: Home(),
)
)
}
```
#### SmartManagement.full
这是默认的。销毁那些没有被使用的、没有被设置为永久的类。在大多数情况下你会希望保持这个配置不受影响。如果你是第一次使用GetX那么不要改变这个配置。
#### SmartManagement.onlyBuilders
使用该选项,只有在`init:`中启动的控制器或用`Get.lazyPut()`加载到Binding中的控制器才会被销毁。
如果你使用`Get.put()``Get.putAsync()`或任何其他方法SmartManagement将没有权限移除这个依赖。
在默认行为下,即使是用 "Get.put "实例化的widget也会被移除这与SmartManagement.onlyBuilders不同。
#### SmartManagement.keepFactory
就像SmartManagement.full一样当它不再被使用时它将删除它的依赖关系但它将保留它们的工厂这意味着如果你再次需要该实例它将重新创建该依赖关系。
### Bindings的工作原理
Bindings会创建过渡性工厂在你点击进入另一个页面的那一刻这些工厂就会被创建一旦换屏动画发生就会被销毁。
这种情况发生得非常快,以至于分析器甚至都来不及注册。
当你再次导航到这个页面时一个新的临时工厂将被调用所以这比使用SmartManagement.keepFactory更可取但如果你不想创建Bindings或者想让你所有的依赖关系都在同一个Binding上它肯定会帮助你。
Factories占用的内存很少它们并不持有实例而是一个具有你想要的那个类的 "形状 "的函数。
这在内存上的成本很低但由于这个库的目的是用最少的资源获得最大的性能所以Get连工厂都默认删除。
请使用对你来说最方便的方法。
## 注释
- 如果你使用多个Bindings不要使用SmartManagement.keepFactory。它被设计成在没有Bindings的情况下使用或者在GetMaterialApp的初始Binding中链接一个Binding。
- 使用Bindings是完全可选的你也可以在使用给定控制器的类上使用`Get.put()``Get.find()`
然而如果你使用Services或任何其他抽象我建议使用Bindings来更好地组织。

View File

@@ -0,0 +1,556 @@
- [路由管理](#路由管理)
- [如何使用](#如何使用)
- [普通路由导航](#普通路由导航)
- [别名路由导航](#别名路由导航)
- [发送数据到别名路由](#发送数据到别名路由)
- [动态网页链接](#动态网页链接)
- [中间件](#中间件)
- [免context导航](#免context导航)
- [SnackBars](#SnackBars)
- [Dialogs](#dialogs)
- [BottomSheets](#bottomSheets)
- [嵌套导航](#嵌套导航)
# 路由管理
这是关于Getx在路由管理方面的完整解释。
## 如何使用
将此添加到你的pubspec.yaml文件中。
```yaml
dependencies:
get:
```
如果你要在没有context的情况下使用路由/SnackBars/Dialogs/BottomSheets或者使用高级的Get API你只需要在你的MaterialApp前面加上 "Get"就可以把它变成GetMaterialApp享受吧!
```dart
GetMaterialApp( // Before: MaterialApp(
home: MyHome(),
)
```
## 普通路由导航
导航到新的页面。
```dart
Get.to(NextScreen());
```
关闭SnackBars、Dialogs、BottomSheets或任何你通常会用Navigator.pop(context)关闭的东西。
```dart
Get.back();
```
进入下一个页面但没有返回上一个页面的选项用于SplashScreens登录页面等
```dart
Get.off(NextScreen());
```
进入下一个界面并取消之前的所有路由(在购物车、投票和测试中很有用)。
```dart
Get.offAll(NextScreen());
```
要导航到下一条路由,并在返回后立即接收或更新数据。
```dart
var data = await Get.to(Payment());
```
在另一个页面上,发送前一个路由的数据。
```dart
Get.back(result: 'success');
```
并使用它,例:
```dart
if(data == 'success') madeAnything();
```
你不想学习我们的语法吗?
只要把 Navigator大写改成 navigator小写你就可以拥有标准导航的所有功能而不需要使用context例如
```dart
// 默认的Flutter导航
Navigator.of(context).push(
context,
MaterialPageRoute(
builder: (BuildContext context) {
return HomePage();
},
),
);
// 使用Flutter语法获得而不需要context。
navigator.push(
MaterialPageRoute(
builder: (_) {
return HomePage();
},
),
);
// get语法 (这要好得多)
Get.to(HomePage());
```
## 别名路由导航
- 如果你喜欢用别名路由导航Get也支持。
导航到下一个页面
```dart
Get.toNamed("/NextScreen");
```
浏览并删除前一个页面。
```dart
Get.offNamed("/NextScreen");
```
浏览并删除所有以前的页面。
```dart
Get.offAllNamed("/NextScreen");
```
要定义路由使用GetMaterialApp。
```dart
void main() {
runApp(
GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => MyHomePage()),
GetPage(name: '/second', page: () => Second()),
GetPage(
name: '/third',
page: () => Third(),
transition: Transition.zoom
),
],
)
);
}
```
要处理到未定义路线的导航404错误可以在GetMaterialApp中定义unknownRoute页面。
```dart
void main() {
runApp(
GetMaterialApp(
unknownRoute: GetPage(name: '/notfound', page: () => UnknownRoutePage()),
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => MyHomePage()),
GetPage(name: '/second', page: () => Second()),
],
)
);
}
```
### 发送数据到别名路由
只要发送你想要的参数即可。Get在这里接受任何东西无论是一个字符串一个Map一个List甚至一个类的实例。
```dart
Get.toNamed("/NextScreen", arguments: 'Get is the best');
```
在你的类或控制器上:
```dart
print(Get.arguments);
//print out: Get is the best
```
### 动态网页链接
Get提供高级动态URL就像在Web上一样。Web开发者可能已经在Flutter上想要这个功能了Get也解决了这个问题。
```dart
Get.offAllNamed("/NextScreen?device=phone&id=354&name=Enzo");
```
在你的controller/bloc/stateful/stateless类上
```dart
print(Get.parameters['id']);
// out: 354
print(Get.parameters['name']);
// out: Enzo
```
你也可以用Get轻松接收NamedParameters。
```dart
void main() {
runApp(
GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(
name: '/',
page: () => MyHomePage(),
),
GetPage(
name: '/profile/',
page: () => MyProfile(),
),
//你可以为有参数的路由定义一个不同的页面,也可以为没有参数的路由定义一个不同的页面,但是你必须在不接收参数的路由上使用斜杠"/",就像上面说的那样。
GetPage(
name: '/profile/:user',
page: () => UserProfile(),
),
GetPage(
name: '/third',
page: () => Third(),
transition: Transition.cupertino
),
],
)
);
}
```
发送别名路由数据
```dart
Get.toNamed("/second/34954");
```
在第二个页面上,通过参数获取数据
```dart
print(Get.parameters['user']);
// out: 34954
```
或像这样发送多个参数
```dart
Get.toNamed("/profile/34954?flag=true");
```
在第二个屏幕上,通常按参数获取数据
```dart
print(Get.parameters['user']);
print(Get.parameters['flag']);
// out: 34954 true
```
现在你需要做的就是使用Get.toNamed()来导航你的别名路由不需要任何context(你可以直接从你的BLoC或Controller类中调用你的路由)当你的应用程序被编译到web时你的路由将出现在URL中。
### 中间件
如果你想通过监听Get事件来触发动作你可以使用routingCallback来实现。
```dart
GetMaterialApp(
routingCallback: (routing) {
if(routing.current == '/second'){
openAds();
}
}
)
```
如果你没有使用GetMaterialApp你可以使用手动API来附加Middleware观察器。
```dart
void main() {
runApp(
MaterialApp(
onGenerateRoute: Router.generateRoute,
initialRoute: "/",
navigatorKey: Get.key,
navigatorObservers: [
GetObserver(MiddleWare.observer), // HERE !!!
],
),
);
}
```
创建一个MiddleWare类
```dart
class MiddleWare {
static observer(Routing routing) {
///你除了可以监听路由外还可以监听每个页面上的SnackBars、Dialogs和Bottomsheets。
if (routing.current == '/second' && !routing.isSnackbar) {
Get.snackbar("Hi", "You are on second route");
} else if (routing.current =='/third'){
print('last route called');
}
}
}
```
现在在你的代码上使用Get
```dart
class First extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.add),
onPressed: () {
Get.snackbar("hi", "i am a modern snackbar");
},
),
title: Text('First Route'),
),
body: Center(
child: ElevatedButton(
child: Text('Open route'),
onPressed: () {
Get.toNamed("/second");
},
),
),
);
}
}
class Second extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.add),
onPressed: () {
Get.snackbar("hi", "i am a modern snackbar");
},
),
title: Text('second Route'),
),
body: Center(
child: ElevatedButton(
child: Text('Open route'),
onPressed: () {
Get.toNamed("/third");
},
),
),
);
}
}
class Third extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Third Route"),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Get.back();
},
child: Text('Go back!'),
),
),
);
}
}
```
## 免context导航
### SnackBars
用Flutter创建一个简单的SnackBar你必须获得Scaffold的context或者你必须使用一个GlobalKey附加到你的Scaffold上。
```dart
final snackBar = SnackBar(
content: Text('Hi!'),
action: SnackBarAction(
label: 'I am a old and ugly snackbar :(',
onPressed: (){}
),
);
// 在小组件树中找到脚手架并使用它显示一个SnackBars。
Scaffold.of(context).showSnackBar(snackBar);
```
用Get
```dart
Get.snackbar('Hi', 'i am a modern snackbar');
```
有了Get你所要做的就是在你代码的任何地方调用你的Get.snackbar或者按照你的意愿定制它。
```dart
Get.snackbar(
"Hey i'm a Get SnackBar!", // title
"It's unbelievable! I'm using SnackBar without context, without boilerplate, without Scaffold, it is something truly amazing!", // message
icon: Icon(Icons.alarm),
shouldIconPulse: true,
onTap:(){},
barBlur: 20,
isDismissible: true,
duration: Duration(seconds: 3),
);
////////// ALL FEATURES //////////
// Color colorText,
// Duration duration,
// SnackPosition snackPosition,
// Widget titleText,
// Widget messageText,
// bool instantInit,
// Widget icon,
// bool shouldIconPulse,
// double maxWidth,
// EdgeInsets margin,
// EdgeInsets padding,
// double borderRadius,
// Color borderColor,
// double borderWidth,
// Color backgroundColor,
// Color leftBarIndicatorColor,
// List<BoxShadow> boxShadows,
// Gradient backgroundGradient,
// TextButton mainButton,
// OnTap onTap,
// bool isDismissible,
// bool showProgressIndicator,
// AnimationController progressIndicatorController,
// Color progressIndicatorBackgroundColor,
// Animation<Color> progressIndicatorValueColor,
// SnackStyle snackStyle,
// Curve forwardAnimationCurve,
// Curve reverseAnimationCurve,
// Duration animationDuration,
// double barBlur,
// double overlayBlur,
// Color overlayColor,
// Form userInputForm
///////////////////////////////////
```
如果您喜欢传统的SnackBars或者想从头开始定制包括只添加一行(Get.snackbar使用了一个强制性的标题和信息),您可以使用
`Get.rawSnackbar();`它提供了建立Get.snackbar的RAW API。
### Dialogs
打开Dialogs
```dart
Get.dialog(YourDialogWidget());
```
打开默认Dialogs
```dart
Get.defaultDialog(
onConfirm: () => print("Ok"),
middleText: "Dialog made in 3 lines of code"
);
```
你也可以用Get.generalDialog代替showGeneralDialog。
对于所有其他的FlutterDialogs小部件包括cupertinos你可以使用Get.overlayContext来代替context并在你的代码中任何地方打开它。
对于不使用Overlay的小组件你可以使用Get.context。
这两个context在99%的情况下都可以代替你的UIcontext除了在没有导航context的情况下使用 inheritedWidget的情况。
### BottomSheets
Get.bottomSheet类似于showModalBottomSheet但不需要context
```dart
Get.bottomSheet(
Container(
child: Wrap(
children: <Widget>[
ListTile(
leading: Icon(Icons.music_note),
title: Text('Music'),
onTap: () {}
),
ListTile(
leading: Icon(Icons.videocam),
title: Text('Video'),
onTap: () {},
),
],
),
)
);
```
## 嵌套导航
Get让Flutter的嵌套导航更加简单。
你不需要context而是通过Id找到你的导航栈。
- 注意创建平行导航堆栈可能是危险的。理想的情况是不要使用NestedNavigators或者尽量少用。如果你的项目需要它请继续但请记住在内存中保持多个导航堆栈可能不是一个好主意(消耗RAM)。
看看它有多简单:
```dart
Navigator(
key: Get.nestedKey(1), // create a key by index
initialRoute: '/',
onGenerateRoute: (settings) {
if (settings.name == '/') {
return GetPageRoute(
page: () => Scaffold(
appBar: AppBar(
title: Text("Main"),
),
body: Center(
child: TextButton(
color: Colors.blue,
onPressed: () {
Get.toNamed('/second', id:1); // navigate by your nested route by index
},
child: Text("Go to second"),
),
),
),
);
} else if (settings.name == '/second') {
return GetPageRoute(
page: () => Center(
child: Scaffold(
appBar: AppBar(
title: Text("Main"),
),
body: Center(
child: Text("second")
),
),
),
);
}
}
),
```

View File

@@ -0,0 +1,714 @@
- [状态管理](#状态管理)
- [响应式状态管理器](#响应式状态管理器)
- [优点](#优势)
- [声明一个响应式变量](#声明一个响应式变量)
- [使用视图中的值](#使用视图中的值)
- [什么时候重建](#什么时候重建)
- [可以使用.obs的地方](#可以使用.obs的地方)
- [关于List的说明](#关于List的说明)
- [为什么使用.value](#为什么使用.value)
- [Obx()](#obx)
- [Workers](#Workers)
- [简单状态管理器](#简单状态管理器)
- [优点](#优点)
- [用法](#用法)
- [如何处理controller](#如何处理controller)
- [无需StatefulWidgets](#无需StatefulWidgets)
- [为什么它存在](#为什么它存在)
- [其他使用方法](#其他使用方法)
- [唯一的ID](#唯一的ID)
- [与其他状态管理器混用](#与其他状态管理器混用)
- [GetBuilder vs GetX vs Obx vs MixinBuilder](#GetBuilder-vs-GetX-vs-Obx-vs-MixinBuilder)
# 状态管理
目前Flutter有几种状态管理器。但是它们中的大多数都涉及到使用ChangeNotifier来更新widget这对于中大型应用的性能来说是一个糟糕的方法。你可以在Flutter官方文档中查到[ChangeNotifier应该使用1个或最多2个监听器](https://api.flutter.dev/flutter/foundation/ChangeNotifier-class.html),这使得它实际上无法用于任何中等或大型应用。
其他的状态管理器也不错,但有其细微的差别。
- BLoC非常安全和高效但是对于初学者来说非常复杂这使得人们无法使用Flutter进行开发。
- MobX比BLoC更容易而且是响应式的几乎是完美的但是你需要使用一个代码生成器对于大型应用来说这降低了生产力因为你需要喝很多咖啡直到你的代码在`flutter clean`之后再次准备好这不是MobX的错而是codegen真的很慢
- Provider使用InheritedWidget来传递相同的监听器以此来解决上面报告的ChangeNotifier的问题这意味着对其ChangeNotifier类的任何访问都必须在widget树内。
Get并不是比任何其他状态管理器更好或更差而是说你应该分析这些要点以及下面的要点来选择是只用Get还是与其他状态管理器结合使用。Get不是其他状态管理器的敌人因为Get是一个微框架而不仅仅是一个状态管理器它的状态管理功能既可以单独使用也可以与其他状态管理器结合使用。
## 响应式状态管理器
响应式编程可能会让很多人感到陌生因为它很复杂但是GetX将响应式编程变得非常简单。
- 你不需要创建StreamControllers.
- 你不需要为每个变量创建一个StreamBuilder。
- 你不需要为每个状态创建一个类。
- 你不需要为一个初始值创建一个get。
使用 Get 的响应式编程就像使用 setState 一样简单。
让我们想象一下,你有一个名称变量,并且希望每次你改变它时,所有使用它的小组件都会自动刷新。
这是你的计数变量:
```dart
var name = 'Jonatas Borges';
```
要想让它变得可观察,你只需要在它的末尾加上".obs"。
```dart
var name = 'Jonatas Borges'.obs;
```
就这么简单!
我们把这个reactive-".obs"(ervables)变量称为_Rx_。
我们做了什么?我们创建了一个 "Stream "的 "String",分配了初始值 "Jonatas Borges",我们通知所有使用 "Jonatas Borges "的widgets它们现在 "属于 "这个变量当_Rx_的值发生变化时它们也要随之改变。
这就是GetX**的**魔力这要归功于Dart的能力。
但是,我们知道,一个`Widget`只有在函数里面才能改变,因为静态类没有 "自动改变 "的能力。
你需要创建一个`StreamBuilder`,订阅这个变量来监听变化,如果你想在同一个范围内改变几个变量,就需要创建一个 "级联 "的嵌套`StreamBuilder`,对吧?
不,你不需要一个`StreamBuilder`,但你对静态类的理解是对的。
在视图中当我们想改变一个特定的Widget时我们通常有很多Flutter方式的模板。
有了**GetX**,你也可以忘记这些模板代码了。
`StreamBuilder( ... )`? `initialValue: ...`? `builder: ...`? 不,你只需要把这个变量放在`Obx()`这个Widget里面就可以了。
```dart
Obx (() => Text (controller.name));
```
_你只需记住 `Obx(()=>`
你只需将Widget通过一个箭头函数传递给 `Obx()`(_Rx_的 "观察者")。
`Obx`是相当聪明的,只有当`controller.name`的值发生变化时才会改变。
如果`name``"John"`,你把它改成了`"John"``name.value="John"`),因为它和之前的`value`是一样的,所以界面上不会有任何变化,而`Obx`为了节省资源会直接忽略新的值不重建Widget。**这是不是很神奇**
> 那么,如果我在一个`Obx`里有5个_Rx_可观察的变量呢
当其中**任何**一个变量发生变化时,它就会更新。
> 如果我在一个类中有 30 个变量,当我更新其中一个变量时,它会更新该类中**所有**的变量吗?
不会,只会更新使用那个 _Rx_ 变量的**特定 Widget**。
所以只有当_Rx_变量的值发生变化时**GetX**才会更新界面。
```
final isOpen = false.obs;
//什么都不会发生......相同的值。
void onButtonTap() => isOpen.value=false;
```
### 优势
**当你需要对更新的内容进行**精细的控制时,**GetX()** 可以帮助你。
如果你不需要 "unique IDs",比如当你执行一个操作时,你的所有变量都会被修改,那么就使用`GetBuilder`
因为它是一个简单的状态更新器(以块为单位,比如`setState()`),只用几行代码就能完成。
它做得很简单对CPU的影响最小只是为了完成一个单一的目的一个_State_ Rebuild并尽可能地花费最少的资源。
如果你需要一个**强大的**状态管理器,用**GetX**是不会错的。
它不能和变量一起工作除了__flows__它里面的东西本质都是`Streams`
你可以将_rxDart_与它结合使用因为所有的东西都是`Streams`
你可以监听每个"_Rx_变量 "的 "事件"。
因为里面的所有东西都是 "Streams"。
这实际上是一种_BLoC_方法比_MobX_更容易而且没有代码生成器或装饰。
你可以把**任何东西**变成一个_"Observable"_只需要在它末尾加上`.obs`
### 最高性能
除了有一个智能的算法来实现最小化的重建,**GetX**还使用了比较器以确保状态已经改变。
如果你的应用程序中遇到错误,并发送重复的状态变更,**GetX**将确保它不会崩溃。
使用**GetX**,只有当`value`改变时,状态才会改变。
这就是**GetX**和使用MobX_的_`computed`的主要区别。
当加入两个__observable__其中一个发生变化时该_observable_的监听器也会发生变化。
使用**GetX**,如果你连接了两个变量,`GetX()`(类似于`Observer()`)只有在它的状态真正变化时才会重建。
### 声明一个响应式变量
你有3种方法可以把一个变量变成是 "可观察的"。
1 - 第一种是使用 **`Rx{Type}`**。
```dart
// 建议使用初始值,但不是强制性的
final name = RxString('');
final isLogged = RxBool(false);
final count = RxInt(0);
final balance = RxDouble(0.0);
final items = RxList<String>([]);
final myMap = RxMap<String, int>({});
```
2 - 第二种是使用 **`Rx`**,规定泛型 `Rx<Type>`
```dart
final name = Rx<String>('');
final isLogged = Rx<Bool>(false);
final count = Rx<Int>(0);
final balance = Rx<Double>(0.0);
final number = Rx<Num>(0)
final items = Rx<List<String>>([]);
final myMap = Rx<Map<String, int>>({});
// 自定义类 - 可以是任何类
final user = Rx<User>();
```
3 - 第三种更实用、更简单、更可取的方法,只需添加 **`.obs`** 作为`value`的属性。
```dart
final name = ''.obs;
final isLogged = false.obs;
final count = 0.obs;
final balance = 0.0.obs;
final number = 0.obs;
final items = <String>[].obs;
final myMap = <String, int>{}.obs;
// 自定义类 - 可以是任何类
final user = User().obs;
```
##### 有一个反应的状态,很容易。
我们知道_Dart_现在正朝着_null safety_的方向发展。
为了做好准备,从现在开始,你应该总是用一个**初始值**来开始你的_Rx_变量。
> 用**GetX**将一个变量转化为一个_observable_ + _initial value_是最简单也是最实用的方法。
你只需在变量的末尾添加一个"`.obs`",即可把它变成可观察的变量,
然后它的`.value`就是_初始值_)。
### 使用视图中的值
```dart
// controller
final count1 = 0.obs;
final count2 = 0.obs;
int get sum => count1.value + count2.value;
```
```dart
// 视图
GetX<Controller>(
builder: (controller) {
print("count 1 rebuild");
return Text('${controller.count1.value}');
},
),
GetX<Controller>(
builder: (controller) {
print("count 2 rebuild");
return Text('${controller.count2.value}');
},
),
GetX<Controller>(
builder: (controller) {
print("count 3 rebuild");
return Text('${controller.sum}');
},
),
```
如果我们把`count1.value++`递增,就会打印出来:
- `count 1 rebuild`
- `count 3 rebuild`
如果我们改变`count2.value++`,就会打印出来。
- `count 2 rebuild`
- `count 3 rebuild`
因为`count2.value`改变了,`sum`的结果现在是`2`
- 注意:默认情况下,第一个事件将重建小组件,即使是相同的`值`
这种行为是由于布尔变量而存在的。
想象一下你这样做。
```dart
var isLogged = false.obs;
```
然后,你检查用户是否 "登录",以触发`ever`的事件。
```dart
@override
onInit(){
ever(isLogged, fireRoute);
isLogged.value = await Preferences.hasToken();
}
fireRoute(logged) {
if (logged) {
Get.off(Home());
} else {
Get.off(Login());
}
}
```
如果 "hasToken "是 "false""isLogged "就不会有任何变化,所以 "ever() "永远不会被调用。
为了避免这种问题_observable_的第一次变化将总是触发一个事件即使它包含相同的`.value`
如果你想删除这种行为,你可以使用:
`isLogged.firstRebuild = false;`
### 什么时候重建
此外Get还提供了精细的状态控制。你可以根据特定的条件对一个事件进行条件控制比如将一个对象添加到List中
```dart
// 第一个参数条件必须返回true或false。
// 第二个参数:如果条件为真,则为新的值。
list.addIf(item < limit, item);
```
没有装饰,没有代码生成器,没有复杂的程序: 爽歪歪!
你知道Flutter的计数器应用吗你的Controller类可能是这样的。
```dart
class CountController extends GetxController {
final count = 0.obs;
}
```
用一个简单的。
```dart
controller.count.value++
```
你可以在你的UI中更新计数器变量不管它存储在哪里。
### 可以使用.obs的地方
你可以在 obs 上转换任何东西,这里有两种方法:
* 可以将你的类值转换为 obs
```dart
class RxUser {
final name = "Camila".obs;
final age = 18.obs;
}
```
* 或者可以将整个类转换为一个可观察的类。
```dart
class User {
User({String name, int age});
var name;
var age;
}
//实例化时。
final user = User(name: "Camila", age: 18).obs;
```
### 关于List的说明
List和它里面的对象一样是完全可以观察的。这样一来如果你在List中添加一个值它会自动重建使用它的widget。
你也不需要在List中使用".value"神奇的dart api允许我们删除它。
不幸的是像String和int这样的原始类型不能被扩展使得.value的使用是强制性的但是如果你使用get和setter来处理这些类型这将不是一个问题。
```dart
// controller
final String title = 'User Info:'.obs
final list = List<User>().obs;
// view
Text(controller.title.value), // 字符串后面需要有.value。
ListView.builder (
itemCount: controller.list.length // List不需要它
)
```
当你在使自己的类可观察时,有另外一种方式来更新它们:
```dart
// model
// 我们将使整个类成为可观察的,而不是每个属性。
class User{
User({this.name = '', this.age = 0});
String name;
int age;
}
// controller
final user = User().obs;
//当你需要更新user变量时。
user.update( (user) { // 这个参数是你要更新的类本身。
user.name = 'Jonny';
user.age = 18;
});
// 更新user变量的另一种方式。
user(User(name: 'João', age: 35));
// view
Obx(()=> Text("Name ${user.value.name}: Age: ${user.value.age}"));
// 你也可以不使用.value来访问模型值。
user().name; // 注意是user变量而不是类变量首字母是小写的
```
你可以使用 "assign "和" assignAll "。
"assign "会清除你的List并添加一个单个对象。
"assignAll "将清除现有的List并添加任何可迭代对象。
### 为什么使用.value
我们可以通过一个简单的装饰和代码生成器来消除使用"String "和 "int "的值的义务,但这个库的目的正是为了避免外部依赖。我们希望提供一个准备好的编程的环境(路由、依赖和状态的管理),以一种简单、轻量级和高性能的方式,而不需要一个外部包。
你可以在你的pubspec中添加3个字母get和一个冒号然后开始编程。从路由管理到状态管理所有的解决方案都是默认的目的是为了方便、高效和高性能。
这个库的总大小还不如一个状态管理器,尽管它是一个完整的解决方案。
如果你被`.value`困扰喜欢代码生成器MobX是一个很好的选择也可以和Get一起使用。对于那些想在pubspec中添加一个单一的依赖然后开始编程而不用担心一个包的版本与另一个包不兼容也不用担心状态更新的错误来自于状态管理器或依赖还是不想担心控制器的可用性get都是刚刚好。
如果你对MobX代码生成器或者BLoC模板熟悉你可以直接用Get来做路由而忘记它有状态管理器。如果有一个项目有90多个控制器MobX代码生成器在标准机器上进行Flutter Clean后需要30多分钟才能完成任务。如果你的项目有5个、10个、15个控制器任何一个状态管理器都能很好的满足你。如果你的项目大得离谱代码生成器对你来说是个问题但你已经获得了Get这个解决方案。
显然如果有人想为项目做贡献创建一个代码生成器或者类似的东西我将在这个readme中链接作为一个替代方案我的需求并不是所有开发者的需求但现在我要说的是已经有很好的解决方案比如MobX。
### Obx()
在Get中使用Bindings的类型是不必要的。你可以使用Obx widget代替GetXGetX只接收创建widget的匿名函数。
如果你不使用类型,你将需要有一个控制器的实例来使用变量,或者使用`Get.find<Controller>()`.value或Controller.to.value来检索值。
### Workers
Workers将协助你在事件发生时触发特定的回调。
```dart
///每次`count1`变化时调用。
ever(count1, (_) => print("$_ has been changed"));
///只有在变量$_第一次被改变时才会被调用。
once(count1, (_) => print("$_ was changed once"));
///防DDos - 每当用户停止输入1秒时调用例如。
debounce(count1, (_) => print("debouce$_"), time: Duration(seconds: 1));
///忽略1秒内的所有变化。
interval(count1, (_) => print("interval $_"), time: Duration(seconds: 1));
```
所有Workers(除 "debounce "外)都有一个名为 "condition"的参数,它可以是一个 "bool "或一个返回 "bool "的回调。
这个`condition`定义了`callback`函数何时执行。
所有worker都会返回一个`Worker`实例,你可以用它来取消(通过`dispose()`worker。
- **`ever`**
每当_Rx_变量发出一个新的值时就会被调用。
- **`everAll`**
和 "ever "很像但它需要一个_Rx_值的 "List",每次它的变量被改变时都会被调用。就是这样。
- **`once`**
'once'只在变量第一次被改变时被调用。
- **`debounce`**
debounce'在搜索函数中非常有用你只希望API在用户完成输入时被调用。如果用户输入 "Jonny"你将在API中进行5次搜索分别是字母J、o、n、n和y。使用Get不会发生这种情况因为你将有一个 "debounce "Worker它只会在输入结束时触发。
- **`interval`**
'interval'与debouce不同debouce如果用户在1秒内对一个变量进行了1000次修改他将在规定的计时器默认为800毫秒后只发送最后一次修改。Interval则会忽略规定时间内的所有用户操作。如果你发送事件1分钟每秒1000个那么当用户停止DDOS事件时debounce将只发送最后一个事件。建议这样做是为了避免滥用在用户可以快速点击某样东西并获得一些好处的功能中想象一下用户点击某样东西可以赚取硬币如果他在同一分钟内点击300次他就会有300个硬币使用间隔你可以设置时间范围为3秒无论是点击300次或100万次1分钟内他最多获得20个硬币。debounce适用于防DDOS适用于搜索等功能每次改变onChange都会调用你的api进行查询。Debounce会等待用户停止输入名称进行请求。如果在上面提到的投币场景中使用它用户只会赢得1个硬币因为只有当用户 "暂停 "到既定时间时,它才会被执行。
- 注意Worker应该总是在启动Controller或Class时使用所以应该总是在onInit(推荐)、Class构造函数或StatefulWidget的initState(大多数情况下不推荐这种做法,但应该不会有任何副作用)。
## 简单状态管理器
Get有一个极其轻巧简单的状态管理器它不使用ChangeNotifier可以满足特别是对Flutter新手的需求而且不会给大型应用带来问题。
GetBuilder正是针对多状态控制的。想象一下你在购物车中添加了30个产品你点击删除一个同时List更新了价格更新了购物车中的徽章也更新为更小的数字。这种类型的方法使GetBuilder成为杀手锏因为它将状态分组并一次性改变而无需为此进行任何 "计算逻辑"。GetBuilder就是考虑到这种情况而创建的因为对于短暂的状态变化你可以使用setState而不需要状态管理器。
这样一来如果你想要一个单独的控制器你可以为其分配ID或者使用GetX。这取决于你记住你有越多的 "单独 "部件GetX的性能就越突出而当有多个状态变化时GetBuilder的性能应该更优越。
### 优点
1. 只更新需要的小部件。
2. 不使用changeNotifier状态管理器使用较少的内存接近0mb
3. 忘掉StatefulWidget! 使用Get你永远不会需要它。对于其他的状态管理器你可能需要使用StatefulWidget来获取你的Provider、BLoC、MobX控制器等的实例。但是你有没有停下来想一想你的appBar你的脚手架以及你的类中的大部分widget都是无状态的那么如果你只能保存有状态的Widget的状态为什么要保存整个类的状态呢Get也解决了这个问题。创建一个无状态类让一切都成为无状态。如果你需要更新单个组件就用GetBuilder把它包起来它的状态就会被维护。
4. 真正的解耦你的项目! 控制器一定不要在你的UI中把你的TextEditController或者你使用的任何控制器放在你的Controller类中。
5. 你是否需要触发一个事件来更新一个widget一旦它被渲染GetBuilder有一个属性 "initState"就像StatefulWidget一样你可以从你的控制器中调用事件直接从控制器中调用不需要再在你的initState中放置事件。
6. 你是否需要触发一个动作比如关闭流、定时器等GetBuilder也有dispose属性只要该widget被销毁你就可以调用事件。
7. 仅在必要时使用流。你可以在你的控制器里面正常使用你的StreamControllers也可以正常使用StreamBuilder但是请记住一个流消耗合理的内存响应式编程很美但是你不应该滥用它。30个流同时打开会比changeNotifier更糟糕而且changeNotifier非常糟糕
8. 更新widgets而不需要为此花费ram。Get只存储GetBuilder的创建者ID必要时更新该GetBuilder。get ID存储在内存中的消耗非常低即使是成千上万的GetBuilders。当你创建一个新的GetBuilder时你实际上是在共享拥有创建者ID的GetBuilder的状态。不会为每个GetBuilder创建一个新的状态这为大型应用节省了大量的内存。基本上你的应用程序将是完全无状态的而少数有状态的Widgets(在GetBuilder内)将有一个单一的状态,因此更新一个状态将更新所有的状态。状态只是一个。
9. Get是全知全能的在大多数情况下它很清楚地知道从内存中取出一个控制器的时机你不需要担心什么时候移除一个控制器Get知道最佳的时机。
### 用法
```dart
// 创建控制器类并扩展GetxController。
class Controller extends GetxController {
int counter = 0;
void increment() {
counter++;
update(); // 当调用增量时使用update()来更新用户界面上的计数器变量。
}
}
// 在你的Stateless/Stateful类中当调用increment时使用GetBuilder来更新Text。
GetBuilder<Controller>(
init: Controller(), // 首次启动
builder: (_) => Text(
'${_.counter}',
),
)
//只在第一次时初始化你的控制器。第二次使用ReBuilder时不要再使用同一控制器。一旦将控制器标记为 "init "的部件部署完毕你的控制器将自动从内存中移除。你不必担心这个问题Get会自动做到这一点只是要确保你不要两次启动同一个控制器。
```
**完成!**
- 你已经学会了如何使用Get管理状态。
- 注意你可能想要一个更大的规模而不是使用init属性。为此你可以创建一个类并扩展Bindings类并在其中添加将在该路由中创建的控制器。控制器不会在那个时候被创建相反这只是一个声明这样你第一次使用Controller时Get就会知道去哪里找。Get会保持懒加载当不再需要Controller时会自动移除它们。请看pub.dev的例子来了解它是如何工作的。
如果你导航了很多路由并且需要之前使用的Controller中的数据你只需要再用一次GetBuilder没有init
```dart
class OtherClass extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: GetBuilder<Controller>(
builder: (s) => Text('${s.counter}'),
),
),
);
}
```
如果你需要在许多其他地方使用你的控制器并且在GetBuilder之外只需在你的控制器中创建一个get就可以轻松地拥有它。(或者使用`Get.find<Controller>()`)
```dart
class Controller extends GetxController {
/// 你不需要这个,我推荐使用它只是为了方便语法。
/// 用静态方法Controller.to.increment()。
/// 没有静态方法的情况下Get.find<Controller>().increment();
/// 使用这两种语法在性能上没有区别也没有任何副作用。一个不需要类型另一个IDE会自动完成。
static Controller get to => Get.find(); // 添加这一行
int counter = 0;
void increment() {
counter++;
update();
}
}
```
然后你可以直接访问你的控制器,这样:
```dart
FloatingActionButton(
onPressed: () {
Controller.to.increment(),
} // 是不是贼简单!
child: Text("${Controller.to.counter}"),
),
```
当你按下FloatingActionButton时所有监听'counter'变量的widget都会自动更新。
### 如何处理controller
比方说,我们有这样的情况。
`Class a => Class B (has controller X) => Class C (has controller X)`
在A类中控制器还没有进入内存因为你还没有使用它Get是懒加载。在类B中你使用了控制器并且它进入了内存。在C类中你使用了与B类相同的控制器Get会将控制器B的状态与控制器C共享同一个控制器还在内存中。如果你关闭C屏和B屏Get会自动将控制器X从内存中移除释放资源因为a类没有使用该控制器。如果你再次导航到B控制器X将再次进入内存如果你没有去C类而是再次回到a类Get将以同样的方式将控制器从内存中移除。如果类C没有使用控制器你把类B从内存中移除就没有类在使用控制器X同样也会被处理掉。唯一能让Get乱了阵脚的例外情况是如果你意外地从路由中删除了B并试图使用C中的控制器在这种情况下B中的控制器的创建者ID被删除了Get被设计为从内存中删除每一个没有创建者ID的控制器。如果你打算这样做在B类的GetBuilder中添加 "autoRemove: false "标志并在C类的GetBuilder中使用adopID = true
### 无需StatefulWidgets
使用StatefulWidgets意味着不必要地存储整个界面的状态甚至因为如果你需要最小化地重建一个widget你会把它嵌入一个Consumer/Observer/BlocProvider/GetBuilder/GetX/Obx中这将是另一个StatefulWidget。
StatefulWidget类是一个比StatelessWidget大的类它将分配更多的RAM只使用一两个类之间可能不会有明显的区别但当你有100个类时它肯定会有区别!
除非你需要使用混合器比如TickerProviderStateMixin否则完全没有必要使用StatefulWidget与Get。
你可以直接从GetBuilder中调用StatefulWidget的所有方法。
例如如果你需要调用initState()或dispose()方法,你可以直接调用它们。
```dart
GetBuilder<Controller>(
initState: (_) => Controller.to.fetchApi(),
dispose: (_) => Controller.to.closeStreams(),
builder: (s) => Text('${s.username}'),
),
```
比这更好的方法是直接从控制器中使用onInit()和onClose()方法。
```dart
@override
void onInit() {
fetchApi();
super.onInit();
}
```
- 注意如果你想在控制器第一次被调用的那一刻启动一个方法你不需要为此使用构造函数使用像Get这样面向性能的包这样做反而是糟糕的做法因为它偏离了控制器被创建或分配的逻辑如果你创建了这个控制器的实例构造函数会立即被调用你会在控制器还没有被使用之前就填充了一个控制器你在没有被使用的情况下就分配了内存这绝对违背这个库的原则。onInit();和onClose();方法就是为此而创建的它们会在Controller被创建或者第一次使用时被调用这取决于你是否使用Get.lazyPut。例如如果你想调用你的API来填充数据你可以忘掉老式的initState/dispose方法只需在onInit中开始调用api如果你需要执行任何命令如关闭流使用onClose()来实现。
### 为什么它存在
这个包的目的正是为了给你提供一个完整的解决方案用于导航路线管理依赖和状态使用尽可能少的依赖高度解耦。Get将所有高低级别的Flutter API都纳入自身以确保你在工作中尽可能减少耦合。我们将所有的东西集中在一个包中以确保你在你的项目中没有任何形式的耦合。这样一来你就可以只在视图中放置小组件而让你的团队中负责业务逻辑的那部分人自由地工作不需要依赖视图中的任何元素来处理业务逻辑。这样就提供了一个更加干净的工作环境这样你的团队中的一部分人只用widget工作而不用担心将数据发送到你的controller你的团队中的一部分人只用业务逻辑工作而不依赖于视图的任何元素。
所以为了简化这个问题。
你不需要在initState中调用方法然后通过参数发送给你的控制器也不需要使用你的控制器构造函数你有onInit()方法,在合适的时间被调用,以启动你的服务。
你不需要调用设备你有onClose()方法它将在确切的时刻被调用当你的控制器不再需要时将从内存中删除。这样一来只给widget留下视图不要从中进行任何形式的业务逻辑。
不要在GetxController里面调用dispose方法它不会有任何作用记住控制器不是Widget你不应该 "dispose "它它会被Get自动智能地从内存中删除。如果你在上面使用了任何流想关闭它只要把它插入到close方法中就可以了。例如
```dart
class Controller extends GetxController {
StreamController<User> user = StreamController<User>();
StreamController<String> name = StreamController<String>();
///关闭流用onClose方法而不是dispose
@override
void onClose() {
user.close();
name.close();
super.onClose();
}
}
```
控制器的生命周期。
- onInit()是创建控制器的地方。
- onClose(),关闭控制器,为删除方法做准备。
- deleted: 你不能访问这个API因为它实际上是将控制器从内存中删除。它真的被删除了不留任何痕迹。
### 其他使用方法
你可以直接在GetBuilder值上使用Controller实例。
```dart
GetBuilder<Controller>(
init: Controller(),
builder: (value) => Text(
'${value.counter}', //here
),
),
```
你可能还需要在GetBuilder之外的控制器实例你可以使用这些方法来实现。
```dart
class Controller extends GetxController {
static Controller get to => Get.find();
[...]
}
//view
GetBuilder<Controller>(
init: Controller(), // 每个控制器只用一次
builder: (_) => Text(
'${Controller.to.counter}', //here
)
),
```
或者
```dart
class Controller extends GetxController {
// static Controller get to => Get.find(); // with no static get
[...]
}
// on stateful/stateless class
GetBuilder<Controller>(
init: Controller(), // 每个控制器只用一次
builder: (_) => Text(
'${Get.find<Controller>().counter}', //here
),
),
```
- 你可以使用 "非规范 "的方法来做这件事。如果你正在使用一些其他的依赖管理器比如get_it、modular等并且只想交付控制器实例你可以这样做。
```dart
Controller controller = Controller();
[...]
GetBuilder<Controller>(
init: controller, //here
builder: (_) => Text(
'${controller.counter}', // here
),
),
```
### 唯一的ID
如果你想用GetBuilder完善一个widget的更新控件你可以给它们分配唯一的ID。
```dart
GetBuilder<Controller>(
id: 'text', //这里
init: Controller(), // 每个控制器只用一次
builder: (_) => Text(
'${Get.find<Controller>().counter}', //here
),
),
```
并更新它:
```dart
update(['text']);
```
您还可以为更新设置条件。
```dart
update(['text'], counter < 10);
```
GetX会自动进行重建并且只重建使用被更改的变量的小组件如果您将一个变量更改为与之前相同的变量并且不意味着状态的更改GetX不会重建小组件以节省内存和CPU周期界面上正在显示3而您再次将变量更改为3。在大多数状态管理器中这将导致一个新的重建但在GetX中如果事实上他的状态已经改变那么widget将只被再次重建
## 与其他状态管理器混用
有人开了一个功能请求因为他们只想使用一种类型的响应式变量而其他的则手动去更新需要为此在GetBuilder中插入一个Obx。思来想去MixinBuilder应运而生。它既可以通过改变".obs "变量进行响应式改变也可以通过update()进行手动更新。然而在4个widget中他是消耗资源最多的一个因为除了有一个Subscription来接收来自他的子代的变化事件外他还订阅了他的控制器的update方法。
扩展GetxController是很重要的因为它们有生命周期可以在它们的onInit()和onClose()方法中 "开始 "和 "结束 "事件。你可以使用任何类来实现这一点但我强烈建议你使用GetxController类来放置你的变量无论它们是否是可观察的。
## GetBuilder vs GetX vs Obx vs MixinBuilder
在十年的编程工作中,我能够学到一些宝贵的经验。
我第一次接触到响应式编程的时候,是那么的 "哇,这太不可思议了",事实上响应式编程是不可思议的。
但是它并不适合所有情况。通常情况下你需要的是同时改变2、3个widget的状态或者是短暂的状态变化这种情况下响应式编程不是不好而是不适合。
响应式编程对RAM的消耗比较大可以通过单独的工作流来弥补这样可以保证只有一个widget被重建而且是在必要的时候但是创建一个有80个对象的List每个对象都有几个流这不是一个好的想法。打开dart inspect查看一个StreamBuilder的消耗量你就会明白我想告诉你什么。
考虑到这一点,我创建了简单的状态管理器。它很简单,这正是你应该对它提出的要求:以一种简单的方式,并且以最高效的方式更新块中的状态。
GetBuilder在RAM中是非常高效的几乎没有比他更高效的方法至少我无法想象如果存在请告诉我们
然而GetBuilder仍然是一个手动的状态管理器你需要调用update()就像你需要调用Provider的notifyListeners()一样。
还有一些情况下响应式编程真的很有趣不使用它就等于重新发明轮子。考虑到这一点GetX的创建是为了提供状态管理器中最现代和先进的一切。它只在必要的时候更新必要的东西如果你出现了错误同时发送了300个状态变化GetX只在状态真正发生变化时才会过滤并更新界面。
GetX比其他响应式状态管理器还是比较高效的但它比GetBuilder多消耗一点内存。思前想后以最大限度地消耗资源为目标Obx应运而生。与GetX和GetBuilder不同的是你将无法在Obx内部初始化一个控制器它只是一个带有StreamSubscription的Widget接收来自你的子代的变化事件仅此而已。它比GetX更高效但输给了GetBuilder这是意料之中的因为它是响应式的而且GetBuilder有最简单的方法即存储widget的hashcode和它的StateSetter。使用Obx你不需要写你的控制器类型你可以从多个不同的控制器中监听到变化但它需要在之前进行初始化或者使用本readme开头的示例方法或者使用Bindings类。

View File

@@ -0,0 +1,16 @@
# example
A new Flutter project.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://flutter.dev/documentation/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://flutter.dev/documentation/cookbook)
For help getting started with Flutter, view our
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

Binary file not shown.

Binary file not shown.

View File

View File

@@ -0,0 +1,59 @@
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 30
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.example"
minSdkVersion 16
targetSdkVersion 30
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.example">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@@ -0,0 +1,41 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.example">
<application
android:label="example"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<!-- Displays an Android View that continues showing the launch screen
Drawable until Flutter paints its first frame, then this splash
screen fades out. A splash screen is useful to avoid any visual
gap between the end of Android's launch screen and the painting of
Flutter's first frame. -->
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>

View File

@@ -0,0 +1,19 @@
package io.flutter.plugins;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import io.flutter.Log;
import io.flutter.embedding.engine.FlutterEngine;
/**
* Generated file. Do not edit.
* This file is generated by the Flutter tool based on the
* plugins that support the Android platform.
*/
@Keep
public final class GeneratedPluginRegistrant {
private static final String TAG = "GeneratedPluginRegistrant";
public static void registerWith(@NonNull FlutterEngine flutterEngine) {
}
}

View File

@@ -0,0 +1,6 @@
package com.example.example
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.example">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@@ -0,0 +1,29 @@
buildscript {
ext.kotlin_version = '1.3.50'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
jcenter()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}

View File

@@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true

View File

@@ -0,0 +1,6 @@
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip

160
packages/get/example/android/gradlew vendored Executable file
View File

@@ -0,0 +1,160 @@
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

View File

@@ -0,0 +1,90 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -0,0 +1,2 @@
sdk.dir=/Users/hamzaaleghwairyeen/Library/Android/sdk
flutter.sdk=/Users/hamzaaleghwairyeen/flutter

View File

@@ -0,0 +1,11 @@
include ':app'
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
def properties = new Properties()
assert localPropertiesFile.exists()
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"

View File

@@ -0,0 +1,581 @@
# GetX codelab
In this example you will learn the basics of GetX. You will see how much easier it is to code with this framework, and you will know what problems GetX proposes to solve.
If the default Flutter application were rewritten with Getx, it would have only a few lines of code. The Getx state manager is easier than using setState. You just need to add a ".obs" at the end of your variable, and wrap the widget you want to change within a Obx().
```dart
void main() => runApp(MaterialApp(home: Home()));
class Home extends StatelessWidget {
var count = 0.obs;
@override
Widget build(context) => Scaffold(
appBar: AppBar(title: Text("counter")),
body: Center(
child: Obx(() => Text("$count")),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () => count ++,
));
}
```
However, this simple example deals with ephemeral state management. If you need to access this data in several places in your application, you will need global state management.
The most common way to do this is to separate the business logic from its visualization. I know, you've heard this concept before, and it might have been scary for you, but with Getx, it's stupidly simple.
Getx provides you with a class capable of initializing data, and removing it when it is no longer needed, and its use is very simple:
Just create a class by extending GetxController and insert ALL your variables and functions there.
```dart
class Controller extends GetxController {
var count = 0;
void increment() {
count++;
update();
}
}
```
Here you can choose between simple state management, or reactive state management.
The simple one will update its variable on the screen whenever update() is called. It is used with a widget called "GetBuilder".
```dart
class Home extends StatelessWidget {
final controller = Get.put(Controller());
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("counter")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
GetBuilder<Controller>(
builder: (_) => Text(
'clicks: ${controller.count}',
)),
ElevatedButton(
child: Text('Next Route'),
onPressed: () {
Get.to(Second());
},
),
],
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: controller.increment(),
),
);
}
}
class Second extends StatelessWidget {
final Controller ctrl = Get.find();
@override
Widget build(context){
return Scaffold(body: Center(child: Text("${ctrl.count}")));
}
}
```
When instantiating your controller, you may have noticed that we use `Get.put(Controller())`. This is enough to make your controller available to other pages as long as it is in memory.
You may have noticed that you have a new button using `Get.to(Second())`. This is enough to navigate to another page. You don't need a
```dart
Navigator.of(context).push(context,
MaterialPageRoute(context, builder: (context){
return Second();
},
);
```
all that huge code, it's reduced to a simple `Get.to(Second())`. Isn't that incredible?
You may also have noticed that on the other page you simply used Get.find (), and the framework knows which controller you registered previously, and returns it effortlessly, you don't need to type the find with `Get.find<Controller>()` so that he knows what you need.
However, this is simple state management. You may want to control the output of each change of state, you may want to use and manipulate streams, you may want a variable to only be changed on the screen, when it definitely changes, you may need a debounce on changing the state, and to these and other needs, Getx has powerful, intelligent, and lightweight state management that can address any need, regardless of the size of your project. And its use is even easier than the previous one.
In your controller, you can remove the update method, Getx is reactive, so when a variable changes, it will automatically change on the screen.
You just need to add a ".obs" in front of your variable, and that's it, it's already reactive.
```dart
class Controller extends GetxController {
var count = 0.obs;
void increment() {
count++;
}
}
```
Now you just need to change GetBuilder for GetX and that's it
```dart
class Home extends StatelessWidget {
final controller = Get.put(Controller());
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("counter")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
GetX<Controller>(
builder: (_) => Text(
'clicks: ${controller.count}',
)),
ElevatedButton(
child: Text('Next Route'),
onPressed: () {
Get.to(Second());
},
),
],
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: controller.increment(),
),
);
}
}
class Second extends StatelessWidget {
final Controller ctrl = Get.find();
@override
Widget build(context){
return Scaffold(body: Center(child: Text("${ctrl.count}")));
}
}
```
GetX is a useful widget when you want to inject the controller into the init property, or when you want to retrieve an instance of the controller within the widget itself. In other cases, you can insert your widget into an Obx, which receives a widget function. This looks much easier and clearer, just like the first example
```dart
class Home extends StatelessWidget {
final controller = Get.put(Controller());
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("counter")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Obx(() => Text(
'clicks: ${controller.count}',
)),
ElevatedButton(
child: Text('Next Route'),
onPressed: () {
Get.to(Second());
},
),
],
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: controller.increment(),
),
);
}
}
class Second extends StatelessWidget {
final Controller ctrl = Get.find();
@override
Widget build(context){
return Scaffold(body: Center(child: Text("${ctrl.count}")));
}
}
```
If you are a more demanding user, you must have said: BLoC separates the View from the business logic. But what about the presentation logic? Will I be obliged to attach it to the visualization? Will I be totally dependent on the context for everything I want to do? Will I have to insert a bunch of variables, TextEditingControllers in my view? If I need to hear the Scroll on my screen, do I need to insert an initState and a function into my view? If I need to trigger an action when this scroll reaches the end, do I insert it into the view, or in the bloc/changeNotifier class? Well, these are common architectural questions, and most of the time the solution to them is ugly.
With Getx you have no doubts when your architecture, if you need a function, it must be on your Controller, if you need to trigger an event, it needs to be on your controller, your view is generally a StatelessWidget free from any dirt.
This means that if someone has already done something youre looking for, you can copy the controller entirely from someone else, and it will work for you, this level of standardization is what Flutter lacked, and its because of this that Getx has become so popular in the last few days. Flutter is amazing, and has minor one-off problems. Getx came to solve these specific problems. Flutter provides powerful APIs, and we turn them into an easy, clean, clear, and concise API for you to build applications in a fast, performance and highly scalable way.
If you have already asked yourself some of these questions above, you have certainly found the solution to your problems. Getx is able to completely separate any logic, be it presentation or business, and you will only have pure widgets in your visualization layer. No variables, no functions, just widgets. This will facilitate the work of your team working with the UI, as well as your team working with your logic. They won't depend on initState to do anything, their controller has onInit. Your code can be tested in isolation, the way it is.
But what about dependency injection? Will I have it attached to my visualization?
If you've used any state manager, you've probably heard of "multiAnything", or something like that.
You have probably already inserted dozens of ChangeNotifier or Blocs classes in a widget, just to have it all over the tree.
This level of coupling is yet another problem that Getx came to solve. For this, in this ecosystem we use BINDINGS.
Bindings are dependency injection classes. They are completely outside your widget tree, making your code cleaner, more organized, and allowing you to access it anywhere without context.
You can initialize dozens of controllers in your Bindings, when you need to know what is being injected into your view, just open the Bindings file on your page and that's it, you can clearly see what has been prepared to be initialized in your View.
Bindings is the first step towards having a scalable application, you can visualize what will be injected into your page, and decouple the dependency injection from your visualization layer.
To create a Binding, simply create a class and implement Bindings
```dart
class SampleBind extends Bindings {
@override
void dependencies() {
Get.lazyPut<Controller>(() => Controller());
Get.lazyPut<Controller2>(() => Controller2());
Get.lazyPut<Controller3>(() => Controller3());
}
}
```
You can use with named routes (preferred)
```dart
void main() {
runApp(GetMaterialApp(
initialRoute: '/home',
getPages: [
GetPage(name: '/home', page: () => First(), binding: SampleBind()),
],
));
}
```
Or unnamed
```dart
Get.to(Second(), binding: SampleBind());
```
There is a trick that can clear your View even more.
Instead of extending StatelessWidget, you can extend GetView, which is a StatelessWidget with a "controller" property.
See the example and see how clean your code can be using this approach.
The standard Flutter counter has almost 100 lines, it would be summarized to:
on main.dart
```dart
void main() {
runApp(GetMaterialApp(
initialRoute: '/home',
getPages: [
GetPage(name: '/home', page: () => HomeView(), binding: HomeBinding()),
],
));
}
```
on home_bindings.dart
```dart
class HomeBinding implements Bindings {
@override
void dependencies() {
Get.lazyPut(() => HomeController());
}
}
```
on home_controller.dart
```dart
class HomeController extends GetxController {
var count = 0.obs;
void increment() => count++;
}
```
on home_view.dart
```dart
class Home extends GetView<Controller> {
@override
Widget build(context) => Scaffold(
appBar: AppBar(title: Text("counter")),
body: Center(
child: Obx(() => Text("${controller.counter}")),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: controller.increment,
));
}
```
What did you do:
He built an example of the counter, (with less code than the original), decoupling its visualization, its business logic, its dependency injection, in a clean, scalable way, facilitating code maintenance and reusability. If you need an accountant on another project, or your developer friend does, you can just share the content of the controller file with him, and everything will work perfectly.
As the view has only widgets, you can use a view for android, and another for iOS, taking advantage of 100% of your business logic, your view has only widgets! you can change them however you want, without affecting your application in any way.
However, some examples like internationalization, Snackbars without context, validators, responsiveness and other Getx resources, were not explored (and it would not even be possible to explore all resources in such a simple example), so below is an example not very complete, but trying demonstrate how to use internationalization, reactive custom classes, reactive lists, snackbars contextless, workers etc.
```dart
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() {
runApp(GetMaterialApp(
// It is not mandatory to use named routes, but dynamic urls are interesting.
initialRoute: '/home',
defaultTransition: Transition.native,
translations: MyTranslations(),
locale: Locale('pt', 'BR'),
getPages: [
//Simple GetPage
GetPage(name: '/home', page: () => First()),
// GetPage with custom transitions and bindings
GetPage(
name: '/second',
page: () => Second(),
customTransition: SizeTransitions(),
binding: SampleBind(),
),
// GetPage with default transitions
GetPage(
name: '/third',
transition: Transition.cupertino,
page: () => Third(),
),
],
));
}
class MyTranslations extends Translations {
@override
Map<String, Map<String, String>> get keys => {
'en': {
'title': 'Hello World %s',
},
'en_US': {
'title': 'Hello World from US',
},
'pt': {
'title': 'Olá de Portugal',
},
'pt_BR': {
'title': 'Olá do Brasil',
},
};
}
class Controller extends GetxController {
int count = 0;
void increment() {
count++;
// use update method to update all count variables
update();
}
}
class First extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.add),
onPressed: () {
Get.snackbar("Hi", "I'm modern snackbar");
},
),
title: Text("title".trArgs(['John'])),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
GetBuilder<Controller>(
init: Controller(),
// You can initialize your controller here the first time. Don't use init in your other GetBuilders of same controller
builder: (_) => Text(
'clicks: ${_.count}',
)),
ElevatedButton(
child: Text('Next Route'),
onPressed: () {
Get.toNamed('/second');
},
),
ElevatedButton(
child: Text('Change locale to English'),
onPressed: () {
Get.updateLocale(Locale('en', 'UK'));
},
),
],
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
Get.find<Controller>().increment();
}),
);
}
}
class Second extends GetView<ControllerX> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('second Route'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Obx(
() {
print("count1 rebuild");
return Text('${controller.count1}');
},
),
Obx(
() {
print("count2 rebuild");
return Text('${controller.count2}');
},
),
Obx(() {
print("sum rebuild");
return Text('${controller.sum}');
}),
Obx(
() => Text('Name: ${controller.user.value?.name}'),
),
Obx(
() => Text('Age: ${controller.user.value?.age}'),
),
ElevatedButton(
child: Text("Go to last page"),
onPressed: () {
Get.toNamed('/third', arguments: 'arguments of second');
},
),
ElevatedButton(
child: Text("Back page and open snackbar"),
onPressed: () {
Get.back();
Get.snackbar(
'User 123',
'Successfully created',
);
},
),
ElevatedButton(
child: Text("Increment"),
onPressed: () {
controller.increment();
},
),
ElevatedButton(
child: Text("Increment"),
onPressed: () {
controller.increment2();
},
),
ElevatedButton(
child: Text("Update name"),
onPressed: () {
controller.updateUser();
},
),
ElevatedButton(
child: Text("Dispose worker"),
onPressed: () {
controller.disposeWorker();
},
),
],
),
),
);
}
}
class Third extends GetView<ControllerX> {
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(onPressed: () {
controller.incrementList();
}),
appBar: AppBar(
title: Text("Third ${Get.arguments}"),
),
body: Center(
child: Obx(() => ListView.builder(
itemCount: controller.list.length,
itemBuilder: (context, index) {
return Text("${controller.list[index]}");
}))),
);
}
}
class SampleBind extends Bindings {
@override
void dependencies() {
Get.lazyPut<ControllerX>(() => ControllerX());
}
}
class User {
User({this.name = 'Name', this.age = 0});
String name;
int age;
}
class ControllerX extends GetxController {
final count1 = 0.obs;
final count2 = 0.obs;
final list = [56].obs;
final user = User().obs;
updateUser() {
user.update((value) {
value!.name = 'Jose';
value.age = 30;
});
}
/// Once the controller has entered memory, onInit will be called.
/// It is preferable to use onInit instead of class constructors or initState method.
/// Use onInit to trigger initial events like API searches, listeners registration
/// or Workers registration.
/// Workers are event handlers, they do not modify the final result,
/// but it allows you to listen to an event and trigger customized actions.
/// Here is an outline of how you can use them:
/// made this if you need cancel you worker
late Worker _ever;
@override
onInit() {
/// Called every time the variable $_ is changed
_ever = ever(count1, (_) => print("$_ has been changed (ever)"));
everAll([count1, count2], (_) => print("$_ has been changed (everAll)"));
/// Called first time the variable $_ is changed
once(count1, (_) => print("$_ was changed once (once)"));
/// Anti DDos - Called every time the user stops typing for 1 second, for example.
debounce(count1, (_) => print("debouce$_ (debounce)"),
time: Duration(seconds: 1));
/// Ignore all changes within 1 second.
interval(count1, (_) => print("interval $_ (interval)"),
time: Duration(seconds: 1));
}
int get sum => count1.value + count2.value;
increment() => count1.value++;
increment2() => count2.value++;
disposeWorker() {
_ever.dispose();
// or _ever();
}
incrementList() => list.add(75);
}
class SizeTransitions extends CustomTransition {
@override
Widget buildTransition(
BuildContext context,
Curve? curve,
Alignment? alignment,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
return Align(
alignment: Alignment.center,
child: SizeTransition(
sizeFactor: CurvedAnimation(
parent: animation,
curve: curve!,
),
child: child,
),
);
}
}
```

View File

@@ -0,0 +1,12 @@
// ignore_for_file: file_names
const Map<String, String> en_US = {
'covid': 'Corona Virus',
'total_confirmed': 'Total Confirmed',
'total_deaths': 'Total Deaths',
'fetch_country': 'Fetch by country',
'corona_by_country': 'Corona by country',
'total_infecteds': 'Total Infecteds',
'details': 'Details',
'total_recovered': 'Total Recovered',
};

View File

@@ -0,0 +1,12 @@
// ignore_for_file: file_names
const Map<String, String> pt_BR = {
'covid': 'Corona Vírus',
'total_confirmed': 'Total confirmado',
'total_deaths': 'Total de mortes',
'fetch_country': 'Listar por país',
'corona_by_country': 'Corona por país',
'total_infecteds': 'Total de infectados',
'details': 'Detalhes',
'total_recovered': 'Total de recuperados'
};

View File

@@ -0,0 +1,15 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'en_us.dart';
import 'pt_br.dart';
class TranslationService extends Translations {
static Locale? get locale => Get.deviceLocale;
static final fallbackLocale = Locale('en', 'US');
@override
Map<String, Map<String, String>> get keys => {
'en_US': en_US,
'pt_BR': pt_BR,
};
}

View File

@@ -0,0 +1,129 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'lang/translation_service.dart';
import 'routes/app_pages.dart';
import 'shared/logger/logger_utils.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return GetMaterialApp.router(
debugShowCheckedModeBanner: false,
enableLog: true,
logWriterCallback: Logger.write,
// initialRoute: AppPages.INITIAL,
getPages: AppPages.routes,
locale: TranslationService.locale,
fallbackLocale: TranslationService.fallbackLocale,
translations: TranslationService(),
);
}
}
/// Nav 2 snippet
// void main() {
// runApp(MyApp());
// }
// class MyApp extends StatelessWidget {
// MyApp({Key? key}) : super(key: key);
// @override
// Widget build(BuildContext context) {
// return GetMaterialApp.router(
// getPages: [
// GetPage(
// participatesInRootNavigator: true,
// name: '/first',
// page: () => First()),
// GetPage(
// name: '/second',
// page: () => Second(),
// ),
// GetPage(
// name: '/third',
// page: () => Third(),
// ),
// ],
// debugShowCheckedModeBanner: false,
// );
// }
// }
// class First extends StatelessWidget {
// @override
// Widget build(BuildContext context) {
// return Scaffold(
// appBar: AppBar(
// title: Text('page one'),
// leading: IconButton(
// icon: Icon(Icons.more),
// onPressed: () {
// Get.changeTheme(
// context.isDarkMode ? ThemeData.light() : ThemeData.dark());
// },
// ),
// ),
// body: Center(
// child: Container(
// height: 300,
// width: 300,
// child: ElevatedButton(
// onPressed: () {},
// child: Text('next screen'),
// ),
// ),
// ),
// );
// }
// }
// class Second extends StatelessWidget {
// @override
// Widget build(BuildContext context) {
// return Scaffold(
// appBar: AppBar(
// title: Text('page two ${Get.parameters["id"]}'),
// ),
// body: Center(
// child: Container(
// height: 300,
// width: 300,
// child: ElevatedButton(
// onPressed: () {},
// child: Text('next screen'),
// ),
// ),
// ),
// );
// }
// }
// class Third extends StatelessWidget {
// @override
// Widget build(BuildContext context) {
// return Scaffold(
// backgroundColor: Colors.red,
// appBar: AppBar(
// title: Text('page three'),
// ),
// body: Center(
// child: Container(
// height: 300,
// width: 300,
// child: ElevatedButton(
// onPressed: () {},
// child: Text('go to first screen'),
// ),
// ),
// ),
// );
// }
// }

View File

@@ -0,0 +1,15 @@
import 'package:get/get.dart';
import '../data/home_api_provider.dart';
import '../data/home_repository.dart';
import '../domain/adapters/repository_adapter.dart';
import '../presentation/controllers/home_controller.dart';
class HomeBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<IHomeProvider>(() => HomeProvider());
Get.lazyPut<IHomeRepository>(() => HomeRepository(provider: Get.find()));
Get.lazyPut(() => HomeController(homeRepository: Get.find()));
}
}

View File

@@ -0,0 +1,19 @@
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 =
(val) => CasesModel.fromJson(val as Map<String, dynamic>);
httpClient.baseUrl = 'https://api.covid19api.com';
}
@override
Future<Response<CasesModel>> getCases(String path) => get(path);
}

View File

@@ -0,0 +1,18 @@
import '../domain/adapters/repository_adapter.dart';
import '../domain/entity/cases_model.dart';
import 'home_api_provider.dart';
class HomeRepository implements IHomeRepository {
HomeRepository({required this.provider});
final IHomeProvider provider;
@override
Future<CasesModel> getCases() async {
final cases = await provider.getCases("/summary");
if (cases.status.hasError) {
return Future.error(cases.statusText!);
} else {
return cases.body!;
}
}
}

View File

@@ -0,0 +1,6 @@
import '../entity/cases_model.dart';
// ignore: one_member_abstracts
abstract class IHomeRepository {
Future<CasesModel> getCases();
}

View File

@@ -0,0 +1,167 @@
// To parse this JSON data, do
//
// final welcome = welcomeFromJson(jsonString);
import 'dart:convert';
class CasesModel {
CasesModel({
required this.id,
required this.message,
required this.global,
required this.countries,
required this.date,
});
final String id;
final String message;
final Global global;
final List<Country> countries;
final DateTime date;
factory 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(
id: json["ID"] as String,
message: json["Message"] as String,
global: Global.fromJson(json["Global"] as Map<String, dynamic>),
countries: List<Country>.from((json["Countries"] as Iterable).map(
(x) => Country.fromJson(x as Map<String, dynamic>),
)),
date: DateTime.parse(json["Date"] as String),
);
Map<String, dynamic> toJson() => {
"ID": id,
"Message": message,
"Global": global.toJson(),
"Countries": List<dynamic>.from(countries.map((x) => x.toJson())),
"Date": date.toIso8601String(),
};
}
class Country {
Country({
required this.id,
required this.country,
required this.countryCode,
required this.slug,
required this.newConfirmed,
required this.totalConfirmed,
required this.newDeaths,
required this.totalDeaths,
required this.newRecovered,
required this.totalRecovered,
required this.date,
required this.premium,
});
final String id;
final String country;
final String countryCode;
final String slug;
final int newConfirmed;
final int totalConfirmed;
final int newDeaths;
final int totalDeaths;
final int newRecovered;
final int totalRecovered;
final DateTime date;
final Premium premium;
factory Country.fromRawJson(String str) =>
Country.fromJson(json.decode(str) as Map<String, dynamic>);
String toRawJson() => json.encode(toJson());
factory Country.fromJson(Map<String, dynamic> json) => Country(
id: json["ID"] as String,
country: json["Country"] as String,
countryCode: json["CountryCode"] as String,
slug: json["Slug"] as String,
newConfirmed: json["NewConfirmed"] as int,
totalConfirmed: json["TotalConfirmed"] as int,
newDeaths: json["NewDeaths"] as int,
totalDeaths: json["TotalDeaths"] as int,
newRecovered: json["NewRecovered"] as int,
totalRecovered: json["TotalRecovered"] as int,
date: DateTime.parse(json["Date"] as String),
premium: Premium.fromJson(json["Premium"] as Map<String, dynamic>),
);
Map<String, dynamic> toJson() => {
"ID": id,
"Country": country,
"CountryCode": countryCode,
"Slug": slug,
"NewConfirmed": newConfirmed,
"TotalConfirmed": totalConfirmed,
"NewDeaths": newDeaths,
"TotalDeaths": totalDeaths,
"NewRecovered": newRecovered,
"TotalRecovered": totalRecovered,
"Date": date.toIso8601String(),
"Premium": premium.toJson(),
};
}
class Premium {
Premium();
factory Premium.fromRawJson(String str) =>
Premium.fromJson(json.decode(str) as Map<String, dynamic>);
String toRawJson() => json.encode(toJson());
factory Premium.fromJson(Map<String, dynamic> json) => Premium();
Map<String, dynamic> toJson() => {};
}
class Global {
Global({
required this.newConfirmed,
required this.totalConfirmed,
required this.newDeaths,
required this.totalDeaths,
required this.newRecovered,
required this.totalRecovered,
required this.date,
});
final int newConfirmed;
final int totalConfirmed;
final int newDeaths;
final int totalDeaths;
final int newRecovered;
final int totalRecovered;
final DateTime date;
factory Global.fromRawJson(String str) =>
Global.fromJson(json.decode(str) as Map<String, dynamic>);
String toRawJson() => json.encode(toJson());
factory Global.fromJson(Map<String, dynamic> json) => Global(
newConfirmed: json["NewConfirmed"] as int,
totalConfirmed: json["TotalConfirmed"] as int,
newDeaths: json["NewDeaths"] as int,
totalDeaths: json["TotalDeaths"] as int,
newRecovered: json["NewRecovered"] as int,
totalRecovered: json["TotalRecovered"] as int,
date: DateTime.parse(json["Date"] as String),
);
Map<String, dynamic> toJson() => {
"NewConfirmed": newConfirmed,
"TotalConfirmed": totalConfirmed,
"NewDeaths": newDeaths,
"TotalDeaths": totalDeaths,
"NewRecovered": newRecovered,
"TotalRecovered": totalRecovered,
"Date": date.toIso8601String(),
};
}

View File

@@ -0,0 +1,84 @@
import 'package:get/get.dart';
import '../../domain/adapters/repository_adapter.dart';
import '../../domain/entity/cases_model.dart';
class HomeController extends SuperController<CasesModel> {
HomeController({required this.homeRepository});
final IHomeRepository homeRepository;
@override
void onInit() {
super.onInit();
//Loading, Success, Error handle with 1 line of code
append(() => homeRepository.getCases);
}
Country getCountryById(String id) {
final index = int.tryParse(id);
if (index != null) {
return state!.countries[index];
}
return state!.countries.first;
}
@override
void onReady() {
print('The build method is done. '
'Your controller is ready to call dialogs and snackbars');
super.onReady();
}
@override
void onClose() {
print('onClose called');
super.onClose();
}
@override
void didChangeMetrics() {
print('the window size did change');
super.didChangeMetrics();
}
@override
void didChangePlatformBrightness() {
print('platform change ThemeMode');
super.didChangePlatformBrightness();
}
@override
Future<bool> didPushRoute(String route) {
print('the route $route will be open');
return super.didPushRoute(route);
}
@override
Future<bool> didPopRoute() {
print('the current route will be closed');
return super.didPopRoute();
}
@override
void onDetached() {
print('onDetached called');
}
@override
void onInactive() {
print('onInative called');
}
@override
void onPaused() {
print('onPaused called');
}
@override
void onResumed() {
print('onResumed called');
}
}

View File

@@ -0,0 +1,56 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../controllers/home_controller.dart';
class CountryView extends GetView<HomeController> {
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
colorFilter: ColorFilter.linearToSrgbGamma(),
image: NetworkImage(
"https://images.pexels.com/photos/3902882/pexels-photo-3902882.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940"))),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 15.0, sigmaY: 15.0),
child: Container(
child: Scaffold(
backgroundColor: Colors.transparent,
appBar: AppBar(
title: Text('corona_by_country'.tr),
backgroundColor: Colors.transparent,
elevation: 0,
centerTitle: true,
),
body: Center(
child: ListView.builder(
itemCount: controller.state!.countries.length,
itemBuilder: (context, index) {
final country = controller.state!.countries[index];
return ListTile(
onTap: () {
//Get.rootDelegate.toNamed('/home/country');
Get.rootDelegate
.toNamed('/home/country/details?id=$index');
},
trailing: CircleAvatar(
backgroundImage: NetworkImage(
"https://flagpedia.net/data/flags/normal/${country.countryCode.toLowerCase()}.png"),
),
title: Text(country.country),
subtitle: Text(
// ignore: lines_longer_than_80_chars
'${'total_infecteds'.tr}${' ${country.totalConfirmed}'}'),
);
}),
),
),
),
),
);
}
}

View File

@@ -0,0 +1,86 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../controllers/home_controller.dart';
class DetailsView extends GetView<HomeController> {
@override
Widget build(BuildContext context) {
final parameter = Get.rootDelegate.parameters;
final country = controller.getCountryById(parameter['id'] ?? '');
return Container(
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
colorFilter: ColorFilter.linearToSrgbGamma(),
image: NetworkImage(
"https://flagpedia.net/data/flags/normal/${country.countryCode.toLowerCase()}.png"),
),
),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 15.0, sigmaY: 15.0),
child: Container(
child: Scaffold(
backgroundColor: Colors.transparent,
appBar: AppBar(
title: Text('details'.tr),
backgroundColor: Colors.black12,
elevation: 0,
centerTitle: true,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'${country.country}',
style: TextStyle(fontSize: 45, fontWeight: FontWeight.bold),
),
SizedBox(
height: 35,
),
Text(
'total_confirmed'.tr,
style: TextStyle(
fontSize: 25,
),
),
Text(
'${country.totalConfirmed}',
style: TextStyle(fontSize: 35, fontWeight: FontWeight.bold),
),
SizedBox(
height: 10,
),
Text(
'total_deaths'.tr,
style: TextStyle(
fontSize: 25,
),
),
Text(
'${country.totalDeaths}',
style: TextStyle(fontSize: 35, fontWeight: FontWeight.bold),
),
SizedBox(
height: 10,
),
Text(
'total_recovered'.tr,
style: TextStyle(
fontSize: 25,
),
),
Text(
'${country.totalRecovered}',
style: TextStyle(fontSize: 35, fontWeight: FontWeight.bold),
),
],
)),
),
),
),
);
}
}

View File

@@ -0,0 +1,118 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../controllers/home_controller.dart';
class HomeView extends GetView<HomeController> {
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
image: DecorationImage(
fit: BoxFit.cover,
colorFilter: ColorFilter.linearToSrgbGamma(),
image: NetworkImage(
"https://images.pexels.com/photos/3902882/pexels-photo-3902882.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940"),
),
),
child: Scaffold(
backgroundColor: Colors.transparent,
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.add),
onPressed: () {
Get.snackbar('title', 'message');
},
),
title: Text('covid'.tr),
backgroundColor: Colors.white10,
elevation: 0,
centerTitle: true,
),
body: Center(
child: controller.obx(
(state) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
height: 100,
),
Text(
'total_confirmed'.tr,
style: TextStyle(
fontSize: 30,
),
),
Text(
'${state!.global.totalConfirmed}',
style: TextStyle(fontSize: 45, fontWeight: FontWeight.bold),
),
SizedBox(
height: 10,
),
Text(
'total_deaths'.tr,
style: TextStyle(
fontSize: 30,
),
),
Text(
'${state.global.totalDeaths}',
style: TextStyle(fontSize: 45, fontWeight: FontWeight.bold),
),
SizedBox(
height: 10,
),
OutlinedButton(
style: OutlinedButton.styleFrom(
textStyle: TextStyle(color: Colors.black),
side: BorderSide(
color: Colors.deepPurple,
width: 3,
),
shape: StadiumBorder(),
),
onPressed: () async {
final data =
await Get.rootDelegate.toNamed('/home/country');
print('DATA: $data');
},
child: Text(
'fetch_country'.tr,
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
),
OutlinedButton(
style: OutlinedButton.styleFrom(
textStyle: TextStyle(color: Colors.black),
side: BorderSide(
color: Colors.deepPurple,
width: 3,
),
shape: StadiumBorder(),
),
onPressed: () {
Get.updateLocale(Locale('pt', 'BR'));
},
child: Text(
'Update language to Portuguese',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
),
],
);
},
),
),
),
);
}
}

View File

@@ -0,0 +1,32 @@
import 'package:get/get.dart';
import '../pages/home/bindings/home_binding.dart';
import '../pages/home/presentation/views/country_view.dart';
import '../pages/home/presentation/views/details_view.dart';
import '../pages/home/presentation/views/home_view.dart';
part 'app_routes.dart';
// ignore: avoid_classes_with_only_static_members
class AppPages {
static const INITIAL = Routes.HOME;
static final routes = [
GetPage(
name: Routes.HOME,
page: () => HomeView(),
binding: HomeBinding(),
children: [
GetPage(
name: Routes.COUNTRY,
page: () => CountryView(),
children: [
GetPage(
name: Routes.DETAILS,
page: () => DetailsView(),
),
],
),
]),
];
}

View File

@@ -0,0 +1,7 @@
part of 'app_pages.dart';
abstract class Routes {
static const HOME = '/home';
static const COUNTRY = '/country';
static const DETAILS = '/details';
}

View File

@@ -0,0 +1,6 @@
mixin Logger {
// Sample of abstract logging function
static void write(String text, {bool isError = false}) {
Future.microtask(() => print('** $text. isError: [$isError]'));
}
}

Some files were not shown because too many files have changed in this diff Show More