Objeto: criação de um componente gráfico genérico para renderização de tabela a partir de objetos JSON.
Principais termos técnicos abordados: objetos JSON, List, DataTable, dicionários, map
Requisitos para as instruções funcionarem: navegador ou ide (VS Code, Android Studio etc.)
Requisitos para compreensão das instruções: noções básicas de programação e ter realizado receita anterior, com exercícios
Como ler essa receita: instruções, dentro dos passos da receita e que requerem ação sua no computador, estarão escritas em cor azul. Comentários sobre os passos estarão em fonte normal, cor preta. Comandos, código-fonte, termos técnicos ou configuração explícita, estarão com fonte diferenciada.
Observação: eventuais instruções de terminal serão comandos Linux. Adapte caso esteja usando outro sistema operacional
Eis uma interface familiar...
E eis a implementação de uma classe que renderiza o conteúdo do app, as cervejas e suas descrições.
(...)
class DataBodyWidget extends StatelessWidget {
List<String> objects;
DataBodyWidget( {this.objects = const [] });
@override
Widget build(BuildContext context) {
return Column(children: objects.map(
(obj) => Expanded(
child: Center(child: Text(obj)),
)
).toList());
}
}
(...)
Me parece uma boa ideia montar uma tabela decente ou algum outro componente nesse DataBodyWidget, não é mesmo?
Esse Column com uma ruma de Expanded/Text parece bem chinfrin.
Vamos lá.
Crie um novo projeto e cole o código inicial, para começar a bricadeira.
import 'package:flutter/material.dart';
void main() {
MyApp app = MyApp();
runApp(app);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(primarySwatch: Colors.deepPurple),
debugShowCheckedModeBanner:false,
home: Scaffold(
appBar: AppBar(
title: const Text("Dicas"),
),
body: DataBodyWidget(objects:[
"La Fin Du Monde - Bock - 65 ibu",
"Sapporo Premiume - Sour Ale - 54 ibu",
"Duvel - Pilsner - 82 ibu"
]),
bottomNavigationBar: NewNavBar(),
));
}
}
class NewNavBar extends StatelessWidget {
NewNavBar();
void botaoFoiTocado(int index) {
print("Tocaram no botão $index");
}
@override
Widget build(BuildContext context) {
return BottomNavigationBar(onTap: botaoFoiTocado, items: const [
BottomNavigationBarItem(
label: "Cafés",
icon: Icon(Icons.coffee_outlined),
),
BottomNavigationBarItem(
label: "Cervejas", icon: Icon(Icons.local_drink_outlined)),
BottomNavigationBarItem(label: "Nações", icon: Icon(Icons.flag_outlined))
]);
}
}
class DataBodyWidget extends StatelessWidget {
List<String> objects;
DataBodyWidget( {this.objects = const [] });
Expanded processarUmElemento(String obj){
return Expanded(
child: Center(child: Text(obj)),
);
}
@override
Widget build(BuildContext context) {
return Column(children: objects.map(
(obj) => Expanded(
child: Center(child: Text(obj)),
)
).toList());
}
}
Vamos nos deter um pouco ao código em destaque, lá na classe MyApp.
A gente está chamando a fábrica de DataBodyWidget com dados fixos, com a lista de strings ["La Fin Du Monde - Bock - 65 ibu", "Sapporo Premiume - Sour Ale - 54 ibu", "Duvel - Pilsner - 82 ibu"].
Assim, a classe DataBodyWidget foi programada para ser um componente um tanto genérico, que exibe listas de quaisquer strings e sem restrições de tamanho, mas a gente chama a fábrica sempre passando os mesmos parâmetros, o que deixa o aplicativo meio engessado. Em programação, a gente diz que os dados passados para essa fábrica estão harcoded, e isso é um termo pejorativo. Significa que, se você quiser mudar os dados que estão sendo passados para a fábrica, você tem que editar o código-fonte, compilar e rodar o aplicativo novamente. Coisa que a gente faz quando está aprendendo, mas que é, nitidamente, uma roubada.
Pior que isso, os dados do aplicativo estão hardcoded numa classe de interface com o usuário.
Vamos começar corrigindo essa parte.
Deixe seu código como a seguir.
import 'package:flutter/material.dart';
var dataObjects = [
"La Fin Du Monde - Bock - 65 ibu",
"Sapporo Premiume - Sour Ale - 54 ibu",
"Duvel - Pilsner - 82 ibu"
];
void main() {
MyApp app = MyApp();
runApp(app);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(primarySwatch: Colors.deepPurple),
debugShowCheckedModeBanner:false,
home: Scaffold(
appBar: AppBar(
title: const Text("Dicas"),
),
body: DataBodyWidget(objects:dataObjects),
bottomNavigationBar: NewNavBar(),
));
}
}
class NewNavBar extends StatelessWidget {
NewNavBar();
void botaoFoiTocado(int index) {
print("Tocaram no botão $index");
}
@override
Widget build(BuildContext context) {
return BottomNavigationBar(onTap: botaoFoiTocado, items: const [
BottomNavigationBarItem(
label: "Cafés",
icon: Icon(Icons.coffee_outlined),
),
BottomNavigationBarItem(
label: "Cervejas", icon: Icon(Icons.local_drink_outlined)),
BottomNavigationBarItem(label: "Nações", icon: Icon(Icons.flag_outlined))
]);
}
}
class DataBodyWidget extends StatelessWidget {
List<String> objects;
DataBodyWidget( {this.objects = const [] });
Expanded processarUmElemento(String obj){
return Expanded(
child: Center(child: Text(obj)),
);
}
@override
Widget build(BuildContext context) {
return Column(children: objects.map(
(obj) => Expanded(
child: Center(child: Text(obj)),
)
).toList());
}
}
Note que separamos uma lista...
var dataObjects = [
"La Fin Du Monde - Bock - 65 ibu",
"Sapporo Premiume - Sour Ale - 54 ibu",
"Duvel - Pilsner - 82 ibu"
];
...fora de qualquer classe ou método. Isso significa que essa informação dataObjects é uma variável global e pode ser acessada simplesmente a partir de qualquer componente de seu app. E está sendo acessada justamente na chamada à fábrica do DataBodyWidget, lá na classe MyApp...
(...)
body: DataBodyWidget(objects:dataObjects)
(...)
A interface gráfica gráfica continua a mesma.
Pode rodar, se quiser, ou acreditar em mim.
- Ah, professor, mas não continua hardcoded não? Pra gente mudar o que se vê na interface do app, tem que alterar a lista dataObjects, recompilar e rodar...
Continua mesmo. O ponto é que não está hardcoded dentro dos componentes de interface. A chamada à fábrica não sabe que está hardcoded, ela puxa os itens de uma variável.
E variável, como você sabe, a gente pode mudar em tempo de execução. Em receitas mais adiante a gente poderá mudar essa variável (tirar ou por strings) e pedir para o flutter reconstruir o componente que a usa para que os valores atualizadod constem na interface.
Mas para cumprir o objetivo desta receita a gente vai apenas deixá-los hardcoded fora da interface gráfica.
Porque, por ora, a gente quer montar uma tabela decente em vez de uma coluna de textos, lembra?
A gente pode montar tabelas mais bonitinhas com o widget DataTable.
Da forma mais simples possível, a gente pode chamar a fábrica do DataTable passando os parâmetros columns e rows.
O código do build do DataBodyWidget está assim:
Widget build(BuildContext context) {
return Column(children: objects.map(
(obj) => Expanded(
child: Center(child: Text(obj)),
)
).toList());
}
A gente pode usar a mesma ideia e mapear cada elemento de objects numa lista de linhas de tabela em vez de mapear em objetos Expanded, e bote uma coisa na sua cabeça desde já: qualquer recanto de interface gráfica em que cabe uma lista está pedindo desesperadamente para você chamar um map.
A gente vai precisar disso ainda nessa receita e eu sei que você vai esquecer, então vou escrever de novo e em negrito:
Qualquer recanto de interface gráfica em que cabe uma lista está pedindo desesperadamente para você chamar um map.
Então, se apaixone logo pelo map, que ele vai ser importantíssimo, inclusive em Programação Web, com JavaScript.
Agora edite sua classe DataBodyWidget e deixe-a como a seguir.
class DataBodyWidget extends StatelessWidget {
List<String> objects;
DataBodyWidget( {this.objects = const [] });
@override
Widget build(BuildContext context) {
return DataTable(
columns: [
DataColumn(label: Expanded(
child: Text("Descrição", style: TextStyle(fontStyle: FontStyle.italic)),
))
],
rows: objects.map(
(obj) => DataRow(
cells:[
DataCell(Text(obj))
]
)
).toList());
}
}
Note que, essencialmente, em vez de nosso map criar uma lista de Expanded com base nos strings da lista objects, está criando uma lista de DataRow.
Rode a criança e veja como fica a interface...
Está mais legal, sem dúvida, e a gente aprendeu a usar os widgets DataTable, DataRow e DataColumn.
O problema é aquele ditado milenar japonês que diz "uma tabela com uma só coluna não é uma porra de uma tabela, é uma lista".
Acontece que essa nossa interface está mais para tabela mesmo: a gente ajuntou 3 informações num string só: nome, estilo e ibu da cerveja. Se nosso app exibe essas 3 informações, então dava pra fazer uma tabela com 3 colunas e a coisa ficava mais coerente.
A questão é que, na lista dataObjects, que guarda os strings que servem de base para a montagem da tabela, essas informações estão juntas...
var dataObjects = [
"La Fin Du Monde - Bock - 65 ibu",
"Sapporo Premiume - Sour Ale - 54 ibu",
"Duvel - Pilsner - 82 ibu"
];
Convém, primeiro, separá-las.
Modifique seu código na declaração da variável global dataObjects.
var dataObjects = [
{
"name": "La Fin Du Monde",
"style": "Bock",
"ibu": "65"
},
{
"name": "Sapporo Premiume",
"style": "Sour Ale",
"ibu": "54"
},
{
"name": "Duvel",
"style": "Pilsner",
"ibu": "82"
}
];
Claro que a coisa ficou mais complicada, mas também não é nada de outro mundo, porque a coisa ficou mais estruturada.
Se você sacar bem, continuamos tendo uma lista de 3 objetos, só que esses objetos não são mais strings. Esses objetos estão delimitados por abre/fecha chaves, que destaquei em negrito no código.
Vamos aprender a entender e escrever esses objetos. Vou precisar de 1 real e 99 a mais de texto, mas é necessário.
Essa forma de escrever objetos é conhecida como JSON - JavaScript Object Notation.
Isso, os objetos são objetos Dart, mas a sintaxe vem do JavaScript, é abertamente JSON.
Assim, vamos aprender a definir e acessar objetos Dart que seguem a sintaxe JSON - lêia jêizon. Precisamos, para isso, entender as regras de criação desses objetos, as regras sintáticas, especificamente. As regras são bem simples, bem simples mesmo, e bem poucas. Eu consigo identificar 3, apenas, ou 4 ou 5, no máximo 6. Eu vou explicá-las através da revolucionária pedagogia do cão chupando manga. Preciso sair do exemplo das cervejas, mas logo volto a ele.
Regra 1 - UM abre/fecha chaves indica UM objeto, e o que está dentro do abre/fecha chaves descreve, CLARO, o conteúdo do objeto.
Assim, o código JSON...
{
CÃO_CHUPANDO_MANGA
}
...descreve UM objeto cujo conteúdo é o cão chupando manga.
- Mas é objeto de que classe, professor?
Por enquanto, de nenhuma - não vamos pensar nisso agora. Pois no formato JSON a gente descreve os objetos diretamente, sem chamar fábricas, explicitando informações e métodos lá contidos na forma de propriedades. Mas isso não é um objeto JSON ainda, porque a gente precisa se livrar do cão chupando manga. E para isso a gente precisa das outras regrinhas.
Regra 2 - Dentro de um abre/fecha chaves, descreve-se o conteúdo de um objeto, SEMPRE escrevendo o nome de uma propriedade (ou atributo) ENTRE ASPAS (restrição do Dart - em JS não precisa), seguido do sinal de dois pontos, seguido do valor daquela propriedade.
Assim, o código JSON...
{
"nome": CÃO_CHUPANDO_MANGA ,
"idade": CÃO_CHUPANDO_MANGA,
"enderecos": CÃO_CHUPANDO_MANGA
}
...descreve um objeto com as propriedades nome, idade e enderecos. Os diabos agora são os valores dessas propriedades. Vamos nos livrar deles.
Dos diabos.
Desses, ao menos.
Regra 3 - Os valores, aqueles depois do dois-pontos, podem ser literais string, números, booleanos ou outros objetos delimitados por { }.
Assim, o código JSON...
{
"nome": "Chicó" ,
"idade": 27 ,
"enderecos": CÃO_CHUPANDO_MANGA
}
...estabelece valores para as propriedades nome e idade. Falta a propriedade enderecos.
Regra 4 - Valores também podem ser vetores. Ou, como a gente costuma chamar, listas. UM abre/fecha colchetes indica, ora vejam, UM vetor, e dentro do abre/fecha colchetes se descrevem os valores do vetor separados, ora vejam, por vírgula.
Vamos por um vetor na propriedade enderecos. Aqui vai...
{
"nome": "Chicó" ,
"idade": 27 ,
"enderecos": [CÃO_CHUPANDO_MANGA, CÃO_CHUPANDO_MANGA, CÃO_CHUPANDO_MANGA]
}
- Ah, mas agora ficamos com uma ruma de cão chupando manga de novo...
Sim, mas esses chifrudos dentro do vetor são valores, e a eles aplica-se a Regra 3. Podemos reescrevê-los assim...
{
"nome": "Chicó" ,
"idade": 27 ,
"enderecos": [10, 77, 47]
}
Nesse caso enderecos seria um vetor/lista de inteiros, o que é ilustrativo mas não é bem o que a gente espera de um vetor identificado por "enderecos". Podemos escrever, ainda...
{
"nome": "Chicó" ,
"idade": 27 ,
"enderecos": ["Viva", 5, true]
}
Eu não estou dizendo que é bonito, mas está dentro das regras. Mas como o mundo selvagem da programação dá liberdade da gente fazer as coisas mais ou menos certas, vamos melhorar esse objeto...
{
"nome": "Chicó" ,
"idade": 27 ,
"enderecos": ["Rua da Compadecida 5, Taperoá/PB", "Rua da Padaria 19, Cabaceiras/PB"]
}
Regra 5 - Os valores das propriedades também podem ser funções. Essa tava na cara, estamos falando de objetos, afinal de contas.
{
"nome": "Chicó" ,
"idade": 27 ,
"enderecos": ["Rua da Compadecida 5, Taperoá/PB", "Rua da Padaria 19, Cabaceiras/PB"],
"frase": () => "não sei, só sei que foi assim"
}
Aqui cabe uma ressalva. Escreva funções em objetos JSON com bastante moderação. Eu aconselho não escrever nunca, e a gente não vai escrever nos próximos exemplos.
É que o JSON é, essencialmente, uma liguagem e intercâmbio de informação. São objetos "leves", objetos de dados. Dá pra compreendê-los facilmente, convertê-los facilmente em texto e, em seguida salvá-los facilmente num arquivo ou enviá-los facilmente pela Internet. Um banco de dados NoSQL ou uma API de algum site pode te dar um texto no formato JSON como resposta a alguma requisição. Esse texto pode facilmente ser convertido em objetos Dart para ser processado e transformado em alguma interface com o usuário, ou pode ser processado no formato JSON mesmo.
Um pouco mais adiante, a gente vai entender como processar objetos JSON.
- Ah, professor, e se a gente quisesse um array de objecos como esse que a gente acabou de descrever?
Isso nos leva à...
...Regra 6 Valores dentro de um vetor/lista podem ser, em si, objetos JSON também.
Assim, basta-nos empurrar os vários objetos entre colchetes, separados por vírgula...
[
{
"nome": "Chicó" ,
"idade": 27 ,
"enderecos": ["Rua da Compadecida 5, Taperoá/PB", "Rua da Padaria 19, Cabaceiras/PB"]
},
{
"nome": "João Grilo" ,
"idade": 29 ,
"enderecos": ["Rua da Compadecida 57, Taperoá/PB", "Rua da Tapa 99, Serra Branca/PB"]
},
{
"nome": "Major Antônio Morais" ,
"idade": 65 ,
"enderecos": ["Rua da Fazenda sn, Taperoá/PB", "Rua da Prata 1, Campina Grande/PB"]
}
]
Agora tá mais parecido com a lista de objetos em nosso app, não é mesmo?
Mas antes de voltar às cervejas, note que essa regra nos permite, também, ajeitar aquela propriedade enderecos nos objetos...
[
{
"nome": "Chicó" ,
"idade": 27 ,
"enderecos": [
{
"rua": "Rua da Compadecida 5",
"cidade": "Taperoá/PB"
},
{
"rua": "Rua da Padaria 19",
"cidade": "Cabaceiras/PB"
}
]
},
{
"nome": "João Grilo" ,
"idade": 29 ,
"enderecos": [
{
"rua": "Rua da Compadecida 57",
"cidade": "Taperoá/PB"
},
{
"rua": "Rua da Tapa 99",
"cidade": "Serra Branca/PB"
}
]
},
{
"nome": "Major Antônio Morais" ,
"idade": 65 ,
"enderecos": [
{
"rua": "Rua da Fazenda sn",
"cidade": "Taperoá/PB"
},
{
"rua": "Rua da Prata 1",
"cidade": "Campina Grande/PB"
}
]
}
]
Mais que isso, podemos criar um objeto que engloba todo esse vetor numa propriedade e que tem outras propriedades e atribuí-o a uma variável.
var filme = {
"titulo": "Auto da Compadecida",
"diretor": "Guel Arraes",
"personagens": [
{
"nome": "Chicó" ,
"idade": 27 ,
"enderecos": [
{
"rua": "Rua da Compadecida 5",
"cidade": "Taperoá/PB"
},
{
"rua": "Rua da Padaria 19",
"cidade": "Cabaceiras/PB"
}
]
},
{
"nome": "João Grilo" ,
"idade": 29 ,
"enderecos": [
{
"rua": "Rua da Compadecida 57",
"cidade": "Taperoá/PB"
},
{
"rua": "Rua da Tapa 99",
"cidade": "Serra Branca/PB"
}
]
},
{
"nome": "Major Antônio Morais" ,
"idade": 65 ,
"enderecos": [
{
"rua": "Rua da Fazenda sn",
"cidade": "Taperoá/PB"
},
{
"rua": "Rua da Prata 1",
"cidade": "Campina Grande/PB"
}
]
}]
};
- E se a gente quiser acessar uma propriedade de um objeto desses? podemos fazes filme.diretor, como nos atributos do objetos "normais"?
No JS, pode. No Dart, você tem que acessar com indexação, por colchetes. Pra acessar o diretor, por exemplo, bastaria fazer filme["diretor"]. Pra acessar o segundo endereço do personagem Chicó, ficaria: filme["personagens"][0]["enderecos"][1].
- Vige Maria...
Veja se não se perde...
filme
... é aquele objetão todo...
filme["personagens"]
... é uma lista de objetos JSON...
filme["personagens"][0]
... é um objeto JSON, aquele cuja propriedade nome é "Chicó"...
filme["personagens"][0]["enderecos"]
... é uma lista de objetos JSON...
filme["personagens"][0]["enderecos"][1]
... é um objeto JSON que representa o segundo endereço do personagem Chicó. Dava pra gente fazer, ainda...
var c = filme["personagens"][0]["enderecos"][1]["cidade"]
... e o valor atribuído à variável c seria "Cabaceiras/PB".
Não se preocupe que a gente não vai ficar acessando essas propriedades desse jeito, mas é importante entender essa relação hierárquica de um objeto que está dentro do outro que está dentro do outro...
Porque é exatamente assim que a gente representa nossas interfaces gráficas, não é mesmo? Um MaterialApp tem um Scaffold (ente outros) que tem um DataBodyWidget (entre outros)...
Assim, usamos hierarquias de objetos para montar nossos componentes gráficos e também usamos hirarquias de objetos para montar nossos dados, os dados a partir dos quais as iterfaces são construídas.
Agora podemos voltar à nossa lista de cervejas...
var dataObjects = [
{
"name": "La Fin Du Monde",
"style": "Bock",
"ibu": "65"
},
{
"name": "Sapporo Premiume",
"style": "Sour Ale",
"ibu": "54"
},
{
"name": "Duvel",
"style": "Pilsner",
"ibu": "82"
}
];
Porque a estruturação desses dados gerou um erro de compilação com a provável seguinte mensagem: The argument type 'List<Map<String, String>>' can't be assigned to the parameter type 'List<String>'.
Vamos corrigir isso.
O problema acontece porque lá em MyApp...
(...)
body: DataBodyWidget(objects:dataObjects),
(...)
... a gente chama a fábrica de DataBodyWidget passando dataObjects, que agora é uma lista de JSON e não mais uma lista de String. Acontece que a gente programou a classe DataBodyWidget...
class DataBodyWidget extends StatelessWidget {
List<String> objects;
DataBodyWidget( {this.objects = const [] });
(...)
...para trabalhar com lista de String mesmo.
Modifique a classe DataBodyWidget.
class DataBodyWidget extends StatelessWidget {
List objects;
DataBodyWidget( {this.objects = const [] });
@override
Widget build(BuildContext context) {
return DataTable(
columns: [
DataColumn(label: Expanded(
child: Text("Descrição", style: TextStyle(fontStyle: FontStyle.italic)),
))
],
rows: objects.map(
(obj) => DataRow(
cells:[
DataCell(Text('$obj'))
]
)
).toList());
}
}
- Oxe, é só isso, homi?
Bem, a gente tinha uma lista de String e não tem mais, tínhamos que tirar o String de lá. Podemos deixar como um List genérico, assim como ficou.
- Mas qual fica sendo o tipo exato se a gente quiser declarar um List de JSON, professor? Seria List<JSON> ?
Não. No Dart um objeto JSON assume o tipo Map<String,dynamic>. É feio assim mesmo e os caras não quiseram dar um tipo nativo para o JSON.
- E o que é esse Map? Tem a ver com o google maps?
Bem, o Map é uma coleção de objetos. Ou seja, é um objeto especial que armazena vários outros objetos dentro de si, como um List, que, genericamente falando, também é uma coleção. Mas, diferentemente do List, o Map não indexa seu conteúdo necesariamente por números inteiros. Pode indexá-los por qualquer coisa, incluindo Strings. Assim, um objeto m do tipo Map<String,dynamic> é uma coleção que guarda qualquer tipo de objeto dentro de si (dynamic) e dá acesso a eles através de um String, por indexação entre colchetes. Exatamente como fazemos com um objeto JSON - var v = m["isso"], var v2 = m["aquilo"].
Mas o nosso atributo objects que é atributo de DataBodyWidget é um List de objetos JSON, então se o Dart trata objetos JSON como Map<String,dynamic>, a gente precisaria declarar o atributo assim:
List<Map<String,dynamic>> objects;
- Vige, melhor deixar só List objects mermo...
Pois é, mas não se assuste com objetos Map. Eles são uma estrutura de dados conhecida como dicionários. Mapeiam uma chave (String, no nosso caso), num valor (dynamic, ou qualquer objeto, no nosso caso). Em Dart, JS e Python, a gente acessa elementos de dicionários através de colchetes, com indexação. E coloca objetos lá através de colchetes também. Em Java, precisamos chamar métodos get e put. Ou seja, há em tudo que é linguagem essa estrutura de dados já pronta, basta declarar e usar, então não reimplemente dicionários - não reinvente a roda!
Rode o app.
A nossa interface ficou assim...
Ou seja, tá pior que antes...
Isso acontece porque cada objeto agora é um JSON com propriedade e valores, o trecho...
(...)
DataCell(Text('$obj'))
(...)
tá mandando transformar cada objeto em string do jeito que der. Pois bem, aquele negócio feio que a gente vê é o jeito que dá.
Mas a gente pode fazer do jeito que a gente quiser também.
Porque a gente sabe acessar propriedades de objetos JSON.
Modifique a classe DataBodyWidget.
class DataBodyWidget extends StatelessWidget {
List objects;
DataBodyWidget( {this.objects = const [] });
@override
Widget build(BuildContext context) {
return DataTable(
columns: [
DataColumn(label: Expanded(
child: Text("Nome", style: TextStyle(fontStyle: FontStyle.italic)),
)),
DataColumn(label: Expanded(
child: Text("Estilo", style: TextStyle(fontStyle: FontStyle.italic)),
)),
DataColumn(label: Expanded(
child: Text("IBU", style: TextStyle(fontStyle: FontStyle.italic)),
))
],
rows: objects.map(
(obj) => DataRow(
cells:[
DataCell(Text(obj["name"])),
DataCell(Text(obj["style"])),
DataCell(Text(obj["ibu"]))
]
)
).toList());
}
}
Agora está bem melhor, não é mesmo. Dê uma sacada na interface...
Só que eu disse lá atrás e disse que você esqueceria: Qualquer recanto de interface gráfica em que cabe uma lista está pedindo desesperadamente para você chamar um map.
E estamos falando da função de alta ordem map, e não dos dicionários.
E há ao menos duas listas - cells e columns - gritando por nós ali na classe DataBodyWidget.
Não as decepcionemos.
Modifique a classe DataBodyWidget.
class DataBodyWidget extends StatelessWidget {
List objects;
DataBodyWidget( {this.objects = const [] });
@override
Widget build(BuildContext context) {
var columnNames = ["Nome","Estilo","IBU"],
propertyNames = ["name", "style", "ibu"];
return DataTable(
columns: columnNames.map(
(name) => DataColumn(
label: Expanded(
child: Text(name, style: TextStyle(fontStyle: FontStyle.italic))
)
)
).toList()
,
rows: objects.map(
(obj) => DataRow(
cells: propertyNames.map(
(propName) => DataCell(Text(obj[propName]))
).toList()
)
).toList());
}
}
Reexecutando a interface fica rigorosamente a mesma, sendo que o código está bem melhor.
Na verdade, falta bem pouco para termos um componente razoavelmente genérico e reutilizável com a classe DataBodyWidget. Vamos tentar solucionar os problemas restantes nos exercícios.
Bem, vocês vão.
Documentação oficial do DataTable, com vídeo
Vídeos sobre a função map (e alguns sobre dicionários)
Faça com que a tabela seja deslizável verticalmente, para quando houver mais dados nela. Acrescente novos objetos ao List dataObjects pra testar.
Tem um componente bem interessante, o ListView e o ListTile. Copie e cole a classe DataBodyWidget. Renomeie a classe colada e sua fábrica para MytileWidget. O build dessa nova classe deve apresentar os dados em tiles e não mais num DataTable. Esteja livre para modificar o que for necessário na classe, inclusive fábrica e atributos. Faça um programa principal que teste seu novo componente.
Na tabela original da receita, há um defeito enorme para torná-la reutilizável. Ela apenas exibe colunas com os nomes Nome, Estilo e IBU. Não faz sentido se for pra exibir dados de café ou de países, por exemplo. A tabela também apenas exibe propriedades identificadas por name, style e ibu. Numa lista de objetos JSON de outra coisa que não seja cerveja, dificilmente os objetos da lista terão essas propriedades. Contorne essas restrições. Um objeto de sua classe DataBodyWidget deve ser capaz de mostrar, numa tabela, o que a gente bem entender. Esteja livre para modificar o que quiser, incluindo fábrica, atributos, método build etc. A solução é mais difícil de pensar do que de implementar então, se você viajar muito na maionese, provavelmente estará errado
Faça o mesmo da questão anterior em relação à questão 2.