Flutter. Chaves! Para que eles servem?


O parâmetro- key pode ser encontrado em quase todos os construtores de widgets, mas esse parâmetro raramente é usado no desenvolvimento. Keys mantêm o estado ao mover widgets na árvore de widgets. Na prática, isso significa que eles podem ser úteis para salvar o local de rolagem ou o estado do usuário quando a coleção for alterada.


Este artigo foi adaptado do vídeo a seguir. Se você preferir ouvir / assistir, em vez de ler, o vídeo fornecerá o mesmo material.


Informações secretas sobre keys


Na maioria das vezes ... você não precisa de keys . Em geral, não há mal em adicioná-los, mas isso também não é necessário, pois eles simplesmente ocorrem como uma nova palavra-chave ou declaração de tipo nos dois lados de uma nova variável (estou falando de você, Map<Foo, Bar> aMap = Map<Foo, Bar>() ).


Mas se você achar que está adicionando, removendo ou reorganizando widgets na coleção que contêm algum estado e são do mesmo tipo, preste atenção às keys !

Para demonstrar por que você precisa de keys ao alterar uma coleção de widgets, escrevi um aplicativo extremamente simples com dois widgets coloridos que mudam de lugar quando um botão é clicado:



Nesta versão do aplicativo, tenho dois widgets sem estado de cores aleatórias ( StatelessWidget ) no widget Row e PositionedTiles com o estado ( StatefulWidget ) para armazenar a ordem dos widgets de cores. Quando clico no botão FloatingActionButton na parte inferior, os widgets de cores mudam corretamente de lugar na lista:


 void main() => runApp(new MaterialApp(home: PositionedTiles())); class PositionedTiles extends StatefulWidget { @override State<StatefulWidget> createState() => PositionedTilesState(); } class PositionedTilesState extends State<PositionedTiles> { List<Widget> tiles = [ StatelessColorfulTile(), StatelessColorfulTile(), ]; @override Widget build(BuildContext context) { return Scaffold( body: Row(children: tiles), floatingActionButton: FloatingActionButton( child: Icon(Icons.sentiment_very_satisfied), onPressed: swapTiles), ); } swapTiles() { setState(() { tiles.insert(1, tiles.removeAt(0)); }); } } class StatelessColorfulTile extends StatelessWidget { Color myColor = UniqueColorGenerator.getColor(); @override Widget build(BuildContext context) { return Container( color: myColor, child: Padding(padding: EdgeInsets.all(70.0))); } } 

Mas se adicionarmos estado aos nossos widgets de cores (torná-los StatefulWidget ) e armazenar a cor neles, quando clicarmos no botão, parecerá que nada está acontecendo:



 List<Widget> tiles = [ StatefulColorfulTile(), StatefulColorfulTile(), ]; ... class StatefulColorfulTile extends StatefulWidget { @override ColorfulTileState createState() => ColorfulTileState(); } class ColorfulTileState extends State<ColorfulTile> { Color myColor; @override void initState() { super.initState(); myColor = UniqueColorGenerator.getColor(); } @override Widget build(BuildContext context) { return Container( color: myColor, child: Padding( padding: EdgeInsets.all(70.0), )); } } 

Como explicação: o código acima é incorreto, pois não mostra a troca de cores quando o usuário clica no botão. Para corrigir esse erro, você precisa adicionar o parâmetro key aos widgets coloridos StatefulWidget e, em seguida, os widgets serão trocados conforme desejado:



 List<Widget> tiles = [ StatefulColorfulTile(key: UniqueKey()), // Keys added here StatefulColorfulTile(key: UniqueKey()), ]; ... class StatefulColorfulTile extends StatefulWidget { StatefulColorfulTile({Key key}) : super(key: key); // NEW CONSTRUCTOR @override ColorfulTileState createState() => ColorfulTileState(); } class ColorfulTileState extends State<ColorfulTile> { Color myColor; @override void initState() { super.initState(); myColor = UniqueColorGenerator.getColor(); } @override Widget build(BuildContext context) { return Container( color: myColor, child: Padding( padding: EdgeInsets.all(70.0), )); } } 

Mas isso só é necessário se você tiver widgets com o estado na subárvore que você está alterando. Se a subárvore inteira do widget em sua coleção não tiver estado, nenhuma chave será necessária.
Lá vai você! Em suma, tudo o que você precisa saber para usar as keys no Flutter . Mas se você quiser se aprofundar um pouco no que está acontecendo ...




Entendendo por que as keys às vezes keys necessárias


Você ainda está aqui, certo? Bem, então chegue mais perto para descobrir a verdadeira natureza das árvores e widgets de elementos para se tornar um Mago Flutter! Wahahaha! Haha Haha Desculpe.


Como você sabe, dentro de cada widget, o Flutter cria o elemento correspondente. Assim como o Flutter cria uma árvore de widgets, ele também cria uma árvore de elementos (ElementTree). O ElementTree é extremamente simples, contém apenas as informações de tipo de cada widget e um link para elementos filho. Você pode pensar em ElementTree como o esqueleto do seu aplicativo Flutter. Ele mostra a estrutura do seu aplicativo, mas todas as informações adicionais podem ser encontradas no link para o widget de origem.


O widget de linha no exemplo acima contém um conjunto de slots ordenados para cada um de seus filhos. Quando reordenamos os widgets de cores em Row, o Flutter percorre o ElementTree para verificar se a estrutura do esqueleto do aplicativo é a mesma.



A validação começa com um RowElement e depois passa para os filhos. O ElementTree verifica se o novo widget tem o mesmo tipo e key que o antigo e, se houver, o elemento atualiza seu link para o novo widget. Na versão sem estado do código, os widgets não têm key , portanto o Flutter simplesmente verifica apenas o tipo. (Se houver muita informação de cada vez, consulte o gráfico animado acima.)


Abaixo ElementTree para widgets de estado parece um pouco diferente. Existem widgets e elementos, como antes, mas também existem alguns objetos de estado para widgets, e as informações de cores são armazenadas neles, e não nos próprios widgets.



No caso de widgets coloridos StatefulWidget sem key , quando altero a ordem de dois widgets, o Flutter percorre o ElementTree, verifica o tipo de RowWidget e atualiza o link. Em seguida, o elemento do widget de cores verifica se o widget correspondente é do mesmo tipo e atualiza o link. O mesmo acontece com o segundo widget. Como o Flutter usa o ElementTree e seu estado correspondente para determinar o que realmente será exibido no seu dispositivo, do nosso ponto de vista, parece que os widgets não foram trocados!



Na versão fixa do código em widgets coloridos com estado no construtor, defini a propriedade key . Agora, se alterarmos os widgets em Row , por tipo eles corresponderão como antes, mas os valores- key do widget de cores e o elemento correspondente no ElementTree serão diferentes. Isso faz com que o Flutter desative esses elementos dos widgets de cores e remova os links para eles no ElementTree, começando pelo primeiro, que não corresponde à key .



Em seguida, o Flutter pesquisa os widgets no elemento Row no ElementTree com a key correspondente. Se corresponder, ele adiciona um link ao elemento do widget. Flutter faz para cada criança sem um link. Agora o Flutter exibirá o que esperamos, os widgets de cores mudarão de lugar quando eu clicar no botão.


Portanto, as keys são úteis se você alterar a ordem ou o número de widgets com o estado na coleção. Neste exemplo, salvei a cor. No entanto, muitas vezes a condição não é tão óbvia. Reproduzindo uma animação, exibindo a entrada do usuário e percorrendo um local - tudo tem um estado.




Quando devo usar keys ?


Resposta curta: se você precisar adicionar keys ao aplicativo, adicione-as na parte superior da subárvore do widget com o estado que deseja salvar.


Um erro comum que vi é que as pessoas pensam que precisam definir key apenas para o primeiro widget com estado, mas há nuances. Não acredita em mim? Para mostrar em que problemas podemos estar, envolvi meus widgets de cores nos widgets Padding , deixando as teclas para os widgets de cores.


 void main() => runApp(new MaterialApp(home: PositionedTiles())); class PositionedTiles extends StatefulWidget { @override State<StatefulWidget> createState() => PositionedTilesState(); } class PositionedTilesState extends State<PositionedTiles> { // Stateful tiles now wrapped in padding (a stateless widget) to increase height // of widget tree and show why keys are needed at the Padding level. List<Widget> tiles = [ Padding( padding: const EdgeInsets.all(8.0), child: StatefulColorfulTile(key: UniqueKey()), ), Padding( padding: const EdgeInsets.all(8.0), child: StatefulColorfulTile(key: UniqueKey()), ), ]; @override Widget build(BuildContext context) { return Scaffold( body: Row(children: tiles), floatingActionButton: FloatingActionButton( child: Icon(Icons.sentiment_very_satisfied), onPressed: swapTiles), ); } swapTiles() { setState(() { tiles.insert(1, tiles.removeAt(0)); }); } } class StatefulColorfulTile extends StatefulWidget { StatefulColorfulTile({Key key}) : super(key: key); @override ColorfulTileState createState() => ColorfulTileState(); } class ColorfulTileState extends State<ColorfulTile> { Color myColor; @override void initState() { super.initState(); myColor = UniqueColorGenerator.getColor(); } @override Widget build(BuildContext context) { return Container( color: myColor, child: Padding( padding: EdgeInsets.all(70.0), )); } } 

Agora, com o toque de um botão, os widgets ficam com cores completamente aleatórias!



É assim que a árvore de widgets e o ElementTree se parecem com os widgets Padding adicionados:



Quando mudamos as posições dos widgets filhos, o algoritmo de correspondência entre elementos e widgets parece um nível na árvore de elementos. No diagrama, os filhos das crianças são escurecidos para que nada desvie o interesse do primeiro nível. Nesse nível, tudo corresponde corretamente.



No segundo nível, o Flutter percebe que a key elemento de cor não corresponde à key widget; portanto, ele desativa esse elemento, descartando-o e removendo todos os links para ele. keys que usamos neste exemplo são as LocalKeys . Isso significa que, ao combinar um widget com elementos, o Flutter procura keys apenas em um determinado nível da árvore.


Como ele não consegue encontrar o elemento do widget de cores nesse nível com a key correspondente, ele cria um novo e inicializa um novo estado, deixando o widget laranja nesse caso!



Se definirmos keys para os widgets Padding :


 void main() => runApp(new MaterialApp(home: PositionedTiles())); class PositionedTiles extends StatefulWidget { @override State<StatefulWidget> createState() => PositionedTilesState(); } class PositionedTilesState extends State<PositionedTiles> { List<Widget> tiles = [ Padding( // Place the keys at the *top* of the tree of the items in the collection. key: UniqueKey(), padding: const EdgeInsets.all(8.0), child: StatefulColorfulTile(), ), Padding( key: UniqueKey(), padding: const EdgeInsets.all(8.0), child: StatefulColorfulTile(), ), ]; @override Widget build(BuildContext context) { return Scaffold( body: Row(children: tiles), floatingActionButton: FloatingActionButton( child: Icon(Icons.sentiment_very_satisfied), onPressed: swapTiles), ); } swapTiles() { setState(() { tiles.insert(1, tiles.removeAt(0)); }); } } class StatefulColorfulTile extends StatefulWidget { StatefulColorfulTile({Key key}) : super(key: key); @override ColorfulTileState createState() => ColorfulTileState(); } class ColorfulTileState extends State<ColorfulTile> { Color myColor; @override void initState() { super.initState(); myColor = UniqueColorGenerator.getColor(); } @override Widget build(BuildContext context) { return Container( color: myColor, child: Padding( padding: EdgeInsets.all(70.0), )); } } 

O Flutter percebe o problema e atualiza os links corretamente, como no exemplo anterior. A ordem no universo é restaurada.





Que tipo de Key devo usar?


As APIs do Flutter nos deram a opção de várias classes de Key . O tipo de key você deve usar depende de qual é o recurso distintivo dos elementos que precisam de keys . Veja as informações que você armazena nos respectivos widgets.


Considere o seguinte aplicativo de tarefas [1], onde você pode alterar a ordem dos itens na lista de tarefas com base na prioridade e, quando terminar, pode excluí-los.



ValueKey
Nesse caso, podemos esperar que o texto do parágrafo sobre a implementação seja permanente e exclusivo. Nesse caso, esse provavelmente é um bom candidato para a ValueKey , onde o texto é "value".


 return TodoItem( key: ValueKey(todo.task), todo: todo, onDismissed: (direction) => _removeTodo(context, todo), ); 

Objectkey
Como alternativa, você pode ter o aplicativo Catálogo de Endereços, que lista informações sobre cada usuário. Nesse caso, cada widget filho armazena uma combinação mais complexa de dados. Qualquer um dos campos individuais, por exemplo, nome ou data de nascimento, pode coincidir com outra entrada, mas a combinação é exclusiva. Nesse caso, o ObjectKey provavelmente é o melhor ajuste.



Uniquekey
Se você tiver vários widgets na coleção com o mesmo valor ou se realmente quiser garantir que cada widget seja diferente de todos os outros, poderá usar o UniqueKey . Usei o UniqueKey no aplicativo de exemplo para alternar cores, porque não tínhamos outros dados constantes que seriam armazenados em nossos widgets e não sabíamos qual a cor que o widget teria ao ser criado.


No entanto, uma coisa que você não deseja usar como key é um número aleatório. Sempre que um widget é criado, um novo número aleatório será gerado e você perderá a consistência entre os quadros. Nesse cenário, você não pode usar keys !


PageStorageKeys
PageStorageKeys são keys especializadas que contêm o estado atual da rolagem para que o aplicativo possa salvá-lo para uso posterior.



Globalkeys
Há duas opções para usar as GlobalKeys : elas permitem que os widgets alterem os pais em qualquer lugar do aplicativo sem perder o estado e podem ser usadas para acessar informações sobre outro widget em uma parte completamente diferente da árvore de widgets. Como um exemplo do primeiro cenário, você pode imaginar que deseja mostrar o mesmo widget em duas telas diferentes, mas com o mesmo estado, para que os dados do widget sejam salvos, você usará o GlobalKey . No segundo caso, pode surgir uma situação quando você precisa verificar a senha, mas não deseja compartilhar informações de status com outros widgets na árvore. GlobalKeys também podem ser úteis para testes, usando a key para acessar um widget específico e solicitar informações sobre seu status.



Freqüentemente (mas nem sempre!), As GlobalKeys pouco como variáveis ​​globais. Geralmente, eles podem ser substituídos por InheritedWidgets ou algo como Redux, ou o modelo BLoC.




Breve conclusão


Em geral, use Keys se desejar manter o estado entre as subárvores do widget. Isso geralmente acontece quando você altera a coleção de widgets do mesmo tipo. Coloque a key na parte superior da subárvore do widget que você deseja salvar e selecione o tipo de key base nos dados armazenados no widget.


Parabéns, você está agora no caminho de se tornar um Flutter Mage! Oh, eu disse um mágico? Eu quis dizer o mágico [2], como quem escreve o código fonte do aplicativo ... o que é quase tão bom. quase.


[1] Inspiração para escrever o código do aplicativo de tarefas pendentes recebido aqui
https://github.com/brianegan/flutter_architecture_samples/tree/master/vanilla
[2] O autor usa a palavra sorcerer e depois adiciona uma carta extra a ele antes do sourcerer

Source: https://habr.com/ru/post/pt446050/


All Articles