Introduzir uma lista de valores dentro de outra lista é muito simples, basta utilizar o operador “…” e pronto….

var lista1 = [1,2,3];
var lista2 = [4,5,6];

var listaCombinada = [...lista1,...lista2];

Uso de listas combinadas – Merging widgets

Você tem um ListView que receberá dados individuais e ainda deseja introduzir uma lista que foi montada em operação anterior / recebeu como parâmetro. O resultado final desejado é a combinação destes elementos:

...
var lista1 = [produto1, produto2, produto3];

...
return ListView(children:[
    titulo,
    Divider(), 
    ...lista1, 
    Divider(), 
    rodape]);

Filip Hracek publicou esta semana artigo sobre a introdução de “null safety” no site do Dart reforçando o “Announcing Dart 2.8” feito pelo Michael Thomsen que já abordava “Breaking changes paving the way for null safety” no início de maio corrente.

Poderia ser somente mais um dos vários artigos que ele traz de contribuição, mas não é.

Seu código vai bugar

Dart é uma linguagem planejada para ser “type-safe” o que significa que uma variável uma vez declarada com um tipo não pode sofrer mutação ao longo da sua vida. Filip relata o que nós sentimos no dia-a-dia, que um número muito grande de “issues” são causados por “nulls” no Dart. Com o plano de introduzir “Null Safety” a alteração irá deixar o compilador mais robusto ao checar em tempo de compilação se houve alguma violação de regra e lá pelo milésima linha foi atribuído um “null” para a variável.

Buscando solução, está nos planos no team incluir como padrão a checagem de “null safety”, ou seja, código anteriores que não observarem a nova regra irá parar de compilar.

Nem tudo foi perdido

Pensando que em alguns casos realmente o desejado seja a possibilidade da variável vir a receber “null” a algum tempo, o time cria um nova anotação nas variáveis / parâmetros para indicar que poderá receber “null” como valor ao longo da sua existência.

Exemplos:

double? valor = 0;  /// indica que variável valor poderá receber "null"

ou

double outro = 0;  /// variável não poderá receber "null" 

O mesmo se aplica aos parâmetros de funções/métodos – podendo receber “?” para indicar que podem receber valor “null”… ou se não for indicado – não poderá.

...
mostrar( String? texto ){
   ....
}

String? pegarValor()=>xxxx;   // aceita "null" como resposta

Transição – planejada

A previsão é que até o final do ano a implementação esteja concluída, então este período será necessário que as “package” e aplicativos planejem a alteração e teste, ainda que não há recomendação de uso em produção no momento – se bem que no “channel beta” que utilizo já esta avisando sobre pontos de quebra do código por conta de “nulls”.

O esperado no momento é que a versão estável virá como padrão não aceitar “null” por padrão, então é bom ficar atentos.

Operadores ternários demandam uma expressão lógica e dois valores de expressões, o primeiro utilizado para “true” e o segundo quando a expressão lógica for “false”:

Como um operador de atribuição de valores, podemos:

var w = (mostrar)?widget1:widget2;

quando “mostrar” for positivo será atribuído “widget1” para a variável w, se negativo “widget2”;

Imaginando que temos um aplicativo que agora esta crescendo. Novos desafios vão surgindo com o tamanho crescente e provável que algumas informações serão compartilhadas com as várias páginas, tais como usuário, algum código de identificação, temas… não faltarão dados a serem compartilhados de forma global. Binding vai ajudar nisto.

Com Binding você pode indicar todos os “controllers” que serão utilizado ao longo das páginas, alguns instanciados logo na entrada e outros quando o usuário fizer uma solicitação tardia (Lazy Load) do controle.

Para carga tardia de “Controller” Get possui um método Get.lazyPut(….) que põe o controle na pilha global e deixa lá para ser instanciado quando for chamado.

class <strong>Setup</strong> extends Bindings {
  @override
  void dependencies() {
    /// inicialização
    <strong>Get.lazyPut&lt;Controller>(()</strong> => Controller(0));
    Get.lazyPut&lt;ControllerX>(() => ControllerX(0));
  }
}

Usando Get.lazyPut(..) o chamada ao “binding” vai associado as páginas que vão consumir os controles. A chamada a Setup.dependencias é feito ao abrir uma nova página que referencia a ela:

return <strong>GetMaterialApp</strong>(
      title: 'Flutter Demo',
      namedRoutes: {
        '/': GetRoute(
          page: MyHomePage(title: 'Flutter Demo Get'),
          <strong>binding: Setup(),</strong>
        ),
      },
      initialRoute: '/',
    );

Aqui cabe algumas considerações. Usando Get.put( classe() ), a instancia é criado de imediato e não faz checagem se já foi instanciado em operação anterior, portanto seu uso deve levar em consideração se é desejável manter varias instancias do mesmo controle. Com Get.lazyPut(…) este problema é evitado. Além de por na pilho, ele também checa a sua existência fazendo funcione como um singleton reutilizando a mesma instancia – sendo adequado para controle global do dado. Mesmo nas situações de apontar para o memo binding mais de uma vez, a instância será única.

Ainda cabe um consideração sobre o comportamento “errático” do Get.lazyPut(…) que talvez poderia ser melhorado. Se a chamada do Get.lazyPut(…) for chamada fora de uma situação que se deseja uma instância de imediato – não tem este comportamento, chamou – cria a instância, quando o ideal seria instância somente quando executa o Get.find<>() ou quando fosse utilizar de fato.

Este comportamento compromete ter um único binding para várias situações, impondo a criação de “bindings” diferentes para a diversidade de controles desejados. Se uma página não consome um determinado controller irá exigir que tenha “binding” separado do principal, quando o ideal seria mantê-lo somente o seu tipo na pilha e não a sua instância, permitindo declará-lo mesmo quando ainda não se sabe o momento do uso.

Se gostou, deixa seu “like” na página e comentários para nos motivar a continuar escrevendo… Não gostou, não tem problema, gostaria de saber sua opinião mesmo assim.

Git: https://github.com/amarildolacerda/get_samples

No post #2 vimos como implementar reatividade com GetBuilder, mas não acabou.

Na leitura da documentação de hoje (05/6/20) notei que havia algo que não tinha visto em leituras anteriores: GetX;

Na primeira leitura me pareceu muito interessante, mas precisava de exemplos para entender o processo, então vamos lá tentar dismitificar este tal.

Entendendo, é uma implementação reativo utilizando Stream, ou seja, lá no seu interior temos Stream notificando as alteraçoes. Então, neste formado ele é mais próximo de um BLoC.

Consumindo “extension” para adicionar reatividade

Em outro post já escrevi sobre as “extension”, isto mesmo… os observers de GetX utilizam “extension” para incluir reatividade.

Exemplo: var contador = 0.obs;

No exemplo ‘.obs’ é uma propriedade/método do “int” que gera uma Stream para “contador” ou se preferir teremos um “listener” que notifica seus assinantes.

O que fazer com isto? Como Stream é possível fazer do jeito mais difícil (só para brincar como Stream):

/// testando o '.obs como stream"
Widget everBuilder(RxInterface listener, Widget Function(dynamic) callback,
    {bool condition = true}) {
  return StreamBuilder(
      stream: listener.subject.stream,
      builder: (x, y) {
        if (y.hasData) return callback(y.data.$new);
        return Container();
      });
}

com everBuilder (….) é possível consumir o ‘contador’ como um Stream tradicional do Dart, da seguinte forma:

 @override
  Widget build(BuildContext context) { 
      /// usando StreamBuilder
      return    everBuilder(contador, (x) {
            return Text('$x');
          });
.....

mas, se o que precisa é um “listener” tradicional, então pode usar o “ever(…)” como a seguir:

&nbsp;ever(contador,&nbsp;(x)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print('ever:&nbsp;${contador.value}&nbsp;-&nbsp;$x'); });

StreamBuilder – pode esquecer, agora “Obx(…)”

Quando escrevi o “everBuilder” ainda não havia me atentado para uma parte bem rápida da documentação que mostra o “Obx(…)”… então esquece, StsreamBuilder ficou ultrapassado. Mantive o “everBuilder” somente para poder ilustrar, mas poderia muito bem ter descartado e ir direto para o Obx..

Diga, lá… então como usar este tal?

Imagina aquele contador de exemplo “acima”, se desejar que la no meio da construção ele faça uma atualização de um Widget, Obx resolve – ele é um builder que lida com tipos de dados ‘.obs’ como no caso do “contador” fazendo assim:

@override
  Widget build(BuildContext context) {
  ....
  return Obx(() => Text('${contador.value}'));
...

Isto mesmo que você esta imaginando, toda vez que algum código mudar o contador (contador.value ++), irá enviar uma mensagem ao “Obx” indicando que o valor mudou, e portanto precisa refazer o “Text(…)”.

Gostou do Obx? então “deixa um gostei” e deixa os seus comentários ou dúvidas. Assim nos motiva a estudar novos recursos.

O conjunto “Controller” e “GetBuilder” permite que a aplicação se torne reativa, ou seja, ele pode atualizar um dado na página do aplicativo.

Em um primeiro momento quando comparado com “ValueNotifier“, bem simplista, o ganho é quase inexistente. Assim como “ValueNotifier” que exige instânciar o objeto e estabelecer o seu gerenciador de estado “ValueListenableBuilder“, em um primeiro olhar (incorreto), o mesmo ocorria com seus equivalentes “Controller” e “GetBuilder”, vejamos o exemplo:

Criando o “Controller”:

class Controller extends GetController {
  int value;
  Controller(this.value) {
    print('Controller $value foi criado');
  }
  inc([int v = 1]) {
    value += v;
    update();
  }

  dec([int v = 1]) {
    inc(-v);
  }
}

Usando o “GetBuilder”:

Widget build(BuildContext context) {
    return ContainerGroup(
      title: 'GetBuilder',
      width: 150,
      child: GetBuilder<Controller>(
          // init: Controller(0),   // transferido para o binding (Setup() na carga no main.dart)
          builder: (a) => Container(
                alignment: Alignment.center,
                width: 140,
                child: Row(
                    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                    children: [
                      Button(
                          width: 40,
                          child: Text('+'),
                          onPressed: () => a.inc()),
                      Text('${a.value}'),
                      Button(
                          width: 40,
                          child: Text('-'),
                          onPressed: () => a.dec()),
                    ]),
              )),
    );
  }

onInit / onClose – Controller

O “Controller” ganho dois eventos que ser sobrescrito (@override) permindo uma separação do código um pouco mais agrupado. onInit é chamado ao criar o GetBuilder (que é um StatefulWidget) equando que onClose é chamado ao ser encerrado.

Os eventos estão encapsulados no “initState” e “dispose” de um StatefulWidget/State, ou seja, então aqueles códigos de inicialização e finalização podem ser transferidos para o “Controller”, entregando mais separação de código ao controller melhorando a leitura e manutenção.

GetBuilder turbinado – sem instanciar o controller

É aqui que o GetBuilder ganha força, NÃO requer uma instância do controller (requer somente na primeira chamada).

Neste ponto simplifica o uso de “controllers” que são utilizados em diferentes classes; o parâmetro “global:true” permite utilizar ou compartilhar a mesma instância do objeto, funcionando como um singleton.

O “GetBuilder” reaproveita o “controller” da primeira chamada armazenada em uma pilha “global”, reutilizando-o nas próximas chamadas.

Como agora a instância é global é bem vindo a propriedade “autoRemove” do “GetBuilder” instruindo a destruição da instância criada sem a necessidade de uma chamada direta ao “dispose”.

https://pub.dev/packages/get

A “package” Get para flutter esta brilhando, então não se perca com as críticas e vamos tentar melhorar o que já é bem interessante.

Porque escrever sobre Get? Bem, pergunta que já me fiz – recorrente. Olhando a documentação, minha primeira impressão é muito confusa e não é a altura da importância e facilidade que introduz – então é preciso uma ajuda para simplificar o texto na mesma altura que a simplificação da “package” nos fornece.

Passo 0 – Instalando o Get

Simples, vai la no arquivo “pubspec.yaml” e inclui o link para “get:”

dependencies:
  flutter:
    sdk: flutter

  get:

Passo 0.0 – Inicializando o Get

A inicialização da estrutura do “Get” consiste basicamente em trocar o “MaterialApp” na entrada do App por “GetMaterialApp“, veja:

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    /// GetMaterialApp -> inicializa do Get
    return GetMaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

1 – “snackbar” em uma linha

Seria redundanto repetir a todo momento a simplicidade, então vamos a como gerar a mensagem:

 Get.snackbar(
    'titulo', 
    'mensagem do snakbar',
    snackPosition: SnackPosition.BOTTOM);

a “package” é muito extensa, ficamos para o próximo capítulo…

O comportamento de variáveis na memória ao longo do tempo pode assumir dois estados bem simples, mas relevante do ponto de visto do controle de fluxo de dados.

Se ao enviar uma variável a uma função e a função assume um novo endereço de memória, nomeamos a passagem desta variável como “by value” (passagem por valor). De outro lado se a função recebe a variável e manipula seu valor no mesmo endereço de memória de origem, então a passagem da variável foi “by reference” (por referência).

Quando uma variáviel é passada para a função por referência, as alterações de seus valores (ou valor) será propagado para o mesmo endereço ou seja, quem chamou a variável também passará a acessar o valor alterado.

Em muitos casos este comportamento é fundamental para fazer controle de valores dos dados onde as alterações são propagadas para todos os pontos de seu uso, mas… nem sempre isto é o desejado – as vezes o desejado é fazer um simples clone dos valores e armazenado em outro endereço sem afetar a valor original.

Como Dart trata – Primitivos x Objetos

Há uma separação no Dart em duas formas básicas: a) tipos primitivos, como “int”, “String”, “double”, “num”; b) objetos;

Os tipos primitivos ao serem enviados como parâmetro de função/método são passados por valor, ou seja, a função recebe um novo endereço de memória. De outro lado, os objetos de classes são passador por referência (o mesmo endereço);

Dilema Referência X Valor

Se dados primitivos são passados por valor, então como posso passar esses valores por referência ? A resposta é trivial, mas nem sempre tão evidente – encapsula o dado primitivo em uma classe e passa o objeto desta classe.

Ex: criando a classe que irá encapsular o valor primitivo

class MinhaClasse{
  double valor;
  MinhaClasse(this.valor);
}

Mas… sempre tem um mas, tenho um objeto e desejo que as informações de origem sejam mantidas sem interferência das manipulações que a função o fez – Hummm, você vai precisa fazer um CLONE da classe gerando uma nova instância da mesma classe e colando os seus valores, então agora, pode passar a segunda instância para a função, garantindo que os dados originais não serão afetados.

ex: usando “MinhaClasse”

/// valor original
class MinhaClasse{  
    double valor;  
    MinhaClasse(this.valor);
   /// gera uma nava instancia com o mesmo valor de origem
    clone(){
        return MinhaClasse(valor);  
       
    }
}    

classe.copyWith

Várias classes de Flutter/Dart possuem uma função “copyWith” que gera uma cópia da classe permitindo por parâmetro alterar algumas de suas variáveis.

Um exemplo desta implementação é possível encontrar em ThemeData, onde o copyWith gera um nova instância e ainda permite passar valores alterando seu valores originais.

ex: alterando “primaryColor” para os próximos widgets

/// trocando o primaryColor
return  Theme(
         data:      Theme.of(context).copyWith(primaryColor:Colors.green),
      child: Container(.....)
 );

No exemplo, copyWith irá gerar novos valores para o “Theme” sem alterar os valores originais, funcionando como um “CLONE” parametrizável.

Quantas vezes você gostaria de ter um novo método em uma classe padrão? Certamente não foram poucas e se já estiver a algum tempo trabalhando com Flutter/Dart. Helpers como também pode ser chamada em outras linguagens de programação, são implementações relativamente novas entre as linguagens e se propõe ir além da estrutura original desenhada pelo autor da classe.

Dart introduziu “extension” no ano passado (2019) e seu uso vem sendo ampliado pelos criadores de “package” como é o exemplo do pacote “Provider” que estendeu o BuildContext para incluír métodos read, watch, select como mecanismo para obter ou escutar alteração nos valores armazenados nas classes gerenciadas pelo “Provider”.

Anatomia de “extension” – a simplicidade

extension [novo nome] on [classe] {
}

Neste ponto os métodos que forem implementados na nova classe ira estender a referência a classe original, talvez fique mais claro com alguns exemplos.

extension StringExtension on String{ 
   int parseInt()=>int.parse(this);
}

A classe “StringExtension” agora estende a classe “String” para incluír o método “parseInt()” que converte uma string em int:

  var texto = '123';
  /// converte para int
  int  i =  texto.parseInt();
  print(i);