Aplicativos reativos em Flutter - Parte I

Iremos aprender, conforme ensinado no vídeo "Build reactive mobile apps with Flutter (Google I/O '18)", qual a lógica aplicada em state (estados) para o desenvolvimento de aplicativos e dar início a uma série de artigos a respeito dos temas relacionados...

Em primeiro lugar, em Flutter, todos os componentes da interface do usuário são widgets. A boa notícia é que o Flutter fornece a você com todas as ferramentas necessárias, não apenas para construir a interface do usuário, mas também para lidar com fluxo de dados complexos de estados, bem com o modelo reativo de Flutter para construir interfaces de usuário.

Vejamos um exemplo de um widget:

class MyButton extends StatelessWidgets {
  @override
  Widget build(BUildContext context) =
    RaisedButton(
      child: Text("Enviar"),
      onPressed: _submit,
    );
}

Como dito acima, flutter gerencia a relação entre os estados e a interface do usuário e só recria esses widgets quando o seu estado mudo, vejamos um exemplo com a função _submit:

void _submit() {
  final nome = controller.text;
  setState(() {
    saudacao = 'Seja bem-vindo, nome';
  });
}

Então, por exemplo, quando chamamos setState() em saudacao, ele se redesenha automaticamente o que é muito bom, mas a situação pode ficar complicada quando se desenvolve aplicativos mais complicados, pois os aplicativos geralmente têm milhares de componentes de interface do usuário.

Portanto, flutter lhe fornece ferramentas necessárias, não apenas para construir a interface do usuário, mas também para lidar com fluxo de dados complexos e estados.

Apesar de os padrões de interface do usuário do Flutter serem bem conhecidos e bem documentado - você pode vê-los em flutter.io -, suas estratégias reativas para lidar com o estado não são tão bem conhecidas. Assim, iremos focar agora em como fazer isso.

Flutter e State

Para poder entender, iremos pegar como referência o aplicativo padrão de criação do Flutter, o incrementer app.

Basicamente a função do app é contar a quantidade de cliques  e mostrá-los na tela. Vamos ao código? 

class _MyHomePageState extends StateMyHomePage {
  int _counter = 0;

  @override
  Widget build(BuildContext context) {  
    return new Scaffold(   
      appBar: new AppBar(      
        title: new Text(widget.title),
      ),
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: Widget[
            new Text(
              'You have pushed the button this many times:',
            ),
            new Text(             
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),     
      floatingActionButton: new FloatingActionButton(
        onPressed: () {
          setState (() {
            _counter += 1;
          )};
        },
        tooltip: 'Increment',
        child: new Icon(Icons.add),
      ),
    );
  }
}

As partes importantes:

  • a) int _counter = 0; é a variável do contador;
  • b) Widget build(BuildContext context) {  é o método de construção que cria a interface do usuário;
  • c) '$_counter', aqui está o widget que mostra o número de cliques;
  • d) floatingActionButton: new FloatingActionButton( este é o botão flutuante com sinal de + que ao ser pressionado permite mudar a variável.

Agora, imagine que esse é um aplicativo muito maior com mais estrutura, então é isso que iremos fazer, reestruturá-lo.

Primeiro: vamos criar um widget chamado de Incrementador() e recortar o que está dentro do widget FloatingActionButton e colar nele:

class _MyHomePageState extends StateMyHomePage {
  int _counter = 0;

  @override
  Widget build(BuildContext context) {  
    return new Scaffold(   
      appBar: new AppBar(      
        title: new Text(widget.title),
      ),
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: Widget[
            new Text(
              'You have pushed the button this many times:',
            ),
            new Text(             
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),     
      floatingActionButton: Incrementador(),
    );
  }
}

Criaremos agora uma no classe StatefulWidget{} onde iremos colocar o conteúdo do FloatingActionButton():

class Incrementador extends StatefulWidget {
@override
_IncrementadorState createState() = _IncrementadorState();
}

class _IncrementadorState extends StateIncrementador {
@override
Widget build(BuildContext context) {
return FloatingActionButton(
onPressed: () {
setState (() {
_counter += 1;
)};
},
tooltip: 'Increment',
child: new Icon(Icons.add),
);
}
}

Entretanto, não basta criar esta função, pois não conseguiremos só com isso acessar a variável do contador _counter que está na classe _MyHomePageState.

Qual seria a solução? Podemos simplesmente tornar a variável global tirando ela da classe que esta. Mas esta não é a uma boa solução, porque só devemos mudar o state do widget em que estamos trabalhando e no código acima estamos no widget do FloatingActionButton, ou seja, o flutter irá tentar reconstruir o botão de ação flutuante e não irá mudar o contador.

Ok, entendi, mas então qual a melhor solução? kk, vamos lá, a melhor solução seria mover o state definido para o widget apropriado onde queremos mostrar a mudança que seria o _myHomePageState:

class _MyHomePageState extends StateMyHomePage {
  int _counter = 0;

void _incrementador(){
setState (() {
_counter += 1;
)};
}

  @override
  Widget build(BuildContext context) {  
    return new Scaffold(   
      appBar: new AppBar(      
        title: new Text(widget.title),
      ),
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: Widget[
            new Text(
              'You have pushed the button this many times:',
            ),
            new Text(             
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),     
      floatingActionButton: Incrementador(_incrementador),
    );
  }
}

Muito bom, agora precisamos passar para a classe Incrementador a função que acabamos de criar que, basicamente, irá chamar o _incrementador(). Faremos isso através de um construtor Incrementador(this.increment): e após iremos chamar com widget.increment:

class Incrementador extends StatefulWidget {
final Function increment;
Incrementador(this.increment);

@override
_IncrementadorState createState() = _IncrementadorState();
}

class _IncrementadorState extends StateIncrementador {
@override
Widget build(BuildContext context) {
return FloatingActionButton(
onPressed: widget.increment,
tooltip: 'Increment',
child: new Icon(Icons.add),
);
}
}

Deve ser assim que atualmente muitos utilizam o state, eu mesmo só programava em FLutter assim. No entanto imaginemos a criação de um um aplicativos com muitos widgets isto seria um grande problema. Para cada widget que você quer mudar o state você deve fazer exatamente o que fizemos para passar os valores e mudá-los ou exibi-los, ou seja, isso significa que todos os widgets que estão nesse caminho precisam saber sobre o state, mesmo que eles não precisem saber. Isso faz com que o aplicativo crie uma porção muito maior de estados da interface do usuário da que você deseja.

Portanto, destacamos aqui dois grande problemas:

  • a) como eu acesso estado que não necessariamente  está no widget onde preciso (accessing state)?
  • b) como eu notifico outros widgets que eles devem reconstruir (updating on change)?

Este tema ficará para o próximo artigo, clique aqui para lê-lo.

305 Visualizações