Languages: English, Brazilian Portuguese.
Get é uma biblioteca poderosa e extra-leve para Flutter que vai te dar superpoderes e aumentar sua produtividade. Navegue sem precisar do context
, abra Dialog
s, Snackbar
s ou BottomSheet
s de qualquer lugar no código, gerencie estados e injete dependências de uma forma simples e prática! Get é seguro, estável, atualizado e oferece uma enorme gama de APIs que não estão presentes no framework padrão.
// Navigator padrão do Flutter
Navigator.of(context).push(
context,
MaterialPageRoute(
builder: (BuildContext context) {
return Home();
},
),
);
// Sintaxe do Get
Get.to(Home());
Começando
A navegação convencional do Flutter tem uma grande quantidade de boilerplate (código que se repete demais), requer o context
para navegar entre telas/rotas, abrir dialogs e usar snackbars no framework, e é entediante.
Somando a isso, quando uma rota é enviada (uma tela carregada), o MaterialApp
inteiro pode ser reconstruído podendo causando travamentos. Isso não acontece com o Get.
Essa biblioteca vai mudar a forma que você trabalha com o Framework e salvar seu código dos boilerplates, aumentando sua produtividade, e eliminando os bugs de reconstrução da sua aplicação.
- Começando
- Como usar?
- Navegação sem rotas nomeadas
- Gerenciador de estado simples
- Reactive State Manager - GetX
- Gerenciamento de dependências simples
- Navegar com rotas nomeadas
Você pode contribuir no projeto de várias formas:
- Ajudando a traduzir o README para outras linguagens.
- Adicionando mais documentação ao README (até o momento, nem metade das funcionalidades do Get foram documentadas).
- Fazendo artigos/vídeos sobre o Get (eles serão inseridos no README, e no futuro na nossa Wiki).
- Fazendo PR's (Pull-Requests) para código/testes.
- Incluindo novas funcionalidades.
Qualquer contribuição é bem-vinda!
Como usar?
Adicione Get ao seu arquivo pubspec.yaml
Troque seu MaterialApp()
por GetMaterialApp()
e aproveite!
import 'package:get/get.dart';
GetMaterialApp( // Before: MaterialApp(
home: MyHome(),
)
Navegação sem rotas nomeadas
Para navegar para uma próxima tela:
Get.to(ProximaTela());
Para retornar para a tela anterior:
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.):
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):
Get.offAll(ProximaTela());
Para navegar para a próxima rota, e receber ou atualizar dados assim que retornar da rota:
var dados = await Get.to(Pagamento());
Na outra tela, envie os dados para a rota anterior:
Get.back(result: 'sucesso');
E use-os:
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:
// 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 pegar o context
do Scaffold, ou você precisa de uma GlobalKey
atrelada ao seu Scaffold.
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:
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!
Get.snackbar(
"Ei, eu sou uma SnackBar Get!", // título
"É inacreditável! Eu estou usando uma SnackBar sem context, sem " +
"boilerplate, sem Scaffold, é algo realmente maravilhoso!", // 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,
// FlatButton 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 só uma linha (Get.snackbar
tem os parâmetros title
e message
obrigatórios), você pode usar Get.rawSnackbar();
que fornece a RAW API na qual Get.snackbar
foi contruído.
Dialogs
Para abrir um dialog:
Get.dialog(SeuWidgetDialog());
Para abrir um dialog padrão:
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 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.
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: () => {},
),
],
),
),
);
Gerenciador de estado simples
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, 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 (escrito em apenas 95 linha de código), 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?
Atualiza somente o widget necessário.
Não usa o
ChangeNotifier
, é o gerenciador de estado que utiliza menos memória (próximo de 0mb até agora).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.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.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.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.Use
Stream
s somente se necessário. Você pode usar seus StreamControllers dentro do seu controller normalmente, e usarStreamBuilder
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 oChangeNotifier
(e olha que o ChangeNotifier é bem ruim)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ó.
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
// Crie a classe Controller e entenda ela do GetController
class Controller extends GetController {
int counter = 0;
void increment() {
counter++;
update(this); // use update(this) 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):
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>()
)
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.counter();
/// sem o método estático: Get.find<Controller>();
/// 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(this);
}
}
E então você pode acessar seu controller diretamente, desse jeito:
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, 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 vai 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:
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.
@override
void onInit() {
fetchApi();
super.onInit();
}
- Nota: Se você quiser rodar 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 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()
eonClose()
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 usarinitState()/dispose()
, apenas comece sua chamada para a api noonInit
, e apenas se você precisar executar algum comando como fechar stream, use oonClose()
.
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 decoupling. 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 coupling.
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 views 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á automatacimente e inteligentemente removido da memória pelo Get. Se você usou algum stream no controller e quer fechá-lo, apensar insira o método para fechar os stream dentro do método onClose()
.
Exemplo:
class Controller extends GetController {
var user = StreamController<User>();
var name = StreamController<String>();
/// close stream = onClose method, not 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 mais acesso a essa API porque ela está literalmente removendo o controller da memória. Está literalmente deletado, sem deixar rastros.
Formas de uso:
- Uso recomendado;
Você pode usar uma instância do Controller diretamente no value
do GetBuilder
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:
class Controller extends GetController {
static Controller get to => Get.find(); // criando um getter estático
[...]
}
// Numa classe stateful/stateless
GetBuilder<Controller>(
init: Controller(), // use somente uma vez por controller, não se esqueça
builder: (_) => Text(
'${Controller.to.counter}', //aqui
)
),
ou essa:
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:
Controller controller = Controller();
[...]
GetBuilder( // não precisa digitar desse jeito
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
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:
update(this,['text']);
Você também pode impor condições para o update acontecer:
update(this,['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 davariável para o mesmo valor que ela já era 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 é pra 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(this)
. 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 - GetX
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:
final count1 = 0.obs;
final count2 = 0.obs;
int get sum => count1.value + count2.value;
GetX<Controller>(
builder: (_) {
print("count 1 rebuild");
return Text('${_.count1.value}');
},
),
GetX<Controller>(
builder: (_) {
print("count 2 rebuild");
return Text('${_.count2.value}');
},
),
GetX<Controller>(
builder: (_) {
print("sum rebuild");
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 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).
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:
class CountController extends RxController {
final count = 0.obs;
}
E com um simples:
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:
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;
}
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
Antes de você entrar de cabeça nesse mundo, eu vou te dar uma dica: sempre acesse o .value
do seu flow quando estiver lendo ele, especialmente se estier trabalhando com Lists onde isso é aparentemente opcional.
Você pode acessar list.length
ou list.value.length
. Na maioria do tempo, os dois vão funcionar, já que a list do GetX herda diretamente do Dart list. Mas tem uma diferença entre você acessando o objeto, e você acessando o flow. Eu recomendo fortemente que você acesse o .value
:
final list = List<Usuario>().obs;
ListView.builder (
itemCount: list.value.lenght
)
ou então criar um getter para ele e abandonar o "value" para sempre. Exemplo:
final _list = List<Usuario>().obs;
List get list => _list.value;
ListView.builder (
itemCount: list.lenght
Você pode adicionar uma lista existente de outro tipo para o observable list usando um list.assign(oldList)
ou o método assignAll()
, que difere de add()
e addAll()
, que precisa ser do mesmo tipo. Todos os métodos existente num list também estão disponíveis no GetX.
Nós poderíamos remover a obrigação de usar o value 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, dependencias e estados), numa forma simples, leve e performática sem precisar de packages externos. Você pode literalmente adicionar 3 letras e um ':' 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 aquele que querem adicionar uma única dependência no pubspec a começar a programar sem se preocupar sobre a versão do package sendo incompatível com outra, ou se o erro de uma atualização do estado está vind 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.
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 gerenciado 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 o README como uma alternativa, minha necessidade não é a necessidade de todos os devs, mas por agora eu digo: há boas solução que já fazem isso, como MobX.
Gerenciamento de dependências simples
Já está usando o Get e quer fazer seu projeto o melhor possível? Get tem um gerenciado de dependência simpels 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:
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
controller.fetchApi();
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:
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:
Text(controller.textFromApi);
Procurando por lazyLoading
?(carregar somente quando for usar) Você pode declarar todos os seus controllers, e eles só vão ser inicializados e chamados quando alguém precisar. Você pode fazer isso
Get.lazyPut<Service>(()=> ApiMock());
/// ApiMock só sera chamado quando alguém usar o Get.find<Service> pela primeira vez
Para remover a instância do Get:
Get.delete<Controller>();
Navegar com rotas nomeadas
- Se você prefere navegar por rotas nomeadas, Get também dá suporte a isso:
Para navegar para uma nova tela
Get.toNamed("/NextScreen");
Para navegar para uma tela sem a opção de voltar para a rota atual.
Get.offNamed("/NextScreen");
Para navegar para uma nova tela e remover todas rotas anteriores da stack
Get.offAllNamed("/NextScreen");
Para definir rotas, use o GetMaterialApp
:
void main() {
runApp(
GetMaterialApp(
initialRoute: '/',
namedRoutes: {
'/': GetRoute(page: MyHomePage()),
'/login': GetRoute(page: Login()),
'/cadastro': GetRoute(page: Cadastro(),transition: Transition.cupertino);
},
)
);
}
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.
Get.toNamed("/ProximaTela", arguments: 'Get é o melhor');
Na sua classe ou controller:
print(Get.arguments); //mostra: 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 featura no Flutter, e muito provavelmente viram um package que promete essa feature e entrega uma sintaxe totalmente diferente do que uma url teria na web, mas o Get também resolve isso.
Get.offAllNamed("/ProximaTela?device=phone&id=354&name=Enzo");
na sua classe controller/bloc/stateful/stateless:
print(Get.parameters['id']); // valor: 354
print(Get.parameters['name']); // valor: Enzo
Você também pode receber parâmetros nomeados com o Get facilmente:
void main() => runApp(
GetMaterialApp(
initialRoute: '/',
namedRoutes: {
'/': GetRoute(page: MyHomePage()),
/// Importante! :user não é uma nova rota, é somente uma
/// especificação do parâmentro. Não use '/second/:user/' e '/second'
/// se você precisa de uma nova rota para o user, então
/// use '/second/user/:user' se '/second' for uma rota
'/second/:user': GetRoute(page: Second()), // recebe a ID
'/third': GetRoute(page: Third(),transition: Transition.cupertino);
},
),
);
Envie dados na rota nomeada
Get.toNamed("/second/34954");
Na segunda tela receba os dados usando Get.parameters[]
print(Get.parameters['user']); // valor: 34954
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
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 listen Get events to trigger actions, you can to use routingCallback to it
GetMaterialApp(
routingCallback: (route){
if(routing.current == '/second'){
openAds();
}
}
If you are not using GetMaterialApp, you can use the manual API to attach Middleware observer.
void main() {
runApp(MaterialApp(
onGenerateRoute: Router.generateRoute,
initialRoute: "/",
navigatorKey: Get.key,
navigatorObservers: [
GetObserver(MiddleWare.observer), // HERE !!!
],
));
}
Create a MiddleWare class
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:
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: RaisedButton(
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: RaisedButton(
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: RaisedButton(
onPressed: () {
Get.back();
},
child: Text('Go back!'),
),
),
);
}
}
Change Theme
Please do not use any higher level widget than GetMaterialApp in order to update it. This can trigger duplicate keys. A lot of people are used to the prehistoric approach of creating a "ThemeProvider" widget just to change the theme of your app, and this is definitely NOT necessary with Get.
You can create your custom theme and simply add it within Get.changeTheme without any boilerplate for that:
Get.changeTheme(ThemeData.light());
If you want to create something like a button that changes the theme with onTap, you can combine two Get APIs for that, the api that checks if the dark theme is being used, and the theme change API, you can just put this within an onPressed:
Get.changeTheme(Get.isDarkMode? ThemeData.light(): ThemeData.dark());
When darkmode is activated, it will switch to the light theme, and when the light theme is activated, it will change to dark.
If you want to know in depth how to change the theme, you can follow this tutorial on Medium that even teaches the persistence of the theme using Get:
- Dynamic Themes in 3 lines using Get - Tutorial by Rod Brown.
Optional Global Settings
You can create Global settings for Get. Just add Get.config to your code before pushing any route or do it directly in your GetMaterialApp
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}
Other Advanced APIs and Manual configurations
GetMaterialApp configures everything for you, but if you want to configure Get Manually using advanced APIs.
MaterialApp(
navigatorKey: Get.key,
navigatorObservers: [GetObserver()],
);
You will also be able to use your own Middleware within GetObserver, this will not influence anything.
MaterialApp(
navigatorKey: Get.key,
navigatorObservers: [GetObserver(MiddleWare.observer)], // Here
);
Get.arguments // give the current args from currentScreen
Get.previousArguments // give arguments of previous route
Get.previousRoute // give name of previous route
Get.rawRoute // give the raw route to access for example, rawRoute.isFirst()
Get.routing // give access to Rounting API from GetObserver
Get.isSnackbarOpen // check if snackbar is open
Get.isDialogOpen // check if dialog is open
Get.isBottomSheetOpen // check if bottomsheet is open
Get.removeRoute() // remove one route.
Get.until() // back repeatedly until the predicate returns true.
Get.offUntil() // go to next route and remove all the previous routes until the predicate returns true.
Get.offNamedUntil() // go to next named route and remove all the previous routes until the predicate returns true.
GetPlatform.isAndroid/isIOS/isWeb... //(This method is completely compatible with FlutterWeb, unlike the framework. "Platform.isAndroid")
Get.height / Get.width // Equivalent to the method: MediaQuery.of(context).size.height
Get.context // Gives the context of the screen in the foreground anywhere in your code.
Get.contextOverlay // Gives the context of the snackbar/dialog/bottomsheet in the foreground anywhere in your code.
Nested Navigators
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:
Navigator(
key: nestedKey(1), // create a key by index
initialRoute: '/',
onGenerateRoute: (settings) {
if (settings.name == '/') {
return GetRouteBase(
page: Scaffold(
appBar: AppBar(
title: Text("Main"),
),
body: Center(
child: FlatButton(
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 GetRouteBase(
page: Center(
child: Scaffold(
appBar: AppBar(
title: Text("Main"),
),
body: Center(
child: Text("second")
),
),
),
);
}
}),
This library will always be updated and implementing new features. Feel free to offer PRs and contribute to them.